mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Merge remote-tracking branch 'nodered/master' into exec-with-stdin
This commit is contained in:
commit
516faf25a0
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [18, 20, 22.4.x]
|
node-version: [18, 20, 22]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
/Gruntfile.js
|
|
||||||
/.git/*
|
|
||||||
*.backup
|
|
||||||
/public/*
|
|
131
CHANGELOG.md
131
CHANGELOG.md
@ -1,3 +1,134 @@
|
|||||||
|
#### 4.0.9: Maintenance Release
|
||||||
|
|
||||||
|
Editor
|
||||||
|
|
||||||
|
- Add details for the dynamic subscription to match the English docs (#5050) @aikitori
|
||||||
|
- Fix tooltip snapping based on `typedInput` type (#5051) @GogoVega
|
||||||
|
- Prevent symbol usage warning in monaco (#5049) @Steve-Mcl
|
||||||
|
- Show subflow flow context under node section of sidebar (#5025) @knolleary
|
||||||
|
- feat: Add custom label for default deploy button in settings.editorTheme (#5030) @matiseni51
|
||||||
|
- Handle long auto-complete suggests (#5042) @knolleary
|
||||||
|
- Handle undefined username when generating user icon (#5043) @knolleary
|
||||||
|
- Handle dragging node into group and splicing link at same time (#5027) @knolleary
|
||||||
|
- Remember context sidebar tree state when refreshing (#5021) @knolleary
|
||||||
|
- Update sf instance env vars when removed from template (#5023) @knolleary
|
||||||
|
- Do not select group when triggering quick-add within it (#5022) @knolleary
|
||||||
|
- Fix library icon handling within library browser component (#5017) @knolleary
|
||||||
|
|
||||||
|
Runtime
|
||||||
|
- Allow env var access to context (#5016) @knolleary
|
||||||
|
- fix debug status reporting if null (#5018) @dceejay
|
||||||
|
- Fix grunt dev via better ndoemon ignore rules (#5015) @knolleary
|
||||||
|
- Fix typo in CHANGELOG (4.0.7-->4.0.8) (#5007) @natcl
|
||||||
|
|
||||||
|
Nodes
|
||||||
|
- Switch: Avoid exceeding call stack when draining message group in Switch (#5014) @knolleary
|
||||||
|
|
||||||
|
#### 4.0.8: Maintenance Release
|
||||||
|
|
||||||
|
Editor
|
||||||
|
|
||||||
|
- Fix config node sort order when importing (#5000) @knolleary
|
||||||
|
|
||||||
|
#### 4.0.7: Maintenance Release
|
||||||
|
|
||||||
|
Editor
|
||||||
|
|
||||||
|
- Fix def can be undefined if the type is missing (#4997) @GogoVega
|
||||||
|
- Fix the user list of nested config node (#4995) @GogoVega
|
||||||
|
- Support custom login message and button (#4993) @knolleary
|
||||||
|
|
||||||
|
#### 4.0.6: Maintenance Release
|
||||||
|
|
||||||
|
Editor
|
||||||
|
|
||||||
|
- Roll up various fixes on config node change history (#4975) @knolleary
|
||||||
|
- Add quotes when installing local tgz to fix spacing in the file path (#4949) @AGhorab-upland
|
||||||
|
- Validate json dropped into editor to avoid unhelpful error messages (#4964) @knolleary
|
||||||
|
- Fix junction insert position via context menu (#4974) @knolleary
|
||||||
|
- Apply zoom scale when calculating annotation positions (#4981) @knolleary
|
||||||
|
- Handle the import of an incomplete Subflow (#4811) @GogoVega
|
||||||
|
- Fix updating the Subflow name during a copy (#4809) @GogoVega
|
||||||
|
- Rename variable to avoid confusion in view.js (#4963) @knolleary
|
||||||
|
- Change groups.length to groups.size (#4959) @hungtcs
|
||||||
|
- Remove disabled node types from QuickAddDialog list (#4946) @GogoVega
|
||||||
|
- Fix `setModulePendingUpdated` with plugins (#4939) @GogoVega
|
||||||
|
- Missing getSubscriptions in the docs while its implemented (#4934) @ersinpw
|
||||||
|
- Apply `envVarExcludes` setting to `util.getSetting` into the function node (#4925) @GogoVega
|
||||||
|
- Fix `envVar` editable list should be sortable (#4932) @GogoVega
|
||||||
|
- Improve the node name auto-generated with the first available number (#4912) @GogoVega
|
||||||
|
|
||||||
|
Runtime
|
||||||
|
|
||||||
|
- Get the env config node from the parent subflow (#4960) @GogoVega
|
||||||
|
- Update dependencies (#4987) @knolleary
|
||||||
|
|
||||||
|
Nodes
|
||||||
|
|
||||||
|
- Performance : make reading single buffer / string file faster by not re-allocating and handling huge buffers (#4980) @Fadoli
|
||||||
|
- Make delay node rate limit reset consistent - not send on reset. (#4940) @dceejay
|
||||||
|
- Fix trigger node date handling for latest time type input (#4915) @dceejay
|
||||||
|
- Fix delay node not dropping when nodeMessageBufferMaxLength is set (#4973)
|
||||||
|
- Ensure node.sep is honoured when generating CSV (#4982) @knolleary
|
||||||
|
|
||||||
|
#### 4.0.5: Maintenance Release
|
||||||
|
|
||||||
|
Editor
|
||||||
|
|
||||||
|
- Refix link call node can call out of a subflow (#4908) @GogoVega
|
||||||
|
|
||||||
|
#### 4.0.4: Maintenance Release
|
||||||
|
|
||||||
|
Editor
|
||||||
|
|
||||||
|
- Fix `link call` node can call out of a subflow (#4892) @GogoVega
|
||||||
|
- Fix wrong unlock state when event is triggered after deployment (#4889) @GogoVega
|
||||||
|
- i18n(App) update with latest language file changes (#4903) @joebordes
|
||||||
|
- fix typo: depreciated (#4895) @dxdc
|
||||||
|
|
||||||
|
Runtime
|
||||||
|
|
||||||
|
- Update dev dependencies (#4893) @knolleary
|
||||||
|
|
||||||
|
Nodes
|
||||||
|
|
||||||
|
- MQTT: Allow msg.userProperties to have number values (#4900) @hardillb
|
||||||
|
|
||||||
|
#### 4.0.3: Maintenance Release
|
||||||
|
|
||||||
|
Editor
|
||||||
|
|
||||||
|
- Refresh page title after changing tab name (#4850) @kazuhitoyokoi
|
||||||
|
- Add Japanese translations for v4.0.2 (again) (#4853) @kazuhitoyokoi
|
||||||
|
- Stay in quick-add mode following context menu insert (#4883) @knolleary
|
||||||
|
- Do not include Junction type in quick-add for virtual links (#4879) @knolleary
|
||||||
|
- Multiplayer cursor tracking (#4845) @knolleary
|
||||||
|
- Hide add-flow options when disabled via editorTheme (#4869) @knolleary
|
||||||
|
- Fix env-var config select when multiple defined (#4872) @knolleary
|
||||||
|
- Fix subflow outbound-link filter (#4857) @GogoVega
|
||||||
|
- Add French translations for v4.0.2 (#4856) @GogoVega
|
||||||
|
- Fix moving link wires (#4851) @knolleary
|
||||||
|
- Adjust type search dialog position to prevent x-overflow (#4844) @Steve-Mcl
|
||||||
|
- fix: modulesInUse might be undefined (#4838) @lorenz-maurer
|
||||||
|
- Add Japanese translations for v4.0.2 (#4849) @kazuhitoyokoi
|
||||||
|
- Fix menu to enable/disable selection when it's a group (#4828) @GogoVega
|
||||||
|
|
||||||
|
Runtime
|
||||||
|
|
||||||
|
- Update dependencies (#4874) @knolleary
|
||||||
|
- GitHub: Add citation file to enable "Cite this repository" feature (#4861) @lobis
|
||||||
|
- Remove use of util.log (#4875) @knolleary
|
||||||
|
|
||||||
|
Nodes
|
||||||
|
|
||||||
|
- Fix invalid property error in range node example (#4855)
|
||||||
|
- Fix typo in flow example name (#4854) @kazuhitoyokoi
|
||||||
|
- Move SNI, ALPN and Verify Server cert out of check (#4882) @hardillb
|
||||||
|
- Set status of mqtt nodes to "disconnected" when deregistered from broker (#4878) @Steve-Mcl
|
||||||
|
- MQTT: Ensure will payload is a string (#4873) @knolleary
|
||||||
|
- Let batch node terminate "early" if msg.parts set to end of sequence (#4829) @dceejay
|
||||||
|
- Fix unintentional Capitalisation in Split node name (#4835) @dceejay
|
||||||
|
|
||||||
#### 4.0.2: Maintenance Release
|
#### 4.0.2: Maintenance Release
|
||||||
|
|
||||||
Editor
|
Editor
|
||||||
|
7
CITATION.cff
Normal file
7
CITATION.cff
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
cff-version: 1.2.0
|
||||||
|
message: "If you use this software, please cite it as below."
|
||||||
|
title: "Node-RED"
|
||||||
|
authors:
|
||||||
|
- family-names: "OpenJS Foundation"
|
||||||
|
- family-names: "Contributors"
|
||||||
|
url: "https://nodered.org"
|
16
nodemon.json
Normal file
16
nodemon.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"ignoreRoot": [
|
||||||
|
".git",
|
||||||
|
".nyc_output",
|
||||||
|
".sass-cache",
|
||||||
|
"bower-components",
|
||||||
|
"coverage"
|
||||||
|
],
|
||||||
|
"ignore": [
|
||||||
|
"/Gruntfile.js",
|
||||||
|
"/.git/*",
|
||||||
|
"*.backup",
|
||||||
|
"/public/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
42
package.json
42
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "node-red",
|
"name": "node-red",
|
||||||
"version": "4.0.2",
|
"version": "4.0.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",
|
||||||
@ -26,26 +26,26 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"acorn": "8.11.3",
|
"acorn": "8.12.1",
|
||||||
"acorn-walk": "8.3.2",
|
"acorn-walk": "8.3.4",
|
||||||
"ajv": "8.14.0",
|
"ajv": "8.17.1",
|
||||||
"async-mutex": "0.5.0",
|
"async-mutex": "0.5.0",
|
||||||
"basic-auth": "2.0.1",
|
"basic-auth": "2.0.1",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"body-parser": "1.20.2",
|
"body-parser": "1.20.3",
|
||||||
"cheerio": "1.0.0-rc.10",
|
"cheerio": "1.0.0-rc.10",
|
||||||
"clone": "2.1.2",
|
"clone": "2.1.2",
|
||||||
"content-type": "1.0.5",
|
"content-type": "1.0.5",
|
||||||
"cookie": "0.6.0",
|
"cookie": "0.7.2",
|
||||||
"cookie-parser": "1.4.6",
|
"cookie-parser": "1.4.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.19.2",
|
"express": "4.21.2",
|
||||||
"express-session": "1.18.0",
|
"express-session": "1.18.1",
|
||||||
"form-data": "4.0.0",
|
"form-data": "4.0.0",
|
||||||
"fs-extra": "11.2.0",
|
"fs-extra": "11.2.0",
|
||||||
"got": "12.6.0",
|
"got": "12.6.1",
|
||||||
"hash-sum": "2.0.0",
|
"hash-sum": "2.0.0",
|
||||||
"hpagent": "1.2.0",
|
"hpagent": "1.2.0",
|
||||||
"https-proxy-agent": "5.0.1",
|
"https-proxy-agent": "5.0.1",
|
||||||
@ -60,11 +60,11 @@
|
|||||||
"memorystore": "1.6.7",
|
"memorystore": "1.6.7",
|
||||||
"mime": "3.0.0",
|
"mime": "3.0.0",
|
||||||
"moment": "2.30.1",
|
"moment": "2.30.1",
|
||||||
"moment-timezone": "0.5.45",
|
"moment-timezone": "0.5.46",
|
||||||
"mqtt": "5.7.0",
|
"mqtt": "5.7.0",
|
||||||
"multer": "1.4.5-lts.1",
|
"multer": "1.4.5-lts.1",
|
||||||
"mustache": "4.2.0",
|
"mustache": "4.2.0",
|
||||||
"node-red-admin": "^4.0.0",
|
"node-red-admin": "^4.0.1",
|
||||||
"node-watch": "0.7.4",
|
"node-watch": "0.7.4",
|
||||||
"nopt": "5.0.0",
|
"nopt": "5.0.0",
|
||||||
"oauth2orize": "1.12.0",
|
"oauth2orize": "1.12.0",
|
||||||
@ -72,11 +72,11 @@
|
|||||||
"passport": "0.7.0",
|
"passport": "0.7.0",
|
||||||
"passport-http-bearer": "1.0.1",
|
"passport-http-bearer": "1.0.1",
|
||||||
"passport-oauth2-client-password": "0.1.2",
|
"passport-oauth2-client-password": "0.1.2",
|
||||||
"raw-body": "2.5.2",
|
"raw-body": "3.0.0",
|
||||||
"rfdc": "^1.3.1",
|
"rfdc": "^1.3.1",
|
||||||
"semver": "7.5.4",
|
"semver": "7.6.3",
|
||||||
"tar": "7.2.0",
|
"tar": "7.4.3",
|
||||||
"tough-cookie": "4.1.4",
|
"tough-cookie": "^5.0.0",
|
||||||
"uglify-js": "3.17.4",
|
"uglify-js": "3.17.4",
|
||||||
"uuid": "9.0.1",
|
"uuid": "9.0.1",
|
||||||
"ws": "7.5.10",
|
"ws": "7.5.10",
|
||||||
@ -86,10 +86,10 @@
|
|||||||
"@node-rs/bcrypt": "1.10.4"
|
"@node-rs/bcrypt": "1.10.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"dompurify": "2.4.1",
|
"dompurify": "2.5.7",
|
||||||
"grunt": "1.6.1",
|
"grunt": "1.6.1",
|
||||||
"grunt-chmod": "~1.1.1",
|
"grunt-chmod": "~1.1.1",
|
||||||
"grunt-cli": "~1.4.3",
|
"grunt-cli": "~1.5.0",
|
||||||
"grunt-concurrent": "3.0.0",
|
"grunt-concurrent": "3.0.0",
|
||||||
"grunt-contrib-clean": "2.0.1",
|
"grunt-contrib-clean": "2.0.1",
|
||||||
"grunt-contrib-compress": "2.0.0",
|
"grunt-contrib-compress": "2.0.0",
|
||||||
@ -100,7 +100,7 @@
|
|||||||
"grunt-contrib-watch": "1.1.0",
|
"grunt-contrib-watch": "1.1.0",
|
||||||
"grunt-jsdoc": "2.4.1",
|
"grunt-jsdoc": "2.4.1",
|
||||||
"grunt-jsdoc-to-markdown": "6.0.0",
|
"grunt-jsdoc-to-markdown": "6.0.0",
|
||||||
"grunt-jsonlint": "2.1.3",
|
"grunt-jsonlint": "3.0.0",
|
||||||
"grunt-mkdir": "~1.1.0",
|
"grunt-mkdir": "~1.1.0",
|
||||||
"grunt-npm-command": "~0.1.2",
|
"grunt-npm-command": "~0.1.2",
|
||||||
"grunt-sass": "~3.1.0",
|
"grunt-sass": "~3.1.0",
|
||||||
@ -110,11 +110,11 @@
|
|||||||
"jquery-i18next": "1.2.1",
|
"jquery-i18next": "1.2.1",
|
||||||
"jsdoc-nr-template": "github:node-red/jsdoc-nr-template",
|
"jsdoc-nr-template": "github:node-red/jsdoc-nr-template",
|
||||||
"marked": "4.3.0",
|
"marked": "4.3.0",
|
||||||
"mermaid": "^10.4.0",
|
"mermaid": "11.3.0",
|
||||||
"minami": "1.2.3",
|
"minami": "1.2.3",
|
||||||
"mocha": "9.2.2",
|
"mocha": "9.2.2",
|
||||||
"node-red-node-test-helper": "^0.3.3",
|
"node-red-node-test-helper": "^0.3.3",
|
||||||
"nodemon": "2.0.20",
|
"nodemon": "3.1.7",
|
||||||
"proxy": "^1.0.2",
|
"proxy": "^1.0.2",
|
||||||
"sass": "1.62.1",
|
"sass": "1.62.1",
|
||||||
"should": "13.2.3",
|
"should": "13.2.3",
|
||||||
|
@ -126,6 +126,14 @@ async function login(req,res) {
|
|||||||
if (themeContext.login && themeContext.login.image) {
|
if (themeContext.login && themeContext.login.image) {
|
||||||
response.image = themeContext.login.image;
|
response.image = themeContext.login.image;
|
||||||
}
|
}
|
||||||
|
if (themeContext.login?.message) {
|
||||||
|
response.loginMessage = themeContext.login?.message
|
||||||
|
}
|
||||||
|
if (themeContext.login?.button) {
|
||||||
|
response.prompts = [
|
||||||
|
{ type: "button", ...themeContext.login.button }
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
res.json(response);
|
res.json(response);
|
||||||
}
|
}
|
||||||
|
@ -185,13 +185,12 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (theme.deployButton) {
|
if (theme.deployButton) {
|
||||||
if (theme.deployButton.type == "simple") {
|
themeSettings.deployButton = {};
|
||||||
themeSettings.deployButton = {
|
|
||||||
type: "simple"
|
|
||||||
}
|
|
||||||
if (theme.deployButton.label) {
|
if (theme.deployButton.label) {
|
||||||
themeSettings.deployButton.label = theme.deployButton.label;
|
themeSettings.deployButton.label = theme.deployButton.label;
|
||||||
}
|
}
|
||||||
|
if (theme.deployButton.type == "simple") {
|
||||||
|
themeSettings.deployButton.type = theme.deployButton.type;
|
||||||
if (theme.deployButton.icon) {
|
if (theme.deployButton.icon) {
|
||||||
url = serveFile(themeApp,"/deploy/",theme.deployButton.icon);
|
url = serveFile(themeApp,"/deploy/",theme.deployButton.icon);
|
||||||
if (url) {
|
if (url) {
|
||||||
@ -206,13 +205,25 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (theme.login) {
|
if (theme.login) {
|
||||||
|
let themeContextLogin = {}
|
||||||
|
let hasLoginTheme = false
|
||||||
if (theme.login.image) {
|
if (theme.login.image) {
|
||||||
url = serveFile(themeApp,"/login/",theme.login.image);
|
url = serveFile(themeApp,"/login/",theme.login.image);
|
||||||
if (url) {
|
if (url) {
|
||||||
themeContext.login = {
|
themeContextLogin.image = url
|
||||||
image: url
|
hasLoginTheme = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (theme.login.message) {
|
||||||
|
themeContextLogin.message = theme.login.message
|
||||||
|
hasLoginTheme = true
|
||||||
|
}
|
||||||
|
if (theme.login.button) {
|
||||||
|
themeContextLogin.button = theme.login.button
|
||||||
|
hasLoginTheme = true
|
||||||
|
}
|
||||||
|
if (hasLoginTheme) {
|
||||||
|
themeContext.login = themeContextLogin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
themeApp.get("/", async function(req,res) {
|
themeApp.get("/", async function(req,res) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@node-red/editor-api",
|
"name": "@node-red/editor-api",
|
||||||
"version": "4.0.2",
|
"version": "4.0.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": "4.0.2",
|
"@node-red/util": "4.0.9",
|
||||||
"@node-red/editor-client": "4.0.2",
|
"@node-red/editor-client": "4.0.9",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"body-parser": "1.20.2",
|
"body-parser": "1.20.3",
|
||||||
"clone": "2.1.2",
|
"clone": "2.1.2",
|
||||||
"cors": "2.8.5",
|
"cors": "2.8.5",
|
||||||
"express-session": "1.18.0",
|
"express-session": "1.18.1",
|
||||||
"express": "4.19.2",
|
"express": "4.21.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",
|
||||||
|
@ -562,7 +562,9 @@
|
|||||||
"types": {
|
"types": {
|
||||||
"local": "Local",
|
"local": "Local",
|
||||||
"examples": "Examples"
|
"examples": "Examples"
|
||||||
}
|
},
|
||||||
|
"type": "Type",
|
||||||
|
"name": "Name"
|
||||||
},
|
},
|
||||||
"palette": {
|
"palette": {
|
||||||
"noInfo": "no information available",
|
"noInfo": "no information available",
|
||||||
|
@ -27,7 +27,8 @@
|
|||||||
"lock": "Bloquear",
|
"lock": "Bloquear",
|
||||||
"unlock": "Desbloquear",
|
"unlock": "Desbloquear",
|
||||||
"locked": "Bloqueado",
|
"locked": "Bloqueado",
|
||||||
"unlocked": "Desbloqueado"
|
"unlocked": "Desbloqueado",
|
||||||
|
"format": "Formato"
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"string": "texto",
|
"string": "texto",
|
||||||
@ -303,7 +304,8 @@
|
|||||||
"missingType": "La entrada no es un flujo válido - elemento __index__ falta la propiedad 'type'"
|
"missingType": "La entrada no es un flujo válido - elemento __index__ falta la propiedad 'type'"
|
||||||
},
|
},
|
||||||
"conflictNotification1": "Algunos de los nodos que estás importando ya existen en tu espacio de trabajo.",
|
"conflictNotification1": "Algunos de los nodos que estás importando ya existen en tu espacio de trabajo.",
|
||||||
"conflictNotification2": "Selecciona qué nodos importar y si reemplazar los nodos existentes o importar una copia de los mismos."
|
"conflictNotification2": "Selecciona qué nodos importar y si reemplazar los nodos existentes o importar una copia de los mismos.",
|
||||||
|
"alreadyExists": "Este nodo ya existe"
|
||||||
},
|
},
|
||||||
"copyMessagePath": "Ruta copiada",
|
"copyMessagePath": "Ruta copiada",
|
||||||
"copyMessageValue": "Valor copiado",
|
"copyMessageValue": "Valor copiado",
|
||||||
@ -371,8 +373,12 @@
|
|||||||
"deleted": "eliminado",
|
"deleted": "eliminado",
|
||||||
"flowDeleted": "flujo eliminado",
|
"flowDeleted": "flujo eliminado",
|
||||||
"flowAdded": "flujo añadido",
|
"flowAdded": "flujo añadido",
|
||||||
|
"moved": "movido",
|
||||||
"movedTo": "movido a __id__",
|
"movedTo": "movido a __id__",
|
||||||
"movedFrom": "movido desde __id__"
|
"movedFrom": "movido desde __id__",
|
||||||
|
"none": "ninguno",
|
||||||
|
"position": "posición",
|
||||||
|
"wires": "conectores"
|
||||||
},
|
},
|
||||||
"nodeCount": "__count__ nodo",
|
"nodeCount": "__count__ nodo",
|
||||||
"nodeCount_plural": "__count__ nodos",
|
"nodeCount_plural": "__count__ nodos",
|
||||||
@ -381,9 +387,14 @@
|
|||||||
"reviewChanges": "Revisar Cambios",
|
"reviewChanges": "Revisar Cambios",
|
||||||
"noBinaryFileShowed": "No se puede mostrar el contenido del archivo binario",
|
"noBinaryFileShowed": "No se puede mostrar el contenido del archivo binario",
|
||||||
"viewCommitDiff": "Ver cambios de commit",
|
"viewCommitDiff": "Ver cambios de commit",
|
||||||
|
"commit": "Commit",
|
||||||
"compareChanges": "Comparar Cambios",
|
"compareChanges": "Comparar Cambios",
|
||||||
"saveConflict": "Guardar resolución de conflictos",
|
"saveConflict": "Guardar resolución de conflictos",
|
||||||
"conflictHeader": "<span>__resolved__</span> de <span>__unresolved__</span> conflictos resueltos",
|
"conflictHeader": "<span>__resolved__</span> de <span>__unresolved__</span> conflictos resueltos",
|
||||||
|
"localChanges": "Cambios Locales",
|
||||||
|
"remoteChanges": "Cambios Remotos",
|
||||||
|
"useLocalChanges": "utilizar cambios locales",
|
||||||
|
"useRemoteChanges": "utilizar cambios remotos",
|
||||||
"commonVersionError": "La versión común no contiene JSON válido:",
|
"commonVersionError": "La versión común no contiene JSON válido:",
|
||||||
"oldVersionError": "La versión anterior no contiene JSON válido:",
|
"oldVersionError": "La versión anterior no contiene JSON válido:",
|
||||||
"newVersionError": "La versión nueva no contiene JSON válido:"
|
"newVersionError": "La versión nueva no contiene JSON válido:"
|
||||||
@ -551,7 +562,9 @@
|
|||||||
"types": {
|
"types": {
|
||||||
"local": "Local",
|
"local": "Local",
|
||||||
"examples": "Ejemplos"
|
"examples": "Ejemplos"
|
||||||
}
|
},
|
||||||
|
"type": "Tipo",
|
||||||
|
"name": "Nombre"
|
||||||
},
|
},
|
||||||
"palette": {
|
"palette": {
|
||||||
"noInfo": "no hay información disponible",
|
"noInfo": "no hay información disponible",
|
||||||
@ -613,6 +626,8 @@
|
|||||||
},
|
},
|
||||||
"nodeCount": "__label__ nodo",
|
"nodeCount": "__label__ nodo",
|
||||||
"nodeCount_plural": "__label__ nodos",
|
"nodeCount_plural": "__label__ nodos",
|
||||||
|
"pluginCount": "__count__ extensión",
|
||||||
|
"pluginCount_plural": "__count__ extensiones",
|
||||||
"moduleCount": "__count__ módulo disponible",
|
"moduleCount": "__count__ módulo disponible",
|
||||||
"moduleCount_plural": "__count__ módulos disponibles",
|
"moduleCount_plural": "__count__ módulos disponibles",
|
||||||
"inuse": "en uso",
|
"inuse": "en uso",
|
||||||
@ -640,6 +655,7 @@
|
|||||||
"errors": {
|
"errors": {
|
||||||
"catalogLoadFailed": "<p>La carga del catálogo de nodos ha fallado</p><p>Revise la consola del navegador para mas información</p>",
|
"catalogLoadFailed": "<p>La carga del catálogo de nodos ha fallado</p><p>Revise la consola del navegador para mas información</p>",
|
||||||
"installFailed": "<p>Fallo al instalar: __module__</p><p>__message__</p><p>Revise el log para mas información</p>",
|
"installFailed": "<p>Fallo al instalar: __module__</p><p>__message__</p><p>Revise el log para mas información</p>",
|
||||||
|
"installTimeout": "<p>La instalación continúa en segundo plano.</p><p>Los nodos aparecerán en la paleta cuando finalice. Consulta el registro para obtener más información.</p>",
|
||||||
"removeFailed": "<p>Fallo al eliminar: __module__</p><p>__message__</p><p>Revise el log para mas información</p>",
|
"removeFailed": "<p>Fallo al eliminar: __module__</p><p>__message__</p><p>Revise el log para mas información</p>",
|
||||||
"updateFailed": "<p>Fallo al actualizar: __module__</p><p>__message__</p><p>Revise el log para mas información</p>",
|
"updateFailed": "<p>Fallo al actualizar: __module__</p><p>__message__</p><p>Revise el log para mas información</p>",
|
||||||
"enableFailed": "<p>Fallo al activar: __module__</p><p>__message__</p><p>Revise el log para mas información</p>",
|
"enableFailed": "<p>Fallo al activar: __module__</p><p>__message__</p><p>Revise el log para mas información</p>",
|
||||||
@ -654,6 +670,9 @@
|
|||||||
"body":"<p>Eliminando '__module__'</p><p>La eliminación del nodo lo desinstalará de Node-RED. Es posible que el nodo siga utilizando recursos hasta que Node-RED sea reiniciado.</p>",
|
"body":"<p>Eliminando '__module__'</p><p>La eliminación del nodo lo desinstalará de Node-RED. Es posible que el nodo siga utilizando recursos hasta que Node-RED sea reiniciado.</p>",
|
||||||
"title": "Eliminar nodos"
|
"title": "Eliminar nodos"
|
||||||
},
|
},
|
||||||
|
"removePlugin": {
|
||||||
|
"body": "<p>Extensión __module__ eliminada. Vuelve a cargar el editor para borrar los elementos sobrantes.</p>"
|
||||||
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"body":"<p>Actualizando '__module__'</p><p>La actualización del nodo requerirá un reinicio manual de Node-RED para completarse. Debe ser reiniciado manualmente.</p>",
|
"body":"<p>Actualizando '__module__'</p><p>La actualización del nodo requerirá un reinicio manual de Node-RED para completarse. Debe ser reiniciado manualmente.</p>",
|
||||||
"title": "Actualizar nodos"
|
"title": "Actualizar nodos"
|
||||||
@ -665,7 +684,8 @@
|
|||||||
"review": "Abrir información del nodo",
|
"review": "Abrir información del nodo",
|
||||||
"install": "Instalar",
|
"install": "Instalar",
|
||||||
"remove": "Eliminar",
|
"remove": "Eliminar",
|
||||||
"update": "Actualizar"
|
"update": "Actualizar",
|
||||||
|
"understood": "Entendido"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -718,6 +738,7 @@
|
|||||||
"nodeHelp": "Ayuda de nodo",
|
"nodeHelp": "Ayuda de nodo",
|
||||||
"showHelp": "Mostrar ayuda",
|
"showHelp": "Mostrar ayuda",
|
||||||
"showInOutline": "Mostrar en controno",
|
"showInOutline": "Mostrar en controno",
|
||||||
|
"hideTopics": "Esconder temas",
|
||||||
"showTopics": "Mostrar temas",
|
"showTopics": "Mostrar temas",
|
||||||
"noHelp": "No hay ningun tema de ayuda seleccionado",
|
"noHelp": "No hay ningun tema de ayuda seleccionado",
|
||||||
"changeLog": "Registro de Cambios"
|
"changeLog": "Registro de Cambios"
|
||||||
@ -792,6 +813,7 @@
|
|||||||
"branches": "Ramas",
|
"branches": "Ramas",
|
||||||
"noBranches": "Sin ramas",
|
"noBranches": "Sin ramas",
|
||||||
"deleteConfirm": "¿Estás seguro de que quieres eliminar la rama local '__name__'? Esta acción no puede deshacerse.",
|
"deleteConfirm": "¿Estás seguro de que quieres eliminar la rama local '__name__'? Esta acción no puede deshacerse.",
|
||||||
|
"deleteBranch": "Eliminar rama",
|
||||||
"unmergedConfirm": "La rama local '__name__' tiene cambios no fusionados que se perderán. ¿Estás seguro de que quieres eliminarla?",
|
"unmergedConfirm": "La rama local '__name__' tiene cambios no fusionados que se perderán. ¿Estás seguro de que quieres eliminarla?",
|
||||||
"deleteUnmergedBranch": "Eliminar rama no fusionada",
|
"deleteUnmergedBranch": "Eliminar rama no fusionada",
|
||||||
"gitRemotes": "Git remotes",
|
"gitRemotes": "Git remotes",
|
||||||
@ -913,6 +935,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"typedInput": {
|
"typedInput": {
|
||||||
|
"selected": "__count__ seleccionado",
|
||||||
|
"selected_plural": "__count__ seleccionados",
|
||||||
"type": {
|
"type": {
|
||||||
"str": "texto",
|
"str": "texto",
|
||||||
"num": "número",
|
"num": "número",
|
||||||
@ -923,7 +947,14 @@
|
|||||||
"date": "marca tiempo",
|
"date": "marca tiempo",
|
||||||
"jsonata": "expresión",
|
"jsonata": "expresión",
|
||||||
"env": "variable de entorno",
|
"env": "variable de entorno",
|
||||||
"cred": "credencial"
|
"cred": "credencial",
|
||||||
|
"conf-types": "nodo configuración"
|
||||||
|
},
|
||||||
|
"date": {
|
||||||
|
"format": {
|
||||||
|
"timestamp": "milisegundos desde epoch",
|
||||||
|
"object": "Objeto de fecha de JavaScript"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"editableList": {
|
"editableList": {
|
||||||
@ -1205,6 +1236,18 @@
|
|||||||
"diagnostics": {
|
"diagnostics": {
|
||||||
"title": "Información Sistema"
|
"title": "Información Sistema"
|
||||||
},
|
},
|
||||||
|
"languages": {
|
||||||
|
"de": "Deutsch",
|
||||||
|
"en-US": "English",
|
||||||
|
"es-ES": "Español (España)",
|
||||||
|
"fr": "Français",
|
||||||
|
"ja": "日本語",
|
||||||
|
"ko": "Korean",
|
||||||
|
"pt-BR": "Português (Brasil)",
|
||||||
|
"ru": "Русский",
|
||||||
|
"zh-CN": "简体中文",
|
||||||
|
"zh-TW": "繁體中文"
|
||||||
|
},
|
||||||
"validator": {
|
"validator": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"invalid-json": "Datos JSON inválidos: __error__",
|
"invalid-json": "Datos JSON inválidos: __error__",
|
||||||
|
@ -562,7 +562,9 @@
|
|||||||
"types": {
|
"types": {
|
||||||
"local": "ローカル",
|
"local": "ローカル",
|
||||||
"examples": "サンプル"
|
"examples": "サンプル"
|
||||||
}
|
},
|
||||||
|
"type": "型",
|
||||||
|
"name": "名前"
|
||||||
},
|
},
|
||||||
"palette": {
|
"palette": {
|
||||||
"noInfo": "情報がありません",
|
"noInfo": "情報がありません",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@node-red/editor-client",
|
"name": "@node-red/editor-client",
|
||||||
"version": "4.0.2",
|
"version": "4.0.9",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -453,10 +453,61 @@ RED.history = (function() {
|
|||||||
RED.events.emit("nodes:change",newConfigNode);
|
RED.events.emit("nodes:change",newConfigNode);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else if (i === "env" && ev.node.type.indexOf("subflow:") === 0) {
|
||||||
|
// Subflow can have config node in node.env
|
||||||
|
let nodeList = ev.node.env || [];
|
||||||
|
nodeList = nodeList.reduce((list, prop) => {
|
||||||
|
if (prop.type === "conf-type" && prop.value) {
|
||||||
|
list.push(prop.value);
|
||||||
}
|
}
|
||||||
|
return list;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
nodeList.forEach(function(id) {
|
||||||
|
const configNode = RED.nodes.node(id);
|
||||||
|
if (configNode) {
|
||||||
|
if (configNode.users.indexOf(ev.node) !== -1) {
|
||||||
|
configNode.users.splice(configNode.users.indexOf(ev.node), 1);
|
||||||
|
RED.events.emit("nodes:change", configNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
nodeList = ev.changes.env || [];
|
||||||
|
nodeList = nodeList.reduce((list, prop) => {
|
||||||
|
if (prop.type === "conf-type" && prop.value) {
|
||||||
|
list.push(prop.value);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
nodeList.forEach(function(id) {
|
||||||
|
const configNode = RED.nodes.node(id);
|
||||||
|
if (configNode) {
|
||||||
|
if (configNode.users.indexOf(ev.node) === -1) {
|
||||||
|
configNode.users.push(ev.node);
|
||||||
|
RED.events.emit("nodes:change", configNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (i === "credentials" && ev.changes[i]) {
|
||||||
|
// Reset - Only want to keep the changes
|
||||||
|
inverseEv.changes[i] = {};
|
||||||
|
for (const [key, value] of Object.entries(ev.changes[i])) {
|
||||||
|
// Edge case: node.credentials is cleared after a deploy, so we can't
|
||||||
|
// capture values for the inverse event when undoing past a deploy
|
||||||
|
if (ev.node.credentials) {
|
||||||
|
inverseEv.changes[i][key] = ev.node.credentials[key];
|
||||||
|
}
|
||||||
|
ev.node.credentials[key] = value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
ev.node[i] = ev.changes[i];
|
ev.node[i] = ev.changes[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ev.node.dirty = true;
|
ev.node.dirty = true;
|
||||||
ev.node.changed = ev.changed;
|
ev.node.changed = ev.changed;
|
||||||
|
|
||||||
@ -536,6 +587,24 @@ RED.history = (function() {
|
|||||||
RED.editor.updateNodeProperties(ev.node,outputMap);
|
RED.editor.updateNodeProperties(ev.node,outputMap);
|
||||||
RED.editor.validateNode(ev.node);
|
RED.editor.validateNode(ev.node);
|
||||||
}
|
}
|
||||||
|
// If it's a Config Node, validate user nodes too.
|
||||||
|
// NOTE: The Config Node must be validated before validating users.
|
||||||
|
if (ev.node.users) {
|
||||||
|
const validatedNodes = new Set();
|
||||||
|
const userStack = ev.node.users.slice();
|
||||||
|
|
||||||
|
validatedNodes.add(ev.node.id);
|
||||||
|
while (userStack.length) {
|
||||||
|
const node = userStack.pop();
|
||||||
|
if (!validatedNodes.has(node.id)) {
|
||||||
|
validatedNodes.add(node.id);
|
||||||
|
if (node.users) {
|
||||||
|
userStack.push(...node.users);
|
||||||
|
}
|
||||||
|
RED.editor.validateNode(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (ev.links) {
|
if (ev.links) {
|
||||||
inverseEv.createdLinks = [];
|
inverseEv.createdLinks = [];
|
||||||
for (i=0;i<ev.links.length;i++) {
|
for (i=0;i<ev.links.length;i++) {
|
||||||
|
@ -398,14 +398,13 @@ RED.multiplayer = (function () {
|
|||||||
anonIconBody.setAttribute("d",`M ${radius/2} ${radius/2 + 5} h -2.5 c -2 1 -2 -5 0.5 -4.5 c 2 1 2 1 4 0 c 2.5 -0.5 2.5 5.5 0 4.5 z`);
|
anonIconBody.setAttribute("d",`M ${radius/2} ${radius/2 + 5} h -2.5 c -2 1 -2 -5 0.5 -4.5 c 2 1 2 1 4 0 c 2.5 -0.5 2.5 5.5 0 4.5 z`);
|
||||||
group.appendChild(anonIconBody)
|
group.appendChild(anonIconBody)
|
||||||
} else {
|
} else {
|
||||||
const labelText = user.username ? user.username.substring(0,2) : user
|
|
||||||
const label = document.createElementNS("http://www.w3.org/2000/svg","text");
|
const label = document.createElementNS("http://www.w3.org/2000/svg","text");
|
||||||
if (user.username) {
|
if (user.username || user.email) {
|
||||||
label.setAttribute("class","red-ui-multiplayer-annotation-label");
|
label.setAttribute("class","red-ui-multiplayer-annotation-label");
|
||||||
label.textContent = user.username.substring(0,2)
|
label.textContent = (user.username || user.email).substring(0,2)
|
||||||
} else {
|
} else {
|
||||||
label.setAttribute("class","red-ui-multiplayer-annotation-label red-ui-multiplayer-user-count")
|
label.setAttribute("class","red-ui-multiplayer-annotation-label red-ui-multiplayer-user-count")
|
||||||
label.textContent = user
|
label.textContent = 'nr'
|
||||||
}
|
}
|
||||||
label.setAttribute("text-anchor", "middle")
|
label.setAttribute("text-anchor", "middle")
|
||||||
label.setAttribute("x",radius/2);
|
label.setAttribute("x",radius/2);
|
||||||
|
@ -73,7 +73,13 @@ RED.nodes = (function() {
|
|||||||
|
|
||||||
var exports = {
|
var exports = {
|
||||||
setModulePendingUpdated: function(module,version) {
|
setModulePendingUpdated: function(module,version) {
|
||||||
|
if (!!RED.plugins.getModule(module)) {
|
||||||
|
// The module updated is a plugin
|
||||||
|
RED.plugins.getModule(module).pending_version = version;
|
||||||
|
} else {
|
||||||
moduleList[module].pending_version = version;
|
moduleList[module].pending_version = version;
|
||||||
|
}
|
||||||
|
|
||||||
RED.events.emit("registry:module-updated",{module:module,version:version});
|
RED.events.emit("registry:module-updated",{module:module,version:version});
|
||||||
},
|
},
|
||||||
getModule: function(module) {
|
getModule: function(module) {
|
||||||
@ -701,12 +707,15 @@ RED.nodes = (function() {
|
|||||||
}
|
}
|
||||||
n["_"] = RED._;
|
n["_"] = RED._;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Both node and config node can use a config node
|
||||||
|
updateConfigNodeUsers(newNode, { action: "add" });
|
||||||
|
|
||||||
if (n._def.category == "config") {
|
if (n._def.category == "config") {
|
||||||
configNodes[n.id] = n;
|
configNodes[n.id] = newNode;
|
||||||
} else {
|
} else {
|
||||||
if (n.wires && (n.wires.length > n.outputs)) { n.outputs = n.wires.length; }
|
if (n.wires && (n.wires.length > n.outputs)) { n.outputs = n.wires.length; }
|
||||||
n.dirty = true;
|
n.dirty = true;
|
||||||
updateConfigNodeUsers(n);
|
|
||||||
if (n._def.category == "subflows" && typeof n.i === "undefined") {
|
if (n._def.category == "subflows" && typeof n.i === "undefined") {
|
||||||
var nextId = 0;
|
var nextId = 0;
|
||||||
RED.nodes.eachNode(function(node) {
|
RED.nodes.eachNode(function(node) {
|
||||||
@ -768,9 +777,11 @@ RED.nodes = (function() {
|
|||||||
var removedLinks = [];
|
var removedLinks = [];
|
||||||
var removedNodes = [];
|
var removedNodes = [];
|
||||||
var node;
|
var node;
|
||||||
|
|
||||||
if (id in configNodes) {
|
if (id in configNodes) {
|
||||||
node = configNodes[id];
|
node = configNodes[id];
|
||||||
delete configNodes[id];
|
delete configNodes[id];
|
||||||
|
updateConfigNodeUsers(node, { action: "remove" });
|
||||||
RED.events.emit('nodes:remove',node);
|
RED.events.emit('nodes:remove',node);
|
||||||
RED.workspaces.refresh();
|
RED.workspaces.refresh();
|
||||||
} else if (allNodes.hasNode(id)) {
|
} else if (allNodes.hasNode(id)) {
|
||||||
@ -779,6 +790,9 @@ RED.nodes = (function() {
|
|||||||
delete nodeLinks[id];
|
delete nodeLinks[id];
|
||||||
removedLinks = links.filter(function(l) { return (l.source === node) || (l.target === node); });
|
removedLinks = links.filter(function(l) { return (l.source === node) || (l.target === node); });
|
||||||
removedLinks.forEach(removeLink);
|
removedLinks.forEach(removeLink);
|
||||||
|
updateConfigNodeUsers(node, { action: "remove" });
|
||||||
|
|
||||||
|
// TODO: Legacy code for exclusive config node
|
||||||
var updatedConfigNode = false;
|
var updatedConfigNode = false;
|
||||||
for (var d in node._def.defaults) {
|
for (var d in node._def.defaults) {
|
||||||
if (node._def.defaults.hasOwnProperty(d)) {
|
if (node._def.defaults.hasOwnProperty(d)) {
|
||||||
@ -792,10 +806,6 @@ RED.nodes = (function() {
|
|||||||
if (configNode._def.exclusive) {
|
if (configNode._def.exclusive) {
|
||||||
removeNode(node[d]);
|
removeNode(node[d]);
|
||||||
removedNodes.push(configNode);
|
removedNodes.push(configNode);
|
||||||
} else {
|
|
||||||
var users = configNode.users;
|
|
||||||
users.splice(users.indexOf(node),1);
|
|
||||||
RED.events.emit('nodes:change',configNode)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1032,23 +1042,34 @@ RED.nodes = (function() {
|
|||||||
return {nodes:removedNodes,links:removedLinks, groups: removedGroups, junctions: removedJunctions};
|
return {nodes:removedNodes,links:removedLinks, groups: removedGroups, junctions: removedJunctions};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a Subflow to the Workspace
|
||||||
|
*
|
||||||
|
* @param {object} sf The Subflow to add.
|
||||||
|
* @param {boolean|undefined} createNewIds Whether to update the name.
|
||||||
|
*/
|
||||||
function addSubflow(sf, createNewIds) {
|
function addSubflow(sf, createNewIds) {
|
||||||
if (createNewIds) {
|
if (createNewIds) {
|
||||||
var subflowNames = Object.keys(subflows).map(function(sfid) {
|
// Update the Subflow name to highlight that this is a copy
|
||||||
return subflows[sfid].name;
|
const subflowNames = Object.keys(subflows).map(function (sfid) {
|
||||||
});
|
return subflows[sfid].name || "";
|
||||||
|
})
|
||||||
|
subflowNames.sort()
|
||||||
|
|
||||||
subflowNames.sort();
|
let copyNumber = 1;
|
||||||
var copyNumber = 1;
|
let subflowName = sf.name;
|
||||||
var subflowName = sf.name;
|
|
||||||
subflowNames.forEach(function(name) {
|
subflowNames.forEach(function(name) {
|
||||||
if (subflowName == name) {
|
if (subflowName == name) {
|
||||||
copyNumber++;
|
|
||||||
subflowName = sf.name + " (" + copyNumber + ")";
|
subflowName = sf.name + " (" + copyNumber + ")";
|
||||||
|
copyNumber++;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
sf.name = subflowName;
|
sf.name = subflowName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sf.instances = [];
|
||||||
|
|
||||||
subflows[sf.id] = sf;
|
subflows[sf.id] = sf;
|
||||||
allNodes.addTab(sf.id);
|
allNodes.addTab(sf.id);
|
||||||
linkTabMap[sf.id] = [];
|
linkTabMap[sf.id] = [];
|
||||||
@ -1101,7 +1122,7 @@ RED.nodes = (function() {
|
|||||||
module: "node-red"
|
module: "node-red"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
sf.instances = [];
|
|
||||||
sf._def = RED.nodes.getType("subflow:"+sf.id);
|
sf._def = RED.nodes.getType("subflow:"+sf.id);
|
||||||
RED.events.emit("subflows:add",sf);
|
RED.events.emit("subflows:add",sf);
|
||||||
}
|
}
|
||||||
@ -1743,7 +1764,8 @@ RED.nodes = (function() {
|
|||||||
// Remove the old subflow definition - but leave the instances in place
|
// Remove the old subflow definition - but leave the instances in place
|
||||||
var removalResult = RED.subflow.removeSubflow(n.id, true);
|
var removalResult = RED.subflow.removeSubflow(n.id, true);
|
||||||
// Create the list of nodes for the new subflow def
|
// Create the list of nodes for the new subflow def
|
||||||
var subflowNodes = [n].concat(zMap[n.id]);
|
// Need to sort the list in order to remove missing nodes
|
||||||
|
var subflowNodes = [n].concat(zMap[n.id]).filter((s) => !!s);
|
||||||
// Import the new subflow - no clashes should occur as we've removed
|
// Import the new subflow - no clashes should occur as we've removed
|
||||||
// the old version
|
// the old version
|
||||||
var result = importNodes(subflowNodes);
|
var result = importNodes(subflowNodes);
|
||||||
@ -1780,9 +1802,20 @@ RED.nodes = (function() {
|
|||||||
// Replace config nodes
|
// Replace config nodes
|
||||||
//
|
//
|
||||||
configNodeIds.forEach(function(id) {
|
configNodeIds.forEach(function(id) {
|
||||||
removedNodes = removedNodes.concat(convertNode(getNode(id)));
|
const configNode = getNode(id);
|
||||||
|
const currentUserCount = configNode.users;
|
||||||
|
|
||||||
|
// Add a snapshot of the Config Node
|
||||||
|
removedNodes = removedNodes.concat(convertNode(configNode));
|
||||||
|
|
||||||
|
// Remove the Config Node instance
|
||||||
removeNode(id);
|
removeNode(id);
|
||||||
importNodes([newConfigNodes[id]])
|
|
||||||
|
// Import the new one
|
||||||
|
importNodes([newConfigNodes[id]]);
|
||||||
|
|
||||||
|
// Re-attributes the user count
|
||||||
|
getNode(id).users = currentUserCount;
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -2023,6 +2056,8 @@ RED.nodes = (function() {
|
|||||||
if (matchingSubflow) {
|
if (matchingSubflow) {
|
||||||
subflow_denylist[n.id] = matchingSubflow;
|
subflow_denylist[n.id] = matchingSubflow;
|
||||||
} else {
|
} else {
|
||||||
|
const oldId = n.id;
|
||||||
|
|
||||||
subflow_map[n.id] = n;
|
subflow_map[n.id] = n;
|
||||||
if (createNewIds || options.importMap[n.id] === "copy") {
|
if (createNewIds || options.importMap[n.id] === "copy") {
|
||||||
nid = getID();
|
nid = getID();
|
||||||
@ -2050,7 +2085,7 @@ RED.nodes = (function() {
|
|||||||
n.status.id = getID();
|
n.status.id = getID();
|
||||||
}
|
}
|
||||||
new_subflows.push(n);
|
new_subflows.push(n);
|
||||||
addSubflow(n,createNewIds || options.importMap[n.id] === "copy");
|
addSubflow(n,createNewIds || options.importMap[oldId] === "copy");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2064,6 +2099,8 @@ RED.nodes = (function() {
|
|||||||
activeWorkspace = RED.workspaces.active();
|
activeWorkspace = RED.workspaces.active();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pendingConfigNodes = []
|
||||||
|
const pendingConfigNodeIds = new Set()
|
||||||
// Find all config nodes and add them
|
// Find all config nodes and add them
|
||||||
for (i=0;i<newNodes.length;i++) {
|
for (i=0;i<newNodes.length;i++) {
|
||||||
n = newNodes[i];
|
n = newNodes[i];
|
||||||
@ -2123,7 +2160,8 @@ RED.nodes = (function() {
|
|||||||
type:n.type,
|
type:n.type,
|
||||||
info: n.info,
|
info: n.info,
|
||||||
users:[],
|
users:[],
|
||||||
_config:{}
|
_config:{},
|
||||||
|
_configNodeReferences: new Set()
|
||||||
};
|
};
|
||||||
if (!n.z) {
|
if (!n.z) {
|
||||||
delete configNode.z;
|
delete configNode.z;
|
||||||
@ -2138,6 +2176,9 @@ RED.nodes = (function() {
|
|||||||
if (def.defaults.hasOwnProperty(d)) {
|
if (def.defaults.hasOwnProperty(d)) {
|
||||||
configNode[d] = n[d];
|
configNode[d] = n[d];
|
||||||
configNode._config[d] = JSON.stringify(n[d]);
|
configNode._config[d] = JSON.stringify(n[d]);
|
||||||
|
if (def.defaults[d].type) {
|
||||||
|
configNode._configNodeReferences.add(n[d])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (def.hasOwnProperty('credentials') && n.hasOwnProperty('credentials')) {
|
if (def.hasOwnProperty('credentials') && n.hasOwnProperty('credentials')) {
|
||||||
@ -2154,11 +2195,55 @@ RED.nodes = (function() {
|
|||||||
configNode.id = getID();
|
configNode.id = getID();
|
||||||
}
|
}
|
||||||
node_map[n.id] = configNode;
|
node_map[n.id] = configNode;
|
||||||
new_nodes.push(configNode);
|
pendingConfigNodes.push(configNode);
|
||||||
|
pendingConfigNodeIds.add(configNode.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We need to sort new_nodes (which only contains config nodes at this point)
|
||||||
|
// to ensure they get added in the right order. If NodeA depends on NodeB, then
|
||||||
|
// NodeB must be added first.
|
||||||
|
|
||||||
|
// Limit us to 5 full iterations of the list - this should be more than
|
||||||
|
// enough to process the list as config->config node relationships are
|
||||||
|
// not very common
|
||||||
|
let iterationLimit = pendingConfigNodes.length * 5
|
||||||
|
const handledConfigNodes = new Set()
|
||||||
|
while (pendingConfigNodes.length > 0 && iterationLimit > 0) {
|
||||||
|
const node = pendingConfigNodes.shift()
|
||||||
|
let hasPending = false
|
||||||
|
// Loop through the nodes referenced by this node to see if anything
|
||||||
|
// is pending
|
||||||
|
node._configNodeReferences.forEach(id => {
|
||||||
|
if (pendingConfigNodeIds.has(id) && !handledConfigNodes.has(id)) {
|
||||||
|
// This reference is for a node we know is in this import, but
|
||||||
|
// it isn't added yet - flag as pending
|
||||||
|
hasPending = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (!hasPending) {
|
||||||
|
// This node has no pending config node references - safe to add
|
||||||
|
delete node._configNodeReferences
|
||||||
|
new_nodes.push(node)
|
||||||
|
handledConfigNodes.add(node.id)
|
||||||
|
} else {
|
||||||
|
// This node has pending config node references
|
||||||
|
// Put to the back of the queue
|
||||||
|
pendingConfigNodes.push(node)
|
||||||
|
}
|
||||||
|
iterationLimit--
|
||||||
|
}
|
||||||
|
if (pendingConfigNodes.length > 0) {
|
||||||
|
// We exceeded the iteration count. Could be due to reference loops
|
||||||
|
// between the config nodes. At this point, just add the remaining
|
||||||
|
// nodes as-is
|
||||||
|
pendingConfigNodes.forEach(node => {
|
||||||
|
delete node._configNodeReferences
|
||||||
|
new_nodes.push(node)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Find regular flow nodes and subflow instances
|
// Find regular flow nodes and subflow instances
|
||||||
for (i=0;i<newNodes.length;i++) {
|
for (i=0;i<newNodes.length;i++) {
|
||||||
n = newNodes[i];
|
n = newNodes[i];
|
||||||
@ -2170,7 +2255,7 @@ RED.nodes = (function() {
|
|||||||
x:parseFloat(n.x || 0),
|
x:parseFloat(n.x || 0),
|
||||||
y:parseFloat(n.y || 0),
|
y:parseFloat(n.y || 0),
|
||||||
z:n.z,
|
z:n.z,
|
||||||
type:0,
|
type: n.type,
|
||||||
info: n.info,
|
info: n.info,
|
||||||
changed:false,
|
changed:false,
|
||||||
_config:{}
|
_config:{}
|
||||||
@ -2231,7 +2316,6 @@ RED.nodes = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
node.type = n.type;
|
|
||||||
node._def = def;
|
node._def = def;
|
||||||
if (node.type === "group") {
|
if (node.type === "group") {
|
||||||
node._def = RED.group.def;
|
node._def = RED.group.def;
|
||||||
@ -2261,6 +2345,15 @@ RED.nodes = (function() {
|
|||||||
outputs: n.outputs|| (n.wires && n.wires.length) || 0,
|
outputs: n.outputs|| (n.wires && n.wires.length) || 0,
|
||||||
set: registry.getNodeSet("node-red/unknown")
|
set: registry.getNodeSet("node-red/unknown")
|
||||||
}
|
}
|
||||||
|
var orig = {};
|
||||||
|
for (var p in n) {
|
||||||
|
if (n.hasOwnProperty(p) && p!="x" && p!="y" && p!="z" && p!="id" && p!="wires") {
|
||||||
|
orig[p] = n[p];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node._orig = orig;
|
||||||
|
node.name = n.type;
|
||||||
|
node.type = "unknown";
|
||||||
} else {
|
} else {
|
||||||
if (subflow_denylist[parentId] || createNewIds || options.importMap[n.id] === "copy") {
|
if (subflow_denylist[parentId] || createNewIds || options.importMap[n.id] === "copy") {
|
||||||
parentId = subflow.id;
|
parentId = subflow.id;
|
||||||
@ -2321,29 +2414,31 @@ RED.nodes = (function() {
|
|||||||
node.type = "unknown";
|
node.type = "unknown";
|
||||||
}
|
}
|
||||||
if (node._def.category != "config") {
|
if (node._def.category != "config") {
|
||||||
if (n.hasOwnProperty('inputs')) {
|
if (n.hasOwnProperty('inputs') && node._def.defaults.hasOwnProperty("inputs")) {
|
||||||
node.inputs = n.inputs;
|
node.inputs = parseInt(n.inputs, 10);
|
||||||
node._config.inputs = JSON.stringify(n.inputs);
|
node._config.inputs = JSON.stringify(n.inputs);
|
||||||
} else {
|
} else {
|
||||||
node.inputs = node._def.inputs;
|
node.inputs = node._def.inputs;
|
||||||
}
|
}
|
||||||
if (n.hasOwnProperty('outputs')) {
|
if (n.hasOwnProperty('outputs') && node._def.defaults.hasOwnProperty("outputs")) {
|
||||||
node.outputs = n.outputs;
|
node.outputs = parseInt(n.outputs, 10);
|
||||||
node._config.outputs = JSON.stringify(n.outputs);
|
node._config.outputs = JSON.stringify(n.outputs);
|
||||||
} else {
|
} else {
|
||||||
node.outputs = node._def.outputs;
|
node.outputs = node._def.outputs;
|
||||||
}
|
}
|
||||||
if (node.hasOwnProperty('wires') && node.wires.length > node.outputs) {
|
|
||||||
if (!node._def.defaults.hasOwnProperty("outputs") || !isNaN(parseInt(n.outputs))) {
|
// The node declares outputs in its defaults, but has not got a valid value
|
||||||
|
// Defer to the length of the wires array
|
||||||
|
if (node.hasOwnProperty('wires')) {
|
||||||
|
if (isNaN(node.outputs)) {
|
||||||
|
node.outputs = node.wires.length;
|
||||||
|
} else if (node.wires.length > node.outputs) {
|
||||||
// If 'wires' is longer than outputs, clip wires
|
// If 'wires' is longer than outputs, clip wires
|
||||||
console.log("Warning: node.wires longer than node.outputs - trimming wires:", node.id, " wires:", node.wires.length, " outputs:", node.outputs);
|
console.log("Warning: node.wires longer than node.outputs - trimming wires:", node.id, " wires:", node.wires.length, " outputs:", node.outputs);
|
||||||
node.wires = node.wires.slice(0, node.outputs);
|
node.wires = node.wires.slice(0, node.outputs);
|
||||||
} else {
|
|
||||||
// The node declares outputs in its defaults, but has not got a valid value
|
|
||||||
// Defer to the length of the wires array
|
|
||||||
node.outputs = node.wires.length;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (d in node._def.defaults) {
|
for (d in node._def.defaults) {
|
||||||
if (node._def.defaults.hasOwnProperty(d) && d !== 'inputs' && d !== 'outputs') {
|
if (node._def.defaults.hasOwnProperty(d) && d !== 'inputs' && d !== 'outputs') {
|
||||||
node[d] = n[d];
|
node[d] = n[d];
|
||||||
@ -2408,7 +2503,7 @@ RED.nodes = (function() {
|
|||||||
}
|
}
|
||||||
// If importing a link node, ensure both ends of each link are either:
|
// If importing a link node, ensure both ends of each link are either:
|
||||||
// - not in a subflow
|
// - not in a subflow
|
||||||
// - both in the same subflow
|
// - both in the same subflow (not for link call node)
|
||||||
if (/^link /.test(n.type) && n.links) {
|
if (/^link /.test(n.type) && n.links) {
|
||||||
n.links = n.links.filter(function(id) {
|
n.links = n.links.filter(function(id) {
|
||||||
const otherNode = node_map[id] || RED.nodes.node(id);
|
const otherNode = node_map[id] || RED.nodes.node(id);
|
||||||
@ -2419,6 +2514,10 @@ RED.nodes = (function() {
|
|||||||
if (otherNode.z === n.z) {
|
if (otherNode.z === n.z) {
|
||||||
// Both ends in the same flow/subflow
|
// Both ends in the same flow/subflow
|
||||||
return true
|
return true
|
||||||
|
} else if (n.type === "link call" && !getSubflow(otherNode.z)) {
|
||||||
|
// Link call node can call out of a subflow as long as otherNode is
|
||||||
|
// not in a subflow
|
||||||
|
return true
|
||||||
} else if (!!getSubflow(n.z) || !!getSubflow(otherNode.z)) {
|
} else if (!!getSubflow(n.z) || !!getSubflow(otherNode.z)) {
|
||||||
// One end is in a subflow - remove the link
|
// One end is in a subflow - remove the link
|
||||||
return false
|
return false
|
||||||
@ -2436,11 +2535,6 @@ RED.nodes = (function() {
|
|||||||
nodeList = nodeList.map(function(id) {
|
nodeList = nodeList.map(function(id) {
|
||||||
var node = node_map[id];
|
var node = node_map[id];
|
||||||
if (node) {
|
if (node) {
|
||||||
if (node._def.category === 'config') {
|
|
||||||
if (node.users.indexOf(n) === -1) {
|
|
||||||
node.users.push(n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return node.id;
|
return node.id;
|
||||||
}
|
}
|
||||||
return id;
|
return id;
|
||||||
@ -2454,9 +2548,11 @@ RED.nodes = (function() {
|
|||||||
n = new_subflows[i];
|
n = new_subflows[i];
|
||||||
n.in.forEach(function(input) {
|
n.in.forEach(function(input) {
|
||||||
input.wires.forEach(function(wire) {
|
input.wires.forEach(function(wire) {
|
||||||
|
if (node_map.hasOwnProperty(wire.id)) {
|
||||||
var link = {source:input, sourcePort:0, target:node_map[wire.id]};
|
var link = {source:input, sourcePort:0, target:node_map[wire.id]};
|
||||||
addLink(link);
|
addLink(link);
|
||||||
new_links.push(link);
|
new_links.push(link);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
delete input.wires;
|
delete input.wires;
|
||||||
});
|
});
|
||||||
@ -2465,11 +2561,13 @@ RED.nodes = (function() {
|
|||||||
var link;
|
var link;
|
||||||
if (subflow_map[wire.id] && subflow_map[wire.id].id == n.id) {
|
if (subflow_map[wire.id] && subflow_map[wire.id].id == n.id) {
|
||||||
link = {source:n.in[wire.port], sourcePort:wire.port,target:output};
|
link = {source:n.in[wire.port], sourcePort:wire.port,target:output};
|
||||||
} else {
|
} else if (node_map.hasOwnProperty(wire.id) || subflow_map.hasOwnProperty(wire.id)) {
|
||||||
link = {source:node_map[wire.id]||subflow_map[wire.id], sourcePort:wire.port,target:output};
|
link = {source:node_map[wire.id]||subflow_map[wire.id], sourcePort:wire.port,target:output};
|
||||||
}
|
}
|
||||||
|
if (link) {
|
||||||
addLink(link);
|
addLink(link);
|
||||||
new_links.push(link);
|
new_links.push(link);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
delete output.wires;
|
delete output.wires;
|
||||||
});
|
});
|
||||||
@ -2478,11 +2576,13 @@ RED.nodes = (function() {
|
|||||||
var link;
|
var link;
|
||||||
if (subflow_map[wire.id] && subflow_map[wire.id].id == n.id) {
|
if (subflow_map[wire.id] && subflow_map[wire.id].id == n.id) {
|
||||||
link = {source:n.in[wire.port], sourcePort:wire.port,target:n.status};
|
link = {source:n.in[wire.port], sourcePort:wire.port,target:n.status};
|
||||||
} else {
|
} else if (node_map.hasOwnProperty(wire.id) || subflow_map.hasOwnProperty(wire.id)) {
|
||||||
link = {source:node_map[wire.id]||subflow_map[wire.id], sourcePort:wire.port,target:n.status};
|
link = {source:node_map[wire.id]||subflow_map[wire.id], sourcePort:wire.port,target:n.status};
|
||||||
}
|
}
|
||||||
|
if (link) {
|
||||||
addLink(link);
|
addLink(link);
|
||||||
new_links.push(link);
|
new_links.push(link);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
delete n.status.wires;
|
delete n.status.wires;
|
||||||
}
|
}
|
||||||
@ -2661,19 +2761,43 @@ RED.nodes = (function() {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update any config nodes referenced by the provided node to ensure their 'users' list is correct
|
/**
|
||||||
function updateConfigNodeUsers(n) {
|
* Update any config nodes referenced by the provided node to ensure
|
||||||
for (var d in n._def.defaults) {
|
* their 'users' list is correct.
|
||||||
if (n._def.defaults.hasOwnProperty(d)) {
|
*
|
||||||
var property = n._def.defaults[d];
|
* @param {object} node The node in which to check if it contains references
|
||||||
|
* @param {object} options Options to apply.
|
||||||
|
* @param {"add" | "remove"} [options.action] Add or remove the node from
|
||||||
|
* the Config Node users list. Default `add`.
|
||||||
|
* @param {boolean} [options.emitEvent] Emit the `nodes:changes` event.
|
||||||
|
* Default true.
|
||||||
|
*/
|
||||||
|
function updateConfigNodeUsers(node, options) {
|
||||||
|
const defaultOptions = { action: "add", emitEvent: true };
|
||||||
|
options = Object.assign({}, defaultOptions, options);
|
||||||
|
|
||||||
|
for (var d in node._def.defaults) {
|
||||||
|
if (node._def.defaults.hasOwnProperty(d)) {
|
||||||
|
var property = node._def.defaults[d];
|
||||||
if (property.type) {
|
if (property.type) {
|
||||||
var type = registry.getNodeType(property.type);
|
var type = registry.getNodeType(property.type);
|
||||||
|
// Need to ensure the type is a config node to not treat links nodes
|
||||||
if (type && type.category == "config") {
|
if (type && type.category == "config") {
|
||||||
var configNode = configNodes[n[d]];
|
var configNode = configNodes[node[d]];
|
||||||
if (configNode) {
|
if (configNode) {
|
||||||
if (configNode.users.indexOf(n) === -1) {
|
if (options.action === "add") {
|
||||||
configNode.users.push(n);
|
if (configNode.users.indexOf(node) === -1) {
|
||||||
RED.events.emit('nodes:change',configNode)
|
configNode.users.push(node);
|
||||||
|
if (options.emitEvent) {
|
||||||
|
RED.events.emit('nodes:change', configNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (options.action === "remove") {
|
||||||
|
if (configNode.users.indexOf(node) !== -1) {
|
||||||
|
const users = configNode.users;
|
||||||
|
users.splice(users.indexOf(node), 1);
|
||||||
|
if (options.emitEvent) {
|
||||||
|
RED.events.emit('nodes:change', configNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2681,6 +2805,36 @@ RED.nodes = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subflows can have config node env
|
||||||
|
if (node.type.indexOf("subflow:") === 0) {
|
||||||
|
node.env?.forEach((prop) => {
|
||||||
|
if (prop.type === "conf-type" && prop.value) {
|
||||||
|
// Add the node to the config node users
|
||||||
|
const configNode = getNode(prop.value);
|
||||||
|
if (configNode) {
|
||||||
|
if (options.action === "add") {
|
||||||
|
if (configNode.users.indexOf(node) === -1) {
|
||||||
|
configNode.users.push(node);
|
||||||
|
if (options.emitEvent) {
|
||||||
|
RED.events.emit('nodes:change', configNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (options.action === "remove") {
|
||||||
|
if (configNode.users.indexOf(node) !== -1) {
|
||||||
|
const users = configNode.users;
|
||||||
|
users.splice(users.indexOf(node), 1);
|
||||||
|
if (options.emitEvent) {
|
||||||
|
RED.events.emit('nodes:change', configNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function flowVersion(version) {
|
function flowVersion(version) {
|
||||||
if (version !== undefined) {
|
if (version !== undefined) {
|
||||||
|
@ -334,6 +334,30 @@ RED.clipboard = (function() {
|
|||||||
},100);
|
},100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates if the provided string looks like valid flow json
|
||||||
|
* @param {string} flowString the string to validate
|
||||||
|
* @returns If valid, returns the node array
|
||||||
|
*/
|
||||||
|
function validateFlowString(flowString) {
|
||||||
|
const res = JSON.parse(flowString)
|
||||||
|
if (!Array.isArray(res)) {
|
||||||
|
throw new Error(RED._("clipboard.import.errors.notArray"));
|
||||||
|
}
|
||||||
|
for (let i = 0; i < res.length; i++) {
|
||||||
|
if (typeof res[i] !== "object") {
|
||||||
|
throw new Error(RED._("clipboard.import.errors.itemNotObject",{index:i}));
|
||||||
|
}
|
||||||
|
if (!Object.hasOwn(res[i], 'id')) {
|
||||||
|
throw new Error(RED._("clipboard.import.errors.missingId",{index:i}));
|
||||||
|
}
|
||||||
|
if (!Object.hasOwn(res[i], 'type')) {
|
||||||
|
throw new Error(RED._("clipboard.import.errors.missingType",{index:i}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
var validateImportTimeout;
|
var validateImportTimeout;
|
||||||
function validateImport() {
|
function validateImport() {
|
||||||
if (activeTab === "red-ui-clipboard-dialog-import-tab-clipboard") {
|
if (activeTab === "red-ui-clipboard-dialog-import-tab-clipboard") {
|
||||||
@ -351,21 +375,7 @@ RED.clipboard = (function() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (!/^\[[\s\S]*\]$/m.test(v)) {
|
validateFlowString(v)
|
||||||
throw new Error(RED._("clipboard.import.errors.notArray"));
|
|
||||||
}
|
|
||||||
var res = JSON.parse(v);
|
|
||||||
for (var i=0;i<res.length;i++) {
|
|
||||||
if (typeof res[i] !== "object") {
|
|
||||||
throw new Error(RED._("clipboard.import.errors.itemNotObject",{index:i}));
|
|
||||||
}
|
|
||||||
if (!res[i].hasOwnProperty('id')) {
|
|
||||||
throw new Error(RED._("clipboard.import.errors.missingId",{index:i}));
|
|
||||||
}
|
|
||||||
if (!res[i].hasOwnProperty('type')) {
|
|
||||||
throw new Error(RED._("clipboard.import.errors.missingType",{index:i}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
currentPopoverError = null;
|
currentPopoverError = null;
|
||||||
popover.close(true);
|
popover.close(true);
|
||||||
importInput.removeClass("input-error");
|
importInput.removeClass("input-error");
|
||||||
@ -998,16 +1008,16 @@ RED.clipboard = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function importNodes(nodesStr,addFlow) {
|
function importNodes(nodesStr,addFlow) {
|
||||||
var newNodes = nodesStr;
|
let newNodes = nodesStr;
|
||||||
if (typeof nodesStr === 'string') {
|
if (typeof nodesStr === 'string') {
|
||||||
try {
|
try {
|
||||||
nodesStr = nodesStr.trim();
|
nodesStr = nodesStr.trim();
|
||||||
if (nodesStr.length === 0) {
|
if (nodesStr.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
newNodes = JSON.parse(nodesStr);
|
newNodes = validateFlowString(nodesStr)
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
var e = new Error(RED._("clipboard.invalidFlow",{message:err.message}));
|
const e = new Error(RED._("clipboard.invalidFlow",{message:err.message}));
|
||||||
e.code = "NODE_RED";
|
e.code = "NODE_RED";
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
@ -1342,6 +1352,7 @@ RED.clipboard = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
|
console.warn('Import failed: ', err)
|
||||||
// Ensure any errors throw above doesn't stop the drop target from
|
// Ensure any errors throw above doesn't stop the drop target from
|
||||||
// being hidden.
|
// being hidden.
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@
|
|||||||
}
|
}
|
||||||
this.menu = RED.popover.menu({
|
this.menu = RED.popover.menu({
|
||||||
tabSelect: true,
|
tabSelect: true,
|
||||||
width: 300,
|
width: Math.max(300, this.element.width()),
|
||||||
maxHeight: 200,
|
maxHeight: 200,
|
||||||
class: "red-ui-autoComplete-container",
|
class: "red-ui-autoComplete-container",
|
||||||
options: completions,
|
options: completions,
|
||||||
|
@ -63,6 +63,7 @@
|
|||||||
pre: value.substring(0,idx),
|
pre: value.substring(0,idx),
|
||||||
match: value.substring(idx,idx+len),
|
match: value.substring(idx,idx+len),
|
||||||
post: value.substring(idx+len),
|
post: value.substring(idx+len),
|
||||||
|
exact: idx === 0 && value.length === searchValue.length
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function generateSpans(match) {
|
function generateSpans(match) {
|
||||||
@ -83,7 +84,7 @@
|
|||||||
const srcMatch = getMatch(optSrc, val);
|
const srcMatch = getMatch(optSrc, val);
|
||||||
if (valMatch.found || srcMatch.found) {
|
if (valMatch.found || srcMatch.found) {
|
||||||
const element = $('<div>',{style: "display: flex"});
|
const element = $('<div>',{style: "display: flex"});
|
||||||
const valEl = $('<div/>',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"});
|
const valEl = $('<div/>',{ class: "red-ui-autoComplete-completion" });
|
||||||
valEl.append(generateSpans(valMatch));
|
valEl.append(generateSpans(valMatch));
|
||||||
valEl.appendTo(element);
|
valEl.appendTo(element);
|
||||||
if (optSrc) {
|
if (optSrc) {
|
||||||
@ -159,7 +160,7 @@
|
|||||||
if (valMatch.found) {
|
if (valMatch.found) {
|
||||||
const optSrc = envVarsMap[v]
|
const optSrc = envVarsMap[v]
|
||||||
const element = $('<div>',{style: "display: flex"});
|
const element = $('<div>',{style: "display: flex"});
|
||||||
const valEl = $('<div/>',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"});
|
const valEl = $('<div/>',{ class: "red-ui-autoComplete-completion" });
|
||||||
valEl.append(generateSpans(valMatch))
|
valEl.append(generateSpans(valMatch))
|
||||||
valEl.appendTo(element)
|
valEl.appendTo(element)
|
||||||
|
|
||||||
@ -201,7 +202,7 @@
|
|||||||
const that = this
|
const that = this
|
||||||
const getContextKeysFromRuntime = function(scope, store, searchKey, done) {
|
const getContextKeysFromRuntime = function(scope, store, searchKey, done) {
|
||||||
contextKnownKeys[scope] = contextKnownKeys[scope] || {}
|
contextKnownKeys[scope] = contextKnownKeys[scope] || {}
|
||||||
contextKnownKeys[scope][store] = contextKnownKeys[scope][store] || new Set()
|
contextKnownKeys[scope][store] = contextKnownKeys[scope][store] || new Map()
|
||||||
if (searchKey.length > 0) {
|
if (searchKey.length > 0) {
|
||||||
try {
|
try {
|
||||||
RED.utils.normalisePropertyExpression(searchKey)
|
RED.utils.normalisePropertyExpression(searchKey)
|
||||||
@ -223,11 +224,12 @@
|
|||||||
const result = data[store] || {}
|
const result = data[store] || {}
|
||||||
const keys = result.keys || []
|
const keys = result.keys || []
|
||||||
const keyPrefix = searchKey + (searchKey.length > 0 ? '.' : '')
|
const keyPrefix = searchKey + (searchKey.length > 0 ? '.' : '')
|
||||||
keys.forEach(key => {
|
keys.forEach(keyInfo => {
|
||||||
|
const key = keyInfo.key
|
||||||
if (/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(key)) {
|
if (/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(key)) {
|
||||||
contextKnownKeys[scope][store].add(keyPrefix + key)
|
contextKnownKeys[scope][store].set(keyPrefix + key, keyInfo)
|
||||||
} else {
|
} else {
|
||||||
contextKnownKeys[scope][store].add(searchKey + "[\""+key.replace(/"/,"\\\"")+"\"]")
|
contextKnownKeys[scope][store].set(searchKey + "[\""+key.replace(/"/,"\\\"")+"\"]", keyInfo)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
done()
|
done()
|
||||||
@ -242,14 +244,14 @@
|
|||||||
// Get the flow id of the node we're editing
|
// Get the flow id of the node we're editing
|
||||||
const editStack = RED.editor.getEditStack()
|
const editStack = RED.editor.getEditStack()
|
||||||
if (editStack.length === 0) {
|
if (editStack.length === 0) {
|
||||||
done([])
|
done(new Map())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const editingNode = editStack.pop()
|
const editingNode = editStack.pop()
|
||||||
if (editingNode.z) {
|
if (editingNode.z) {
|
||||||
scope = `${scope}/${editingNode.z}`
|
scope = `${scope}/${editingNode.z}`
|
||||||
} else {
|
} else {
|
||||||
done([])
|
done(new Map())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -269,17 +271,29 @@
|
|||||||
return function(val, done) {
|
return function(val, done) {
|
||||||
getContextKeys(val, function (keys) {
|
getContextKeys(val, function (keys) {
|
||||||
const matches = []
|
const matches = []
|
||||||
keys.forEach(v => {
|
keys.forEach((keyInfo, v) => {
|
||||||
let optVal = v
|
let optVal = v
|
||||||
let valMatch = getMatch(optVal, val);
|
let valMatch = getMatch(optVal, val);
|
||||||
if (!valMatch.found && val.length > 0 && val.endsWith('.')) {
|
if (!valMatch.found && val.length > 0) {
|
||||||
|
if (val.endsWith('.')) {
|
||||||
// Search key ends in '.' - but doesn't match. Check again
|
// Search key ends in '.' - but doesn't match. Check again
|
||||||
// with [" at the end instead so we match bracket notation
|
// with [" at the end instead so we match bracket notation
|
||||||
valMatch = getMatch(optVal, val.substring(0, val.length - 1) + '["')
|
valMatch = getMatch(optVal, val.substring(0, val.length - 1) + '["')
|
||||||
|
// } else if (val.endsWith('[') && /^array/.test(keyInfo.format)) {
|
||||||
|
// console.log('this case')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (valMatch.found) {
|
if (valMatch.found) {
|
||||||
const element = $('<div>',{style: "display: flex"});
|
const element = $('<div>',{style: "display: flex"});
|
||||||
const valEl = $('<div/>',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"});
|
const valEl = $('<div/>',{ class: "red-ui-autoComplete-completion" });
|
||||||
|
// if (keyInfo.format) {
|
||||||
|
// valMatch.post += ' ' + keyInfo.format
|
||||||
|
// }
|
||||||
|
if (valMatch.exact && /^array/.test(keyInfo.format)) {
|
||||||
|
valMatch.post += `[0-${keyInfo.length}]`
|
||||||
|
optVal += '['
|
||||||
|
|
||||||
|
}
|
||||||
valEl.append(generateSpans(valMatch))
|
valEl.append(generateSpans(valMatch))
|
||||||
valEl.appendTo(element)
|
valEl.appendTo(element)
|
||||||
matches.push({
|
matches.push({
|
||||||
@ -1567,7 +1581,8 @@
|
|||||||
if (tooltip) {
|
if (tooltip) {
|
||||||
tooltip.setContent(valid);
|
tooltip.setContent(valid);
|
||||||
} else {
|
} else {
|
||||||
tooltip = RED.popover.tooltip(this.elementDiv, valid);
|
const target = this.typeMap[type]?.options ? this.optionSelectLabel : this.elementDiv;
|
||||||
|
tooltip = RED.popover.tooltip(target, valid);
|
||||||
this.element.data("tooltip", tooltip);
|
this.element.data("tooltip", tooltip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,15 +54,15 @@ RED.contextMenu = (function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const scale = RED.view.scale()
|
||||||
const offset = $("#red-ui-workspace-chart").offset()
|
const offset = $("#red-ui-workspace-chart").offset()
|
||||||
|
let addX = (options.x - offset.left + $("#red-ui-workspace-chart").scrollLeft()) / scale
|
||||||
let addX = options.x - offset.left + $("#red-ui-workspace-chart").scrollLeft()
|
let addY = (options.y - offset.top + $("#red-ui-workspace-chart").scrollTop()) / scale
|
||||||
let addY = options.y - offset.top + $("#red-ui-workspace-chart").scrollTop()
|
|
||||||
|
|
||||||
if (RED.view.snapGrid) {
|
if (RED.view.snapGrid) {
|
||||||
const gridSize = RED.view.gridSize()
|
const gridSize = RED.view.gridSize()
|
||||||
addX = gridSize * Math.floor(addX / gridSize)
|
addX = gridSize * Math.round(addX / gridSize)
|
||||||
addY = gridSize * Math.floor(addY / gridSize)
|
addY = gridSize * Math.round(addY / gridSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (RED.settings.theme("menu.menu-item-action-list", true)) {
|
if (RED.settings.theme("menu.menu-item-action-list", true)) {
|
||||||
@ -87,7 +87,9 @@ RED.contextMenu = (function () {
|
|||||||
},
|
},
|
||||||
(hasLinks) ? { // has least 1 wire selected
|
(hasLinks) ? { // has least 1 wire selected
|
||||||
label: RED._("contextMenu.junction"),
|
label: RED._("contextMenu.junction"),
|
||||||
onselect: 'core:split-wires-with-junctions',
|
onselect: function () {
|
||||||
|
RED.actions.invoke('core:split-wires-with-junctions', { x: addX, y: addY })
|
||||||
|
},
|
||||||
disabled: !canEdit || !hasLinks
|
disabled: !canEdit || !hasLinks
|
||||||
} : {
|
} : {
|
||||||
label: RED._("contextMenu.junction"),
|
label: RED._("contextMenu.junction"),
|
||||||
|
@ -44,6 +44,7 @@ RED.deploy = (function() {
|
|||||||
/**
|
/**
|
||||||
* options:
|
* options:
|
||||||
* type: "default" - Button with drop-down options - no further customisation available
|
* type: "default" - Button with drop-down options - no further customisation available
|
||||||
|
* label: the text to display - default: "Deploy"
|
||||||
* type: "simple" - Button without dropdown. Customisations:
|
* type: "simple" - Button without dropdown. Customisations:
|
||||||
* label: the text to display - default: "Deploy"
|
* label: the text to display - default: "Deploy"
|
||||||
* icon : the icon to use. Null removes the icon. default: "red/images/deploy-full-o.svg"
|
* icon : the icon to use. Null removes the icon. default: "red/images/deploy-full-o.svg"
|
||||||
@ -51,13 +52,14 @@ RED.deploy = (function() {
|
|||||||
function init(options) {
|
function init(options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
var type = options.type || "default";
|
var type = options.type || "default";
|
||||||
|
var label = options.label || RED._("deploy.deploy");
|
||||||
|
|
||||||
if (type == "default") {
|
if (type == "default") {
|
||||||
$('<li><span class="red-ui-deploy-button-group button-group">'+
|
$('<li><span class="red-ui-deploy-button-group button-group">'+
|
||||||
'<a id="red-ui-header-button-deploy" class="red-ui-deploy-button disabled" href="#">'+
|
'<a id="red-ui-header-button-deploy" class="red-ui-deploy-button disabled" href="#">'+
|
||||||
'<span class="red-ui-deploy-button-content">'+
|
'<span class="red-ui-deploy-button-content">'+
|
||||||
'<img id="red-ui-header-button-deploy-icon" src="red/images/deploy-full-o.svg"> '+
|
'<img id="red-ui-header-button-deploy-icon" src="red/images/deploy-full-o.svg"> '+
|
||||||
'<span>'+RED._("deploy.deploy")+'</span>'+
|
'<span>'+label+'</span>'+
|
||||||
'</span>'+
|
'</span>'+
|
||||||
'<span class="red-ui-deploy-button-spinner hide">'+
|
'<span class="red-ui-deploy-button-spinner hide">'+
|
||||||
'<img src="red/images/spin.svg"/>'+
|
'<img src="red/images/spin.svg"/>'+
|
||||||
@ -78,7 +80,6 @@ RED.deploy = (function() {
|
|||||||
mainMenuItems.push({id:"deploymenu-item-reload", icon:"red/images/deploy-reload.svg",label:RED._("deploy.restartFlows"),sublabel:RED._("deploy.restartFlowsDesc"),onselect:"core:restart-flows"})
|
mainMenuItems.push({id:"deploymenu-item-reload", icon:"red/images/deploy-reload.svg",label:RED._("deploy.restartFlows"),sublabel:RED._("deploy.restartFlowsDesc"),onselect:"core:restart-flows"})
|
||||||
RED.menu.init({id:"red-ui-header-button-deploy-options", options: mainMenuItems });
|
RED.menu.init({id:"red-ui-header-button-deploy-options", options: mainMenuItems });
|
||||||
} else if (type == "simple") {
|
} else if (type == "simple") {
|
||||||
var label = options.label || RED._("deploy.deploy");
|
|
||||||
var icon = 'red/images/deploy-full-o.svg';
|
var icon = 'red/images/deploy-full-o.svg';
|
||||||
if (options.hasOwnProperty('icon')) {
|
if (options.hasOwnProperty('icon')) {
|
||||||
icon = options.icon;
|
icon = options.icon;
|
||||||
@ -424,11 +425,15 @@ RED.deploy = (function() {
|
|||||||
const unknownNodes = [];
|
const unknownNodes = [];
|
||||||
const invalidNodes = [];
|
const invalidNodes = [];
|
||||||
|
|
||||||
|
const isDisabled = function (node) {
|
||||||
|
return (node.d || RED.nodes.workspace(node.z)?.disabled);
|
||||||
|
};
|
||||||
|
|
||||||
RED.nodes.eachConfig(function (node) {
|
RED.nodes.eachConfig(function (node) {
|
||||||
if (node.valid === undefined) {
|
if (node.valid === undefined) {
|
||||||
RED.editor.validateNode(node);
|
RED.editor.validateNode(node);
|
||||||
}
|
}
|
||||||
if (!node.valid && !node.d) {
|
if (!node.valid && !isDisabled(node)) {
|
||||||
invalidNodes.push(getNodeInfo(node));
|
invalidNodes.push(getNodeInfo(node));
|
||||||
}
|
}
|
||||||
if (node.type === "unknown") {
|
if (node.type === "unknown") {
|
||||||
@ -438,7 +443,7 @@ RED.deploy = (function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
RED.nodes.eachNode(function (node) {
|
RED.nodes.eachNode(function (node) {
|
||||||
if (!node.valid && !node.d) {
|
if (!node.valid && !isDisabled(node)) {
|
||||||
invalidNodes.push(getNodeInfo(node));
|
invalidNodes.push(getNodeInfo(node));
|
||||||
}
|
}
|
||||||
if (node.type === "unknown") {
|
if (node.type === "unknown") {
|
||||||
@ -452,7 +457,7 @@ RED.deploy = (function() {
|
|||||||
|
|
||||||
const unusedConfigNodes = [];
|
const unusedConfigNodes = [];
|
||||||
RED.nodes.eachConfig(function (node) {
|
RED.nodes.eachConfig(function (node) {
|
||||||
if ((node._def.hasUsers !== false) && (node.users.length === 0)) {
|
if ((node._def.hasUsers !== false) && (node.users.length === 0) && !isDisabled(node)) {
|
||||||
unusedConfigNodes.push(getNodeInfo(node));
|
unusedConfigNodes.push(getNodeInfo(node));
|
||||||
hasUnusedConfig = true;
|
hasUnusedConfig = true;
|
||||||
}
|
}
|
||||||
@ -589,7 +594,9 @@ RED.deploy = (function() {
|
|||||||
RED.notify('<p>' + RED._("deploy.successfulDeploy") + '</p>', "success");
|
RED.notify('<p>' + RED._("deploy.successfulDeploy") + '</p>', "success");
|
||||||
}
|
}
|
||||||
const flowsToLock = new Set()
|
const flowsToLock = new Set()
|
||||||
|
// Node's properties cannot be modified if its workspace is locked.
|
||||||
function ensureUnlocked(id) {
|
function ensureUnlocked(id) {
|
||||||
|
// TODO: `RED.nodes.subflow` is useless
|
||||||
const flow = id && (RED.nodes.workspace(id) || RED.nodes.subflow(id) || null);
|
const flow = id && (RED.nodes.workspace(id) || RED.nodes.subflow(id) || null);
|
||||||
const isLocked = flow ? flow.locked : false;
|
const isLocked = flow ? flow.locked : false;
|
||||||
if (flow && isLocked) {
|
if (flow && isLocked) {
|
||||||
@ -642,6 +649,7 @@ RED.deploy = (function() {
|
|||||||
delete confNode.credentials;
|
delete confNode.credentials;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// Subflow cannot be locked
|
||||||
RED.nodes.eachSubflow(function (subflow) {
|
RED.nodes.eachSubflow(function (subflow) {
|
||||||
if (subflow.changed) {
|
if (subflow.changed) {
|
||||||
subflow.changed = false;
|
subflow.changed = false;
|
||||||
@ -650,12 +658,18 @@ RED.deploy = (function() {
|
|||||||
});
|
});
|
||||||
RED.nodes.eachWorkspace(function (ws) {
|
RED.nodes.eachWorkspace(function (ws) {
|
||||||
if (ws.changed || ws.added) {
|
if (ws.changed || ws.added) {
|
||||||
ensureUnlocked(ws.z)
|
// Ensure the Workspace is unlocked to modify its properties.
|
||||||
|
ensureUnlocked(ws.id);
|
||||||
ws.changed = false;
|
ws.changed = false;
|
||||||
delete ws.added
|
delete ws.added
|
||||||
|
if (flowsToLock.has(ws)) {
|
||||||
|
ws.locked = true;
|
||||||
|
flowsToLock.delete(ws);
|
||||||
|
}
|
||||||
RED.events.emit("flows:change", ws)
|
RED.events.emit("flows:change", ws)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// Ensures all workspaces to be locked have been locked.
|
||||||
flowsToLock.forEach(flow => {
|
flowsToLock.forEach(flow => {
|
||||||
flow.locked = true
|
flow.locked = true
|
||||||
})
|
})
|
||||||
|
@ -808,6 +808,20 @@ RED.editor = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const oldCreds = {};
|
||||||
|
if (editing_node._def.credentials) {
|
||||||
|
for (const prop in editing_node._def.credentials) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(editing_node._def.credentials, prop)) {
|
||||||
|
if (editing_node._def.credentials[prop].type === 'password') {
|
||||||
|
oldCreds['has_' + prop] = editing_node.credentials['has_' + prop];
|
||||||
|
}
|
||||||
|
if (prop in editing_node.credentials) {
|
||||||
|
oldCreds[prop] = editing_node.credentials[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const rc = editing_node._def.oneditsave.call(editing_node);
|
const rc = editing_node._def.oneditsave.call(editing_node);
|
||||||
if (rc === true) {
|
if (rc === true) {
|
||||||
@ -839,6 +853,25 @@ RED.editor = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (editing_node._def.credentials) {
|
||||||
|
for (const prop in editing_node._def.credentials) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(editing_node._def.credentials, prop)) {
|
||||||
|
if (oldCreds[prop] !== editing_node.credentials[prop]) {
|
||||||
|
if (editing_node.credentials[prop] === '__PWRD__') {
|
||||||
|
// The password may not exist in oldCreds
|
||||||
|
// The value '__PWRD__' means the password exists,
|
||||||
|
// so ignore this change
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
editState.changes.credentials = editState.changes.credentials || {};
|
||||||
|
editState.changes.credentials['has_' + prop] = oldCreds['has_' + prop];
|
||||||
|
editState.changes.credentials[prop] = oldCreds[prop];
|
||||||
|
editState.changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1484,131 +1517,178 @@ RED.editor = (function() {
|
|||||||
text: adding ? RED._("editor.configAdd") : RED._("editor.configUpdate"),
|
text: adding ? RED._("editor.configAdd") : RED._("editor.configUpdate"),
|
||||||
class: "primary",
|
class: "primary",
|
||||||
click: function() {
|
click: function() {
|
||||||
var editState = {
|
// TODO: Already defined
|
||||||
|
const configProperty = name;
|
||||||
|
const configType = type;
|
||||||
|
const configTypeDef = RED.nodes.getType(configType);
|
||||||
|
|
||||||
|
const wasChanged = editing_config_node.changed;
|
||||||
|
const editState = {
|
||||||
changes: {},
|
changes: {},
|
||||||
changed: false,
|
changed: false,
|
||||||
outputMap: null
|
outputMap: null
|
||||||
};
|
};
|
||||||
var configProperty = name;
|
|
||||||
var configId = editing_config_node.id;
|
|
||||||
var configType = type;
|
|
||||||
var configAdding = adding;
|
|
||||||
var configTypeDef = RED.nodes.getType(configType);
|
|
||||||
var d;
|
|
||||||
var input;
|
|
||||||
|
|
||||||
if (configTypeDef.oneditsave) {
|
// Call `oneditsave` and search for changes
|
||||||
try {
|
handleEditSave(editing_config_node, editState);
|
||||||
configTypeDef.oneditsave.call(editing_config_node);
|
|
||||||
} catch(err) {
|
|
||||||
console.warn("oneditsave",editing_config_node.id,editing_config_node.type,err.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (d in configTypeDef.defaults) {
|
|
||||||
if (configTypeDef.defaults.hasOwnProperty(d)) {
|
|
||||||
var newValue;
|
|
||||||
input = $("#node-config-input-"+d);
|
|
||||||
if (input.attr('type') === "checkbox") {
|
|
||||||
newValue = input.prop('checked');
|
|
||||||
} else if ("format" in configTypeDef.defaults[d] && configTypeDef.defaults[d].format !== "" && input[0].nodeName === "DIV") {
|
|
||||||
newValue = input.text();
|
|
||||||
} else {
|
|
||||||
newValue = input.val();
|
|
||||||
}
|
|
||||||
if (newValue != null && newValue !== editing_config_node[d]) {
|
|
||||||
if (editing_config_node._def.defaults[d].type) {
|
|
||||||
if (newValue == "_ADD_") {
|
|
||||||
newValue = "";
|
|
||||||
}
|
|
||||||
// Change to a related config node
|
|
||||||
var configNode = RED.nodes.node(editing_config_node[d]);
|
|
||||||
if (configNode) {
|
|
||||||
var users = configNode.users;
|
|
||||||
users.splice(users.indexOf(editing_config_node),1);
|
|
||||||
RED.events.emit("nodes:change",configNode);
|
|
||||||
}
|
|
||||||
configNode = RED.nodes.node(newValue);
|
|
||||||
if (configNode) {
|
|
||||||
configNode.users.push(editing_config_node);
|
|
||||||
RED.events.emit("nodes:change",configNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
editing_config_node[d] = newValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Search for changes in the edit box (panes)
|
||||||
activeEditPanes.forEach(function (pane) {
|
activeEditPanes.forEach(function (pane) {
|
||||||
if (pane.apply) {
|
if (pane.apply) {
|
||||||
pane.apply.call(pane, editState);
|
pane.apply.call(pane, editState);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
editing_config_node.label = configTypeDef.label;
|
// TODO: Why?
|
||||||
|
editing_config_node.label = configTypeDef.label
|
||||||
var scope = $("#red-ui-editor-config-scope").val();
|
|
||||||
editing_config_node.z = scope;
|
|
||||||
|
|
||||||
|
// Check if disabled has changed
|
||||||
if ($("#node-config-input-node-disabled").prop('checked')) {
|
if ($("#node-config-input-node-disabled").prop('checked')) {
|
||||||
if (editing_config_node.d !== true) {
|
if (editing_config_node.d !== true) {
|
||||||
|
editState.changes.d = editing_config_node.d;
|
||||||
|
editState.changed = true;
|
||||||
editing_config_node.d = true;
|
editing_config_node.d = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (editing_config_node.d === true) {
|
if (editing_config_node.d === true) {
|
||||||
|
editState.changes.d = editing_config_node.d;
|
||||||
|
editState.changed = true;
|
||||||
delete editing_config_node.d;
|
delete editing_config_node.d;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scope) {
|
// NOTE: must be undefined if no scope used
|
||||||
// Search for nodes that use this one that are no longer
|
const scope = $("#red-ui-editor-config-scope").val() || undefined;
|
||||||
|
|
||||||
|
// Check if the scope has changed
|
||||||
|
if (editing_config_node.z !== scope) {
|
||||||
|
editState.changes.z = editing_config_node.z;
|
||||||
|
editState.changed = true;
|
||||||
|
editing_config_node.z = scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for nodes that use this config node that are no longer
|
||||||
// in scope, so must be removed
|
// in scope, so must be removed
|
||||||
editing_config_node.users = editing_config_node.users.filter(function(n) {
|
const historyEvents = [];
|
||||||
var keep = true;
|
if (scope) {
|
||||||
for (var d in n._def.defaults) {
|
const newUsers = editing_config_node.users.filter(function (node) {
|
||||||
if (n._def.defaults.hasOwnProperty(d)) {
|
let keepNode = false;
|
||||||
if (n._def.defaults[d].type === editing_config_node.type &&
|
let nodeModified = null;
|
||||||
n[d] === editing_config_node.id &&
|
|
||||||
n.z !== scope) {
|
for (const d in node._def.defaults) {
|
||||||
keep = false;
|
if (node._def.defaults.hasOwnProperty(d)) {
|
||||||
// Remove the reference to this node
|
if (node._def.defaults[d].type === editing_config_node.type) {
|
||||||
// and revalidate
|
if (node[d] === editing_config_node.id) {
|
||||||
n[d] = null;
|
if (node.z === editing_config_node.z) {
|
||||||
n.dirty = true;
|
// The node is kept only if at least one property uses
|
||||||
n.changed = true;
|
// this config node in the correct scope.
|
||||||
validateNode(n);
|
keepNode = true;
|
||||||
|
} else {
|
||||||
|
if (!nodeModified) {
|
||||||
|
nodeModified = {
|
||||||
|
t: "edit",
|
||||||
|
node: node,
|
||||||
|
changes: { [d]: node[d] },
|
||||||
|
changed: node.changed,
|
||||||
|
dirty: node.dirty
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
nodeModified.changes[d] = node[d];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the reference to the config node
|
||||||
|
node[d] = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return keep;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the node modified to the history
|
||||||
|
if (nodeModified) {
|
||||||
|
historyEvents.push(nodeModified);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark as changed and revalidate this node
|
||||||
|
if (!keepNode) {
|
||||||
|
node.changed = true;
|
||||||
|
node.dirty = true;
|
||||||
|
validateNode(node);
|
||||||
|
RED.events.emit("nodes:change", node);
|
||||||
|
}
|
||||||
|
|
||||||
|
return keepNode;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Check if users are changed
|
||||||
|
if (editing_config_node.users.length !== newUsers.length) {
|
||||||
|
editState.changes.users = editing_config_node.users;
|
||||||
|
editState.changed = true;
|
||||||
|
editing_config_node.users = newUsers;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configAdding) {
|
if (editState.changed) {
|
||||||
RED.nodes.add(editing_config_node);
|
// Set the congig node as changed
|
||||||
|
editing_config_node.changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now, validate the config node
|
||||||
validateNode(editing_config_node);
|
validateNode(editing_config_node);
|
||||||
var validatedNodes = {};
|
|
||||||
validatedNodes[editing_config_node.id] = true;
|
|
||||||
|
|
||||||
var userStack = editing_config_node.users.slice();
|
// And validate nodes using this config node too
|
||||||
while(userStack.length > 0) {
|
const validatedNodes = new Set();
|
||||||
var user = userStack.pop();
|
const userStack = editing_config_node.users.slice();
|
||||||
if (!validatedNodes[user.id]) {
|
|
||||||
validatedNodes[user.id] = true;
|
validatedNodes.add(editing_config_node.id);
|
||||||
if (user.users) {
|
while (userStack.length) {
|
||||||
userStack = userStack.concat(user.users);
|
const node = userStack.pop();
|
||||||
|
if (!validatedNodes.has(node.id)) {
|
||||||
|
validatedNodes.add(node.id);
|
||||||
|
if (node.users) {
|
||||||
|
userStack.push(...node.users);
|
||||||
}
|
}
|
||||||
validateNode(user);
|
validateNode(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RED.nodes.dirty(true);
|
|
||||||
RED.view.redraw(true);
|
let historyEvent = {
|
||||||
if (!configAdding) {
|
t: "edit",
|
||||||
|
node: editing_config_node,
|
||||||
|
changes: editState.changes,
|
||||||
|
changed: wasChanged,
|
||||||
|
dirty: RED.nodes.dirty()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (historyEvents.length) {
|
||||||
|
// Need a multi events
|
||||||
|
historyEvent = {
|
||||||
|
t: "multi",
|
||||||
|
events: [historyEvent].concat(historyEvents),
|
||||||
|
dirty: historyEvent.dirty
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!adding) {
|
||||||
|
// This event is triggered when the edit box is saved,
|
||||||
|
// regardless of whether there are any modifications.
|
||||||
RED.events.emit("editor:save", editing_config_node);
|
RED.events.emit("editor:save", editing_config_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editState.changed) {
|
||||||
|
if (adding) {
|
||||||
|
RED.history.push({ t: "add", nodes: [editing_config_node.id], dirty: RED.nodes.dirty() });
|
||||||
|
// Add the new config node and trigger the `nodes:add` event
|
||||||
|
RED.nodes.add(editing_config_node);
|
||||||
|
} else {
|
||||||
|
RED.history.push(historyEvent);
|
||||||
RED.events.emit("nodes:change", editing_config_node);
|
RED.events.emit("nodes:change", editing_config_node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RED.nodes.dirty(true);
|
||||||
|
RED.view.redraw(true);
|
||||||
|
}
|
||||||
|
|
||||||
RED.tray.close(function() {
|
RED.tray.close(function() {
|
||||||
var filter = null;
|
var filter = null;
|
||||||
// when editing a config via subflow edit panel, the `configProperty` will not
|
// when editing a config via subflow edit panel, the `configProperty` will not
|
||||||
@ -1756,8 +1836,18 @@ RED.editor = (function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
let envToRemove = new Set()
|
||||||
if (!isSameObj(old_env, new_env)) {
|
if (!isSameObj(old_env, new_env)) {
|
||||||
|
// Get a list of env properties that have been removed
|
||||||
|
// by comparing old_env and new_env
|
||||||
|
if (old_env) {
|
||||||
|
old_env.forEach(env => { envToRemove.add(env.name) })
|
||||||
|
}
|
||||||
|
if (new_env) {
|
||||||
|
new_env.forEach(env => {
|
||||||
|
envToRemove.delete(env.name)
|
||||||
|
})
|
||||||
|
}
|
||||||
editState.changes.env = editing_node.env;
|
editState.changes.env = editing_node.env;
|
||||||
editing_node.env = new_env;
|
editing_node.env = new_env;
|
||||||
editState.changed = true;
|
editState.changed = true;
|
||||||
@ -1766,10 +1856,11 @@ RED.editor = (function() {
|
|||||||
|
|
||||||
|
|
||||||
if (editState.changed) {
|
if (editState.changed) {
|
||||||
var wasChanged = editing_node.changed;
|
let wasChanged = editing_node.changed;
|
||||||
editing_node.changed = true;
|
editing_node.changed = true;
|
||||||
validateNode(editing_node);
|
validateNode(editing_node);
|
||||||
var subflowInstances = [];
|
let subflowInstances = [];
|
||||||
|
let instanceHistoryEvents = []
|
||||||
RED.nodes.eachNode(function(n) {
|
RED.nodes.eachNode(function(n) {
|
||||||
if (n.type == "subflow:"+editing_node.id) {
|
if (n.type == "subflow:"+editing_node.id) {
|
||||||
subflowInstances.push({
|
subflowInstances.push({
|
||||||
@ -1779,13 +1870,35 @@ RED.editor = (function() {
|
|||||||
n._def.color = editing_node.color;
|
n._def.color = editing_node.color;
|
||||||
n.changed = true;
|
n.changed = true;
|
||||||
n.dirty = true;
|
n.dirty = true;
|
||||||
|
if (n.env) {
|
||||||
|
const oldEnv = n.env
|
||||||
|
const newEnv = []
|
||||||
|
let envChanged = false
|
||||||
|
n.env.forEach((env, index) => {
|
||||||
|
if (envToRemove.has(env.name)) {
|
||||||
|
envChanged = true
|
||||||
|
} else {
|
||||||
|
newEnv.push(env)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (envChanged) {
|
||||||
|
instanceHistoryEvents.push({
|
||||||
|
t: 'edit',
|
||||||
|
node: n,
|
||||||
|
changes: { env: oldEnv },
|
||||||
|
dirty: n.dirty,
|
||||||
|
changed: n.changed
|
||||||
|
})
|
||||||
|
n.env = newEnv
|
||||||
|
}
|
||||||
|
}
|
||||||
updateNodeProperties(n);
|
updateNodeProperties(n);
|
||||||
validateNode(n);
|
validateNode(n);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
RED.events.emit("subflows:change",editing_node);
|
RED.events.emit("subflows:change",editing_node);
|
||||||
RED.nodes.dirty(true);
|
RED.nodes.dirty(true);
|
||||||
var historyEvent = {
|
let historyEvent = {
|
||||||
t:'edit',
|
t:'edit',
|
||||||
node:editing_node,
|
node:editing_node,
|
||||||
changes:editState.changes,
|
changes:editState.changes,
|
||||||
@ -1795,7 +1908,13 @@ RED.editor = (function() {
|
|||||||
instances:subflowInstances
|
instances:subflowInstances
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if (instanceHistoryEvents.length > 0) {
|
||||||
|
historyEvent = {
|
||||||
|
t: 'multi',
|
||||||
|
events: [ historyEvent, ...instanceHistoryEvents ],
|
||||||
|
dirty: wasDirty
|
||||||
|
}
|
||||||
|
}
|
||||||
RED.history.push(historyEvent);
|
RED.history.push(historyEvent);
|
||||||
}
|
}
|
||||||
editing_node.dirty = true;
|
editing_node.dirty = true;
|
||||||
|
@ -691,6 +691,7 @@ RED.editor.codeEditor.monaco = (function() {
|
|||||||
2322, //Type 'unknown' is not assignable to type 'string'
|
2322, //Type 'unknown' is not assignable to type 'string'
|
||||||
2339, //property does not exist on
|
2339, //property does not exist on
|
||||||
2345, //Argument of type xxx is not assignable to parameter of type 'DateTimeFormatOptions'
|
2345, //Argument of type xxx is not assignable to parameter of type 'DateTimeFormatOptions'
|
||||||
|
2538, //Ignore symbols as index property error.
|
||||||
7043, //i forget what this one is,
|
7043, //i forget what this one is,
|
||||||
80001, //Convert to ES6 module
|
80001, //Convert to ES6 module
|
||||||
80004, //JSDoc types may be moved to TypeScript types.
|
80004, //JSDoc types may be moved to TypeScript types.
|
||||||
|
@ -131,7 +131,7 @@ RED.editor.envVarList = (function() {
|
|||||||
nameField.trigger('change');
|
nameField.trigger('change');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sortable: ".red-ui-editableList-item-handle",
|
sortable: true,
|
||||||
removable: false
|
removable: false
|
||||||
});
|
});
|
||||||
var parentEnv = {};
|
var parentEnv = {};
|
||||||
|
@ -27,6 +27,12 @@
|
|||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function file2Text(file,cb) {
|
||||||
|
file.arrayBuffer().then(d => {
|
||||||
|
cb( new TextDecoder().decode(d) )
|
||||||
|
}).catch(ex => { cb(`error: ${ex}`) })
|
||||||
|
}
|
||||||
|
|
||||||
var initialized = false;
|
var initialized = false;
|
||||||
var currentEditor = null;
|
var currentEditor = null;
|
||||||
/**
|
/**
|
||||||
@ -52,6 +58,7 @@
|
|||||||
if (files.length === 1) {
|
if (files.length === 1) {
|
||||||
var file = files[0];
|
var file = files[0];
|
||||||
var name = file.name.toLowerCase();
|
var name = file.name.toLowerCase();
|
||||||
|
var fileType = file.type.toLowerCase();
|
||||||
|
|
||||||
if (name.match(/\.(apng|avif|gif|jpeg|png|svg|webp)$/)) {
|
if (name.match(/\.(apng|avif|gif|jpeg|png|svg|webp)$/)) {
|
||||||
file2base64Image(file, function (image) {
|
file2base64Image(file, function (image) {
|
||||||
@ -63,6 +70,29 @@
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( fileType.startsWith("text/") ) {
|
||||||
|
file2Text(file, function (txt) {
|
||||||
|
var session = currentEditor.getSession();
|
||||||
|
var pos = session.getCursorPosition();
|
||||||
|
session.insert(pos, txt);
|
||||||
|
$("#red-ui-image-drop-target").hide();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} else if ($.inArray("text/plain", ev.originalEvent.dataTransfer.types) != -1) {
|
||||||
|
let item = Object.values(ev.originalEvent.dataTransfer.items).filter(d => d.type == "text/plain")[0]
|
||||||
|
|
||||||
|
if (item) {
|
||||||
|
item.getAsString(txt => {
|
||||||
|
var session = currentEditor.getSession();
|
||||||
|
var pos = session.getCursorPosition();
|
||||||
|
session.insert(pos, txt);
|
||||||
|
$("#red-ui-image-drop-target").hide();
|
||||||
|
})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$("#red-ui-image-drop-target").hide();
|
$("#red-ui-image-drop-target").hide();
|
||||||
|
@ -20,10 +20,31 @@
|
|||||||
apply: function(editState) {
|
apply: function(editState) {
|
||||||
var old_env = node.env;
|
var old_env = node.env;
|
||||||
var new_env = [];
|
var new_env = [];
|
||||||
|
|
||||||
if (/^subflow:/.test(node.type)) {
|
if (/^subflow:/.test(node.type)) {
|
||||||
|
// Get the list of environment variables from the node properties
|
||||||
new_env = RED.subflow.exportSubflowInstanceEnv(node);
|
new_env = RED.subflow.exportSubflowInstanceEnv(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (old_env && old_env.length) {
|
||||||
|
old_env.forEach(function (prop) {
|
||||||
|
if (prop.type === "conf-type" && prop.value) {
|
||||||
|
const stillInUse = new_env?.some((p) => p.type === "conf-type" && p.name === prop.name && p.value === prop.value);
|
||||||
|
if (!stillInUse) {
|
||||||
|
// Remove the node from the config node users
|
||||||
|
// Only for empty value or modified
|
||||||
|
const configNode = RED.nodes.node(prop.value);
|
||||||
|
if (configNode) {
|
||||||
|
if (configNode.users.indexOf(node) !== -1) {
|
||||||
|
configNode.users.splice(configNode.users.indexOf(node), 1);
|
||||||
|
RED.events.emit('nodes:change', configNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Get the values from the Properties table tab
|
// Get the values from the Properties table tab
|
||||||
var items = this.list.editableList('items');
|
var items = this.list.editableList('items');
|
||||||
items.each(function (i,el) {
|
items.each(function (i,el) {
|
||||||
@ -41,7 +62,6 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
if (new_env && new_env.length > 0) {
|
if (new_env && new_env.length > 0) {
|
||||||
new_env.forEach(function(prop) {
|
new_env.forEach(function(prop) {
|
||||||
if (prop.type === "cred") {
|
if (prop.type === "cred") {
|
||||||
@ -52,6 +72,15 @@
|
|||||||
editState.changed = true;
|
editState.changed = true;
|
||||||
}
|
}
|
||||||
delete prop.value;
|
delete prop.value;
|
||||||
|
} else if (prop.type === "conf-type" && prop.value) {
|
||||||
|
const configNode = RED.nodes.node(prop.value);
|
||||||
|
if (configNode) {
|
||||||
|
if (configNode.users.indexOf(node) === -1) {
|
||||||
|
// Add the node to the config node users
|
||||||
|
configNode.users.push(node);
|
||||||
|
RED.events.emit('nodes:change', configNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@
|
|||||||
apply: function(editState) {
|
apply: function(editState) {
|
||||||
var newValue;
|
var newValue;
|
||||||
var d;
|
var d;
|
||||||
|
// If the node is a subflow, the node's properties (exepts name) are saved by `envProperties`
|
||||||
if (node._def.defaults) {
|
if (node._def.defaults) {
|
||||||
for (d in node._def.defaults) {
|
for (d in node._def.defaults) {
|
||||||
if (node._def.defaults.hasOwnProperty(d)) {
|
if (node._def.defaults.hasOwnProperty(d)) {
|
||||||
@ -131,9 +132,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (node._def.credentials) {
|
if (node._def.credentials) {
|
||||||
var credDefinition = node._def.credentials;
|
const credDefinition = node._def.credentials;
|
||||||
var credsChanged = updateNodeCredentials(node,credDefinition,this.inputClass);
|
const credChanges = updateNodeCredentials(node, credDefinition, this.inputClass);
|
||||||
editState.changed = editState.changed || credsChanged;
|
|
||||||
|
if (Object.keys(credChanges).length) {
|
||||||
|
editState.changed = true;
|
||||||
|
editState.changes.credentials = {
|
||||||
|
...(editState.changes.credentials || {}),
|
||||||
|
...credChanges
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -161,10 +169,11 @@
|
|||||||
* @param node - the node containing the credentials
|
* @param node - the node containing the credentials
|
||||||
* @param credDefinition - definition of the credentials
|
* @param credDefinition - definition of the credentials
|
||||||
* @param prefix - prefix of the input fields
|
* @param prefix - prefix of the input fields
|
||||||
* @return {boolean} whether anything has changed
|
* @return {object} an object containing the modified properties
|
||||||
*/
|
*/
|
||||||
function updateNodeCredentials(node, credDefinition, prefix) {
|
function updateNodeCredentials(node, credDefinition, prefix) {
|
||||||
var changed = false;
|
const changes = {};
|
||||||
|
|
||||||
if (!node.credentials) {
|
if (!node.credentials) {
|
||||||
node.credentials = {_:{}};
|
node.credentials = {_:{}};
|
||||||
} else if (!node.credentials._) {
|
} else if (!node.credentials._) {
|
||||||
@ -177,22 +186,33 @@
|
|||||||
if (input.length > 0) {
|
if (input.length > 0) {
|
||||||
var value = input.val();
|
var value = input.val();
|
||||||
if (credDefinition[cred].type == 'password') {
|
if (credDefinition[cred].type == 'password') {
|
||||||
node.credentials['has_' + cred] = (value !== "");
|
if (value === '__PWRD__') {
|
||||||
if (value == '__PWRD__') {
|
// A cred value exists - no changes
|
||||||
continue;
|
} else if (value === '' && node.credentials['has_' + cred] === false) {
|
||||||
}
|
// Empty cred value exists - no changes
|
||||||
changed = true;
|
} else if (value === node.credentials[cred]) {
|
||||||
|
// A cred value exists locally in the editor - no changes
|
||||||
}
|
// Like the user sets a value, saves the config,
|
||||||
|
// reopens the config and save the config again
|
||||||
|
} else {
|
||||||
|
changes['has_' + cred] = node.credentials['has_' + cred];
|
||||||
|
changes[cred] = node.credentials[cred];
|
||||||
node.credentials[cred] = value;
|
node.credentials[cred] = value;
|
||||||
if (value != node.credentials._[cred]) {
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return changed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
node.credentials['has_' + cred] = (value !== '');
|
||||||
|
} else {
|
||||||
|
// Since these creds are loaded by the editor,
|
||||||
|
// values can be directly compared
|
||||||
|
if (value !== node.credentials[cred]) {
|
||||||
|
changes[cred] = node.credentials[cred];
|
||||||
|
node.credentials[cred] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return changes;
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
|
@ -245,10 +245,15 @@ RED.library = (function() {
|
|||||||
if (lib.types && lib.types.indexOf(options.url) === -1) {
|
if (lib.types && lib.types.indexOf(options.url) === -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let icon = 'fa fa-hdd-o';
|
||||||
|
if (lib.icon) {
|
||||||
|
const fullIcon = RED.utils.separateIconPath(lib.icon);
|
||||||
|
icon = (fullIcon.module==="font-awesome"?"fa ":"")+fullIcon.file;
|
||||||
|
}
|
||||||
listing.push({
|
listing.push({
|
||||||
library: lib.id,
|
library: lib.id,
|
||||||
type: options.url,
|
type: options.url,
|
||||||
icon: lib.icon || 'fa fa-hdd-o',
|
icon,
|
||||||
label: RED._(lib.label||lib.id),
|
label: RED._(lib.label||lib.id),
|
||||||
path: "",
|
path: "",
|
||||||
expanded: true,
|
expanded: true,
|
||||||
@ -303,10 +308,15 @@ RED.library = (function() {
|
|||||||
if (lib.types && lib.types.indexOf(options.url) === -1) {
|
if (lib.types && lib.types.indexOf(options.url) === -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let icon = 'fa fa-hdd-o';
|
||||||
|
if (lib.icon) {
|
||||||
|
const fullIcon = RED.utils.separateIconPath(lib.icon);
|
||||||
|
icon = (fullIcon.module==="font-awesome"?"fa ":"")+fullIcon.file;
|
||||||
|
}
|
||||||
listing.push({
|
listing.push({
|
||||||
library: lib.id,
|
library: lib.id,
|
||||||
type: options.url,
|
type: options.url,
|
||||||
icon: lib.icon || 'fa fa-hdd-o',
|
icon,
|
||||||
label: RED._(lib.label||lib.id),
|
label: RED._(lib.label||lib.id),
|
||||||
path: "",
|
path: "",
|
||||||
expanded: true,
|
expanded: true,
|
||||||
@ -839,10 +849,10 @@ RED.library = (function() {
|
|||||||
if (file && file.label && !file.children) {
|
if (file && file.label && !file.children) {
|
||||||
$.get("library/"+file.library+"/"+file.type+"/"+file.path, function(data) {
|
$.get("library/"+file.library+"/"+file.type+"/"+file.path, function(data) {
|
||||||
//TODO: nls + sanitize
|
//TODO: nls + sanitize
|
||||||
var propRow = $('<tr class="red-ui-help-info-row"><td>Type</td><td></td></tr>').appendTo(table);
|
var propRow = $('<tr class="red-ui-help-info-row"><td>'+RED._("library.type")+'</td><td></td></tr>').appendTo(table);
|
||||||
$(propRow.children()[1]).text(activeLibrary.type);
|
$(propRow.children()[1]).text(activeLibrary.type);
|
||||||
if (file.props.hasOwnProperty('name')) {
|
if (file.props.hasOwnProperty('name')) {
|
||||||
propRow = $('<tr class="red-ui-help-info-row"><td>Name</td><td>'+file.props.name+'</td></tr>').appendTo(table);
|
propRow = $('<tr class="red-ui-help-info-row"><td>'+RED._("library.name")+'</td><td>'+file.props.name+'</td></tr>').appendTo(table);
|
||||||
$(propRow.children()[1]).text(file.props.name);
|
$(propRow.children()[1]).text(file.props.name);
|
||||||
}
|
}
|
||||||
for (var p in file.props) {
|
for (var p in file.props) {
|
||||||
|
@ -1362,7 +1362,7 @@ RED.subflow = (function() {
|
|||||||
item.value = ""+input.prop("checked");
|
item.value = ""+input.prop("checked");
|
||||||
break;
|
break;
|
||||||
case "conf-types":
|
case "conf-types":
|
||||||
item.value = input.val()
|
item.value = input.val() === "_ADD_" ? "" : input.val();
|
||||||
item.type = "conf-type"
|
item.type = "conf-type"
|
||||||
}
|
}
|
||||||
if (ui.type === "cred" || item.type !== data.parent.type || item.value !== data.parent.value) {
|
if (ui.type === "cred" || item.type !== data.parent.type || item.value !== data.parent.value) {
|
||||||
|
@ -18,8 +18,6 @@ RED.sidebar.context = (function() {
|
|||||||
var content;
|
var content;
|
||||||
var sections;
|
var sections;
|
||||||
|
|
||||||
var localCache = {};
|
|
||||||
|
|
||||||
var flowAutoRefresh;
|
var flowAutoRefresh;
|
||||||
var nodeAutoRefresh;
|
var nodeAutoRefresh;
|
||||||
var nodeSection;
|
var nodeSection;
|
||||||
@ -27,6 +25,8 @@ RED.sidebar.context = (function() {
|
|||||||
var flowSection;
|
var flowSection;
|
||||||
var globalSection;
|
var globalSection;
|
||||||
|
|
||||||
|
const expandedPaths = {}
|
||||||
|
|
||||||
var currentNode;
|
var currentNode;
|
||||||
var currentFlow;
|
var currentFlow;
|
||||||
|
|
||||||
@ -212,14 +212,41 @@ RED.sidebar.context = (function() {
|
|||||||
var l = keys.length;
|
var l = keys.length;
|
||||||
for (var i = 0; i < l; i++) {
|
for (var i = 0; i < l; i++) {
|
||||||
sortedData[keys[i]].forEach(function(v) {
|
sortedData[keys[i]].forEach(function(v) {
|
||||||
var k = keys[i];
|
const k = keys[i];
|
||||||
var l2 = sortedData[k].length;
|
let payload = v.msg;
|
||||||
var propRow = $('<tr class="red-ui-help-info-row"><td class="red-ui-sidebar-context-property"></td><td></td></tr>').appendTo(container);
|
let format = v.format;
|
||||||
var obj = $(propRow.children()[0]);
|
const tools = $('<span class="button-group"></span>');
|
||||||
|
expandedPaths[id + "." + k] = expandedPaths[id + "." + k] || new Set()
|
||||||
|
const objectElementOptions = {
|
||||||
|
typeHint: format,
|
||||||
|
sourceId: id + "." + k,
|
||||||
|
tools,
|
||||||
|
path: k,
|
||||||
|
rootPath: k,
|
||||||
|
exposeApi: true,
|
||||||
|
ontoggle: function(path,state) {
|
||||||
|
path = path.substring(k.length+1)
|
||||||
|
if (state) {
|
||||||
|
expandedPaths[id+"."+k].add(path)
|
||||||
|
} else {
|
||||||
|
// if 'a' has been collapsed, we want to remove 'a.b' and 'a[0]...' from the set
|
||||||
|
// of collapsed paths
|
||||||
|
for (let expandedPath of expandedPaths[id+"."+k]) {
|
||||||
|
if (expandedPath.startsWith(path+".") || expandedPath.startsWith(path+"[")) {
|
||||||
|
expandedPaths[id+"."+k].delete(expandedPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expandedPaths[id+"."+k].delete(path)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expandPaths: [ ...expandedPaths[id+"."+k] ].sort(),
|
||||||
|
expandLeafNodes: true
|
||||||
|
}
|
||||||
|
const propRow = $('<tr class="red-ui-help-info-row"><td class="red-ui-sidebar-context-property"></td><td></td></tr>').appendTo(container);
|
||||||
|
const obj = $(propRow.children()[0]);
|
||||||
obj.text(k);
|
obj.text(k);
|
||||||
var tools = $('<span class="button-group"></span>');
|
|
||||||
const urlSafeK = encodeURIComponent(k)
|
const urlSafeK = encodeURIComponent(k)
|
||||||
var refreshItem = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-refresh"></i></button>').appendTo(tools).on("click", function(e) {
|
const refreshItem = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-refresh"></i></button>').appendTo(tools).on("click", function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
$.getJSON(baseUrl+"/"+urlSafeK+"?store="+v.store, function(data) {
|
$.getJSON(baseUrl+"/"+urlSafeK+"?store="+v.store, function(data) {
|
||||||
@ -229,16 +256,14 @@ RED.sidebar.context = (function() {
|
|||||||
tools.detach();
|
tools.detach();
|
||||||
$(propRow.children()[1]).empty();
|
$(propRow.children()[1]).empty();
|
||||||
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
|
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
|
||||||
|
...objectElementOptions,
|
||||||
typeHint: data.format,
|
typeHint: data.format,
|
||||||
sourceId: id+"."+k,
|
|
||||||
tools: tools,
|
|
||||||
path: k
|
|
||||||
}).appendTo(propRow.children()[1]);
|
}).appendTo(propRow.children()[1]);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
RED.popover.tooltip(refreshItem,RED._("sidebar.context.refrsh"));
|
RED.popover.tooltip(refreshItem,RED._("sidebar.context.refrsh"));
|
||||||
var deleteItem = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-trash"></i></button>').appendTo(tools).on("click", function(e) {
|
const deleteItem = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-trash"></i></button>').appendTo(tools).on("click", function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
var popover = RED.popover.create({
|
var popover = RED.popover.create({
|
||||||
@ -246,7 +271,7 @@ RED.sidebar.context = (function() {
|
|||||||
target: propRow,
|
target: propRow,
|
||||||
direction: "left",
|
direction: "left",
|
||||||
content: function() {
|
content: function() {
|
||||||
var content = $('<div>');
|
const content = $('<div>');
|
||||||
$('<p data-i18n="sidebar.context.deleteConfirm"></p>').appendTo(content);
|
$('<p data-i18n="sidebar.context.deleteConfirm"></p>').appendTo(content);
|
||||||
var row = $('<p>').appendTo(content);
|
var row = $('<p>').appendTo(content);
|
||||||
var bg = $('<span class="button-group"></span>').appendTo(row);
|
var bg = $('<span class="button-group"></span>').appendTo(row);
|
||||||
@ -269,16 +294,15 @@ RED.sidebar.context = (function() {
|
|||||||
if (container.children().length === 0) {
|
if (container.children().length === 0) {
|
||||||
$('<tr class="red-ui-help-info-row red-ui-search-empty blank" colspan="2"><td data-i18n="sidebar.context.empty"></td></tr>').appendTo(container).i18n();
|
$('<tr class="red-ui-help-info-row red-ui-search-empty blank" colspan="2"><td data-i18n="sidebar.context.empty"></td></tr>').appendTo(container).i18n();
|
||||||
}
|
}
|
||||||
|
delete expandedPaths[id + "." + k]
|
||||||
} else {
|
} else {
|
||||||
payload = data.msg;
|
payload = data.msg;
|
||||||
format = data.format;
|
format = data.format;
|
||||||
tools.detach();
|
tools.detach();
|
||||||
$(propRow.children()[1]).empty();
|
$(propRow.children()[1]).empty();
|
||||||
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
|
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
|
||||||
typeHint: data.format,
|
...objectElementOptions,
|
||||||
sourceId: id+"."+k,
|
typeHint: data.format
|
||||||
tools: tools,
|
|
||||||
path: k
|
|
||||||
}).appendTo(propRow.children()[1]);
|
}).appendTo(propRow.children()[1]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -293,14 +317,7 @@ RED.sidebar.context = (function() {
|
|||||||
|
|
||||||
});
|
});
|
||||||
RED.popover.tooltip(deleteItem,RED._("sidebar.context.delete"));
|
RED.popover.tooltip(deleteItem,RED._("sidebar.context.delete"));
|
||||||
var payload = v.msg;
|
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), objectElementOptions).appendTo(propRow.children()[1]);
|
||||||
var format = v.format;
|
|
||||||
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
|
|
||||||
typeHint: v.format,
|
|
||||||
sourceId: id+"."+k,
|
|
||||||
tools: tools,
|
|
||||||
path: k
|
|
||||||
}).appendTo(propRow.children()[1]);
|
|
||||||
if (contextStores.length > 1) {
|
if (contextStores.length > 1) {
|
||||||
$("<span>",{class:"red-ui-sidebar-context-property-storename"}).text(v.store).appendTo($(propRow.children()[0]))
|
$("<span>",{class:"red-ui-sidebar-context-property-storename"}).text(v.store).appendTo($(propRow.children()[0]))
|
||||||
}
|
}
|
||||||
|
@ -204,7 +204,7 @@ RED.sidebar.info = (function() {
|
|||||||
|
|
||||||
propertiesPanelHeaderIcon.empty();
|
propertiesPanelHeaderIcon.empty();
|
||||||
RED.utils.createNodeIcon({type:"_selection_"}).appendTo(propertiesPanelHeaderIcon);
|
RED.utils.createNodeIcon({type:"_selection_"}).appendTo(propertiesPanelHeaderIcon);
|
||||||
propertiesPanelHeaderLabel.text("Selection");
|
propertiesPanelHeaderLabel.text(RED._("sidebar.info.selection"));
|
||||||
propertiesPanelHeaderReveal.hide();
|
propertiesPanelHeaderReveal.hide();
|
||||||
propertiesPanelHeaderHelp.hide();
|
propertiesPanelHeaderHelp.hide();
|
||||||
propertiesPanelHeaderCopyLink.hide();
|
propertiesPanelHeaderCopyLink.hide();
|
||||||
|
@ -335,13 +335,25 @@ RED.typeSearch = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function applyFilter(filter,type,def) {
|
function applyFilter(filter,type,def) {
|
||||||
return !def || !filter ||
|
if (!filter) {
|
||||||
(
|
// No filter; allow everything
|
||||||
(!filter.spliceMultiple) &&
|
return true
|
||||||
(!filter.type || type === filter.type) &&
|
}
|
||||||
(!filter.input || type === 'junction' || def.inputs > 0) &&
|
if (type === 'junction') {
|
||||||
(!filter.output || type === 'junction' || def.outputs > 0)
|
// Only allow Junction is there's no specific type filter
|
||||||
)
|
return !filter.type
|
||||||
|
}
|
||||||
|
if (filter.type) {
|
||||||
|
// Handle explicit type filter
|
||||||
|
return filter.type === type
|
||||||
|
}
|
||||||
|
if (!def) {
|
||||||
|
// No node definition available - allow it
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Check if the filter is for input/outputs and apply
|
||||||
|
return (!filter.input || def.inputs > 0) &&
|
||||||
|
(!filter.output || def.outputs > 0)
|
||||||
}
|
}
|
||||||
function refreshTypeList(opts) {
|
function refreshTypeList(opts) {
|
||||||
var i;
|
var i;
|
||||||
@ -370,7 +382,7 @@ RED.typeSearch = (function() {
|
|||||||
var items = [];
|
var items = [];
|
||||||
RED.nodes.registry.getNodeTypes().forEach(function(t) {
|
RED.nodes.registry.getNodeTypes().forEach(function(t) {
|
||||||
var def = RED.nodes.getType(t);
|
var def = RED.nodes.getType(t);
|
||||||
if (def.category !== 'config' && t !== 'unknown' && t !== 'tab') {
|
if (def.set?.enabled !== false && def.category !== 'config' && t !== 'unknown' && t !== 'tab') {
|
||||||
items.push({type:t,def: def, label:getTypeLabel(t,def)});
|
items.push({type:t,def: def, label:getTypeLabel(t,def)});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -230,7 +230,7 @@ RED.utils = (function() {
|
|||||||
var pinnedPaths = {};
|
var pinnedPaths = {};
|
||||||
var formattedPaths = {};
|
var formattedPaths = {};
|
||||||
|
|
||||||
function addMessageControls(obj,sourceId,key,msg,rootPath,strippedKey,extraTools) {
|
function addMessageControls(obj,sourceId,key,msg,rootPath,strippedKey,extraTools,enablePinning) {
|
||||||
if (!pinnedPaths.hasOwnProperty(sourceId)) {
|
if (!pinnedPaths.hasOwnProperty(sourceId)) {
|
||||||
pinnedPaths[sourceId] = {}
|
pinnedPaths[sourceId] = {}
|
||||||
}
|
}
|
||||||
@ -250,7 +250,7 @@ RED.utils = (function() {
|
|||||||
RED.clipboard.copyText(msg,copyPayload,"clipboard.copyMessageValue");
|
RED.clipboard.copyText(msg,copyPayload,"clipboard.copyMessageValue");
|
||||||
})
|
})
|
||||||
RED.popover.tooltip(copyPayload,RED._("node-red:debug.sidebar.copyPayload"));
|
RED.popover.tooltip(copyPayload,RED._("node-red:debug.sidebar.copyPayload"));
|
||||||
if (strippedKey !== undefined && strippedKey !== '') {
|
if (enablePinning && strippedKey !== undefined && strippedKey !== '') {
|
||||||
var isPinned = pinnedPaths[sourceId].hasOwnProperty(strippedKey);
|
var isPinned = pinnedPaths[sourceId].hasOwnProperty(strippedKey);
|
||||||
|
|
||||||
var pinPath = $('<button class="red-ui-button red-ui-button-small red-ui-debug-msg-tools-pin"><i class="fa fa-map-pin"></i></button>').appendTo(tools).on("click", function(e) {
|
var pinPath = $('<button class="red-ui-button red-ui-button-small red-ui-debug-msg-tools-pin"><i class="fa fa-map-pin"></i></button>').appendTo(tools).on("click", function(e) {
|
||||||
@ -281,13 +281,16 @@ RED.utils = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function checkExpanded(strippedKey,expandPaths,minRange,maxRange) {
|
function checkExpanded(strippedKey, expandPaths, { minRange, maxRange, expandLeafNodes }) {
|
||||||
if (expandPaths && expandPaths.length > 0) {
|
if (expandPaths && expandPaths.length > 0) {
|
||||||
if (strippedKey === '' && minRange === undefined) {
|
if (strippedKey === '' && minRange === undefined) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
for (var i=0;i<expandPaths.length;i++) {
|
for (var i=0;i<expandPaths.length;i++) {
|
||||||
var p = expandPaths[i];
|
var p = expandPaths[i];
|
||||||
|
if (expandLeafNodes && p === strippedKey) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
if (p.indexOf(strippedKey) === 0 && (p[strippedKey.length] === "." || p[strippedKey.length] === "[") ) {
|
if (p.indexOf(strippedKey) === 0 && (p[strippedKey.length] === "." || p[strippedKey.length] === "[") ) {
|
||||||
|
|
||||||
if (minRange !== undefined && p[strippedKey.length] === "[") {
|
if (minRange !== undefined && p[strippedKey.length] === "[") {
|
||||||
@ -394,6 +397,8 @@ RED.utils = (function() {
|
|||||||
var sourceId = options.sourceId;
|
var sourceId = options.sourceId;
|
||||||
var rootPath = options.rootPath;
|
var rootPath = options.rootPath;
|
||||||
var expandPaths = options.expandPaths;
|
var expandPaths = options.expandPaths;
|
||||||
|
const enablePinning = options.enablePinning
|
||||||
|
const expandLeafNodes = options.expandLeafNodes;
|
||||||
var ontoggle = options.ontoggle;
|
var ontoggle = options.ontoggle;
|
||||||
var exposeApi = options.exposeApi;
|
var exposeApi = options.exposeApi;
|
||||||
var tools = options.tools;
|
var tools = options.tools;
|
||||||
@ -416,11 +421,11 @@ RED.utils = (function() {
|
|||||||
}
|
}
|
||||||
header = $('<span class="red-ui-debug-msg-row"></span>').appendTo(element);
|
header = $('<span class="red-ui-debug-msg-row"></span>').appendTo(element);
|
||||||
if (sourceId) {
|
if (sourceId) {
|
||||||
addMessageControls(header,sourceId,path,obj,rootPath,strippedKey,tools);
|
addMessageControls(header,sourceId,path,obj,rootPath,strippedKey,tools, enablePinning);
|
||||||
}
|
}
|
||||||
if (!key) {
|
if (!key) {
|
||||||
element.addClass("red-ui-debug-msg-top-level");
|
element.addClass("red-ui-debug-msg-top-level");
|
||||||
if (sourceId) {
|
if (sourceId && !expandPaths) {
|
||||||
var pinned = pinnedPaths[sourceId];
|
var pinned = pinnedPaths[sourceId];
|
||||||
expandPaths = [];
|
expandPaths = [];
|
||||||
if (pinned) {
|
if (pinned) {
|
||||||
@ -476,7 +481,7 @@ RED.utils = (function() {
|
|||||||
$('<span class="red-ui-debug-msg-type-meta red-ui-debug-msg-object-type-header"></span>').text(typeHint||'string').appendTo(header);
|
$('<span class="red-ui-debug-msg-type-meta red-ui-debug-msg-object-type-header"></span>').text(typeHint||'string').appendTo(header);
|
||||||
var row = $('<div class="red-ui-debug-msg-object-entry collapsed"></div>').appendTo(element);
|
var row = $('<div class="red-ui-debug-msg-object-entry collapsed"></div>').appendTo(element);
|
||||||
$('<pre class="red-ui-debug-msg-type-string"></pre>').text(obj).appendTo(row);
|
$('<pre class="red-ui-debug-msg-type-string"></pre>').text(obj).appendTo(row);
|
||||||
},function(state) {if (ontoggle) { ontoggle(path,state);}}, checkExpanded(strippedKey,expandPaths));
|
},function(state) {if (ontoggle) { ontoggle(path,state);}}, checkExpanded(strippedKey, expandPaths, { expandLeafNodes }));
|
||||||
}
|
}
|
||||||
e = $('<span class="red-ui-debug-msg-type-string red-ui-debug-msg-object-header"></span>').html('"'+formatString(sanitize(obj))+'"').appendTo(entryObj);
|
e = $('<span class="red-ui-debug-msg-type-string red-ui-debug-msg-object-header"></span>').html('"'+formatString(sanitize(obj))+'"').appendTo(entryObj);
|
||||||
if (/^#[0-9a-f]{6}$/i.test(obj)) {
|
if (/^#[0-9a-f]{6}$/i.test(obj)) {
|
||||||
@ -592,14 +597,16 @@ RED.utils = (function() {
|
|||||||
typeHint: type==='buffer'?'hex':false,
|
typeHint: type==='buffer'?'hex':false,
|
||||||
hideKey: false,
|
hideKey: false,
|
||||||
path: path+"["+i+"]",
|
path: path+"["+i+"]",
|
||||||
sourceId: sourceId,
|
sourceId,
|
||||||
rootPath: rootPath,
|
rootPath,
|
||||||
expandPaths: expandPaths,
|
expandPaths,
|
||||||
ontoggle: ontoggle,
|
expandLeafNodes,
|
||||||
exposeApi: exposeApi,
|
ontoggle,
|
||||||
|
exposeApi,
|
||||||
// tools: tools // Do not pass tools down as we
|
// tools: tools // Do not pass tools down as we
|
||||||
// keep them attached to the top-level header
|
// keep them attached to the top-level header
|
||||||
nodeSelector: options.nodeSelector,
|
nodeSelector: options.nodeSelector,
|
||||||
|
enablePinning
|
||||||
}
|
}
|
||||||
).appendTo(row);
|
).appendTo(row);
|
||||||
}
|
}
|
||||||
@ -623,21 +630,23 @@ RED.utils = (function() {
|
|||||||
typeHint: type==='buffer'?'hex':false,
|
typeHint: type==='buffer'?'hex':false,
|
||||||
hideKey: false,
|
hideKey: false,
|
||||||
path: path+"["+i+"]",
|
path: path+"["+i+"]",
|
||||||
sourceId: sourceId,
|
sourceId,
|
||||||
rootPath: rootPath,
|
rootPath,
|
||||||
expandPaths: expandPaths,
|
expandPaths,
|
||||||
ontoggle: ontoggle,
|
expandLeafNodes,
|
||||||
exposeApi: exposeApi,
|
ontoggle,
|
||||||
|
exposeApi,
|
||||||
// tools: tools // Do not pass tools down as we
|
// tools: tools // Do not pass tools down as we
|
||||||
// keep them attached to the top-level header
|
// keep them attached to the top-level header
|
||||||
nodeSelector: options.nodeSelector,
|
nodeSelector: options.nodeSelector,
|
||||||
|
enablePinning
|
||||||
}
|
}
|
||||||
).appendTo(row);
|
).appendTo(row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})(),
|
})(),
|
||||||
(function() { var path = path+"["+i+"]"; return function(state) {if (ontoggle) { ontoggle(path,state);}}})(),
|
(function() { var path = path+"["+i+"]"; return function(state) {if (ontoggle) { ontoggle(path,state);}}})(),
|
||||||
checkExpanded(strippedKey,expandPaths,minRange,Math.min(fullLength-1,(minRange+9))));
|
checkExpanded(strippedKey,expandPaths,{ minRange, maxRange: Math.min(fullLength-1,(minRange+9)), expandLeafNodes}));
|
||||||
$('<span class="red-ui-debug-msg-object-key"></span>').html("["+minRange+" … "+Math.min(fullLength-1,(minRange+9))+"]").appendTo(header);
|
$('<span class="red-ui-debug-msg-object-key"></span>').html("["+minRange+" … "+Math.min(fullLength-1,(minRange+9))+"]").appendTo(header);
|
||||||
}
|
}
|
||||||
if (fullLength < originalLength) {
|
if (fullLength < originalLength) {
|
||||||
@ -646,7 +655,7 @@ RED.utils = (function() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
function(state) {if (ontoggle) { ontoggle(path,state);}},
|
function(state) {if (ontoggle) { ontoggle(path,state);}},
|
||||||
checkExpanded(strippedKey,expandPaths));
|
checkExpanded(strippedKey, expandPaths, { expandLeafNodes }));
|
||||||
}
|
}
|
||||||
} else if (typeof obj === 'object') {
|
} else if (typeof obj === 'object') {
|
||||||
element.addClass('collapsed');
|
element.addClass('collapsed');
|
||||||
@ -680,14 +689,16 @@ RED.utils = (function() {
|
|||||||
typeHint: false,
|
typeHint: false,
|
||||||
hideKey: false,
|
hideKey: false,
|
||||||
path: newPath,
|
path: newPath,
|
||||||
sourceId: sourceId,
|
sourceId,
|
||||||
rootPath: rootPath,
|
rootPath,
|
||||||
expandPaths: expandPaths,
|
expandPaths,
|
||||||
ontoggle: ontoggle,
|
expandLeafNodes,
|
||||||
exposeApi: exposeApi,
|
ontoggle,
|
||||||
|
exposeApi,
|
||||||
// tools: tools // Do not pass tools down as we
|
// tools: tools // Do not pass tools down as we
|
||||||
// keep them attached to the top-level header
|
// keep them attached to the top-level header
|
||||||
nodeSelector: options.nodeSelector,
|
nodeSelector: options.nodeSelector,
|
||||||
|
enablePinning
|
||||||
}
|
}
|
||||||
).appendTo(row);
|
).appendTo(row);
|
||||||
}
|
}
|
||||||
@ -696,7 +707,7 @@ RED.utils = (function() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
function(state) {if (ontoggle) { ontoggle(path,state);}},
|
function(state) {if (ontoggle) { ontoggle(path,state);}},
|
||||||
checkExpanded(strippedKey,expandPaths));
|
checkExpanded(strippedKey, expandPaths, { expandLeafNodes }));
|
||||||
}
|
}
|
||||||
if (key) {
|
if (key) {
|
||||||
$('<span class="red-ui-debug-msg-type-meta"></span>').text(type).appendTo(entryObj);
|
$('<span class="red-ui-debug-msg-type-meta"></span>').text(type).appendTo(entryObj);
|
||||||
|
@ -11,7 +11,7 @@ RED.view.annotations = (function() {
|
|||||||
}
|
}
|
||||||
let badgeRDX = 0;
|
let badgeRDX = 0;
|
||||||
let badgeLDX = 0;
|
let badgeLDX = 0;
|
||||||
|
const scale = RED.view.scale()
|
||||||
for (let i=0,l=evt.el.__annotations__.length;i<l;i++) {
|
for (let i=0,l=evt.el.__annotations__.length;i<l;i++) {
|
||||||
const annotation = evt.el.__annotations__[i];
|
const annotation = evt.el.__annotations__[i];
|
||||||
if (annotations.hasOwnProperty(annotation.id)) {
|
if (annotations.hasOwnProperty(annotation.id)) {
|
||||||
@ -42,15 +42,17 @@ RED.view.annotations = (function() {
|
|||||||
}
|
}
|
||||||
if (isBadge) {
|
if (isBadge) {
|
||||||
if (showAnnotation) {
|
if (showAnnotation) {
|
||||||
const rect = annotation.element.getBoundingClientRect();
|
// getBoundingClientRect is in real-world scale so needs to be adjusted according to
|
||||||
|
// the current scale factor
|
||||||
|
const rectWidth = annotation.element.getBoundingClientRect().width / scale;
|
||||||
let annotationX
|
let annotationX
|
||||||
if (!opts.align || opts.align === 'right') {
|
if (!opts.align || opts.align === 'right') {
|
||||||
annotationX = evt.node.w - 3 - badgeRDX - rect.width
|
annotationX = evt.node.w - 3 - badgeRDX - rectWidth
|
||||||
badgeRDX += rect.width + 4;
|
badgeRDX += rectWidth + 4;
|
||||||
|
|
||||||
} else if (opts.align === 'left') {
|
} else if (opts.align === 'left') {
|
||||||
annotationX = 3 + badgeLDX
|
annotationX = 3 + badgeLDX
|
||||||
badgeLDX += rect.width + 4;
|
badgeLDX += rectWidth + 4;
|
||||||
}
|
}
|
||||||
annotation.element.setAttribute("transform", "translate("+annotationX+", -8)");
|
annotation.element.setAttribute("transform", "translate("+annotationX+", -8)");
|
||||||
}
|
}
|
||||||
|
@ -1102,18 +1102,27 @@ RED.view.tools = (function() {
|
|||||||
const paletteLabel = RED.utils.getPaletteLabel(n.type, nodeDef)
|
const paletteLabel = RED.utils.getPaletteLabel(n.type, nodeDef)
|
||||||
const defaultNodeNameRE = new RegExp('^'+paletteLabel.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')+' (\\d+)$')
|
const defaultNodeNameRE = new RegExp('^'+paletteLabel.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')+' (\\d+)$')
|
||||||
if (!typeIndex.hasOwnProperty(n.type)) {
|
if (!typeIndex.hasOwnProperty(n.type)) {
|
||||||
const existingNodes = RED.nodes.filterNodes({type: n.type})
|
const existingNodes = RED.nodes.filterNodes({ type: n.type });
|
||||||
let maxNameNumber = 0;
|
const existingIds = existingNodes.reduce((ids, node) => {
|
||||||
existingNodes.forEach(n => {
|
let match = defaultNodeNameRE.exec(node.name);
|
||||||
let match = defaultNodeNameRE.exec(n.name)
|
|
||||||
if (match) {
|
if (match) {
|
||||||
let nodeNumber = parseInt(match[1])
|
const nodeNumber = parseInt(match[1], 10);
|
||||||
if (nodeNumber > maxNameNumber) {
|
if (!ids.includes(nodeNumber)) {
|
||||||
maxNameNumber = nodeNumber
|
ids.push(nodeNumber);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
return ids;
|
||||||
typeIndex[n.type] = maxNameNumber + 1
|
}, []).sort((a, b) => a - b);
|
||||||
|
|
||||||
|
let availableNameNumber = 1;
|
||||||
|
for (let i = 0; i < existingIds.length; i++) {
|
||||||
|
if (existingIds[i] !== availableNameNumber) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
availableNameNumber++;
|
||||||
|
}
|
||||||
|
|
||||||
|
typeIndex[n.type] = availableNameNumber;
|
||||||
}
|
}
|
||||||
if ((options.renameBlank && n.name === '') || (options.renameClash && defaultNodeNameRE.test(n.name))) {
|
if ((options.renameBlank && n.name === '') || (options.renameClash && defaultNodeNameRE.test(n.name))) {
|
||||||
if (generateHistory) {
|
if (generateHistory) {
|
||||||
@ -1145,11 +1154,11 @@ RED.view.tools = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addJunctionsToWires(wires) {
|
function addJunctionsToWires(options = {}) {
|
||||||
if (RED.workspaces.isLocked()) {
|
if (RED.workspaces.isLocked()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let wiresToSplit = wires || (RED.view.selection().links && RED.view.selection().links.filter(e => !e.link));
|
let wiresToSplit = options.wires || (RED.view.selection().links && RED.view.selection().links.filter(e => !e.link));
|
||||||
if (!wiresToSplit) {
|
if (!wiresToSplit) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1197,6 +1206,10 @@ RED.view.tools = (function() {
|
|||||||
if (links.length === 0) {
|
if (links.length === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (addedJunctions.length === 0 && Object.hasOwn(options, 'x') && Object.hasOwn(options, 'y')) {
|
||||||
|
junction.x = options.x
|
||||||
|
junction.y = options.y
|
||||||
|
} else {
|
||||||
let pointCount = 0
|
let pointCount = 0
|
||||||
links.forEach(function(l) {
|
links.forEach(function(l) {
|
||||||
if (l._sliceLocation) {
|
if (l._sliceLocation) {
|
||||||
@ -1212,6 +1225,7 @@ RED.view.tools = (function() {
|
|||||||
})
|
})
|
||||||
junction.x = Math.round(junction.x/pointCount)
|
junction.x = Math.round(junction.x/pointCount)
|
||||||
junction.y = Math.round(junction.y/pointCount)
|
junction.y = Math.round(junction.y/pointCount)
|
||||||
|
}
|
||||||
if (RED.view.snapGrid) {
|
if (RED.view.snapGrid) {
|
||||||
let gridSize = RED.view.gridSize()
|
let gridSize = RED.view.gridSize()
|
||||||
junction.x = (gridSize*Math.round(junction.x/gridSize));
|
junction.x = (gridSize*Math.round(junction.x/gridSize));
|
||||||
@ -1401,7 +1415,7 @@ RED.view.tools = (function() {
|
|||||||
RED.actions.add("core:wire-multiple-to-node", function() { wireMultipleToNode() })
|
RED.actions.add("core:wire-multiple-to-node", function() { wireMultipleToNode() })
|
||||||
|
|
||||||
RED.actions.add("core:split-wire-with-link-nodes", function () { splitWiresWithLinkNodes() });
|
RED.actions.add("core:split-wire-with-link-nodes", function () { splitWiresWithLinkNodes() });
|
||||||
RED.actions.add("core:split-wires-with-junctions", function () { addJunctionsToWires() });
|
RED.actions.add("core:split-wires-with-junctions", function (options) { addJunctionsToWires(options) });
|
||||||
|
|
||||||
RED.actions.add("core:generate-node-names", generateNodeNames )
|
RED.actions.add("core:generate-node-names", generateNodeNames )
|
||||||
|
|
||||||
|
@ -288,7 +288,7 @@ RED.view = (function() {
|
|||||||
}
|
}
|
||||||
selectedLinks.clearUnselected()
|
selectedLinks.clearUnselected()
|
||||||
},
|
},
|
||||||
length: () => groups.length,
|
length: () => groups.size,
|
||||||
forEach: (func) => { groups.forEach(func) },
|
forEach: (func) => { groups.forEach(func) },
|
||||||
toArray: () => [...groups],
|
toArray: () => [...groups],
|
||||||
clear: function () {
|
clear: function () {
|
||||||
@ -321,8 +321,8 @@ RED.view = (function() {
|
|||||||
evt.stopPropagation()
|
evt.stopPropagation()
|
||||||
RED.contextMenu.show({
|
RED.contextMenu.show({
|
||||||
type: 'workspace',
|
type: 'workspace',
|
||||||
x:evt.clientX-5,
|
x: evt.clientX,
|
||||||
y:evt.clientY-5
|
y: evt.clientY
|
||||||
})
|
})
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
@ -1209,7 +1209,10 @@ RED.view = (function() {
|
|||||||
lasso = null;
|
lasso = null;
|
||||||
}
|
}
|
||||||
if (d3.event.touches || d3.event.button === 0) {
|
if (d3.event.touches || d3.event.button === 0) {
|
||||||
if ((mouse_mode === 0 || mouse_mode === RED.state.QUICK_JOINING) && isControlPressed(d3.event) && !(d3.event.altKey || d3.event.shiftKey)) {
|
if (
|
||||||
|
(mouse_mode === 0 && isControlPressed(d3.event) && !(d3.event.altKey || d3.event.shiftKey)) ||
|
||||||
|
mouse_mode === RED.state.QUICK_JOINING
|
||||||
|
) {
|
||||||
// Trigger quick add dialog
|
// Trigger quick add dialog
|
||||||
d3.event.stopPropagation();
|
d3.event.stopPropagation();
|
||||||
clearSelection();
|
clearSelection();
|
||||||
@ -1262,11 +1265,6 @@ RED.view = (function() {
|
|||||||
var targetGroup = options.group;
|
var targetGroup = options.group;
|
||||||
var touchTrigger = options.touchTrigger;
|
var touchTrigger = options.touchTrigger;
|
||||||
|
|
||||||
if (targetGroup) {
|
|
||||||
selectedGroups.add(targetGroup,false);
|
|
||||||
RED.view.redraw();
|
|
||||||
}
|
|
||||||
|
|
||||||
// `point` is the place in the workspace the mouse has clicked.
|
// `point` is the place in the workspace the mouse has clicked.
|
||||||
// This takes into account scrolling and scaling of the workspace.
|
// This takes into account scrolling and scaling of the workspace.
|
||||||
var ox = point[0];
|
var ox = point[0];
|
||||||
@ -1285,7 +1283,6 @@ RED.view = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var mainPos = $("#red-ui-main-container").position();
|
var mainPos = $("#red-ui-main-container").position();
|
||||||
|
|
||||||
if (mouse_mode !== RED.state.QUICK_JOINING) {
|
if (mouse_mode !== RED.state.QUICK_JOINING) {
|
||||||
mouse_mode = RED.state.QUICK_JOINING;
|
mouse_mode = RED.state.QUICK_JOINING;
|
||||||
$(window).on('keyup',disableQuickJoinEventHandler);
|
$(window).on('keyup',disableQuickJoinEventHandler);
|
||||||
@ -1589,9 +1586,6 @@ RED.view = (function() {
|
|||||||
// auto select dropped node - so info shows (if visible)
|
// auto select dropped node - so info shows (if visible)
|
||||||
clearSelection();
|
clearSelection();
|
||||||
nn.selected = true;
|
nn.selected = true;
|
||||||
if (targetGroup) {
|
|
||||||
selectedGroups.add(targetGroup,false);
|
|
||||||
}
|
|
||||||
movingSet.add(nn);
|
movingSet.add(nn);
|
||||||
updateActiveNodes();
|
updateActiveNodes();
|
||||||
updateSelection();
|
updateSelection();
|
||||||
@ -2176,17 +2170,22 @@ RED.view = (function() {
|
|||||||
n.n.moved = true;
|
n.n.moved = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// If a node has moved and ends up being spliced into a link, keep
|
||||||
// Check to see if we need to splice a link
|
// track of which historyEvent to add the splice info to
|
||||||
|
let targetSpliceEvent = null
|
||||||
if (moveEvent.nodes.length > 0) {
|
if (moveEvent.nodes.length > 0) {
|
||||||
historyEvent.events.push(moveEvent)
|
historyEvent.events.push(moveEvent)
|
||||||
if (activeSpliceLink) {
|
targetSpliceEvent = moveEvent
|
||||||
var linkToSplice = d3.select(activeSpliceLink).data()[0];
|
|
||||||
spliceLink(linkToSplice, movingSet.get(0).n, moveEvent)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (moveAndChangedGroupEvent.nodes.length > 0) {
|
if (moveAndChangedGroupEvent.nodes.length > 0) {
|
||||||
historyEvent.events.push(moveAndChangedGroupEvent)
|
historyEvent.events.push(moveAndChangedGroupEvent)
|
||||||
|
targetSpliceEvent = moveAndChangedGroupEvent
|
||||||
|
}
|
||||||
|
// activeSpliceLink will only be set if the movingSet has a single
|
||||||
|
// node that is able to splice.
|
||||||
|
if (targetSpliceEvent && activeSpliceLink) {
|
||||||
|
var linkToSplice = d3.select(activeSpliceLink).data()[0];
|
||||||
|
spliceLink(linkToSplice, movingSet.get(0).n, targetSpliceEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only continue if something has moved
|
// Only continue if something has moved
|
||||||
@ -2687,22 +2686,21 @@ RED.view = (function() {
|
|||||||
addToRemovedLinks(reconnectResult.removedLinks)
|
addToRemovedLinks(reconnectResult.removedLinks)
|
||||||
}
|
}
|
||||||
|
|
||||||
var startDirty = RED.nodes.dirty();
|
const startDirty = RED.nodes.dirty();
|
||||||
var startChanged = false;
|
let movingSelectedGroups = [];
|
||||||
var selectedGroups = [];
|
|
||||||
if (movingSet.length() > 0) {
|
if (movingSet.length() > 0) {
|
||||||
|
|
||||||
for (var i=0;i<movingSet.length();i++) {
|
for (var i=0;i<movingSet.length();i++) {
|
||||||
node = movingSet.get(i).n;
|
node = movingSet.get(i).n;
|
||||||
if (node.type === "group") {
|
if (node.type === "group") {
|
||||||
selectedGroups.push(node);
|
movingSelectedGroups.push(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Make sure we have identified all groups about to be deleted
|
// Make sure we have identified all groups about to be deleted
|
||||||
for (i=0;i<selectedGroups.length;i++) {
|
for (i=0;i<movingSelectedGroups.length;i++) {
|
||||||
selectedGroups[i].nodes.forEach(function(n) {
|
movingSelectedGroups[i].nodes.forEach(function(n) {
|
||||||
if (n.type === "group" && selectedGroups.indexOf(n) === -1) {
|
if (n.type === "group" && movingSelectedGroups.indexOf(n) === -1) {
|
||||||
selectedGroups.push(n);
|
movingSelectedGroups.push(n);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -2719,7 +2717,7 @@ RED.view = (function() {
|
|||||||
addToRemovedLinks(removedEntities.links);
|
addToRemovedLinks(removedEntities.links);
|
||||||
if (node.g) {
|
if (node.g) {
|
||||||
var group = RED.nodes.group(node.g);
|
var group = RED.nodes.group(node.g);
|
||||||
if (selectedGroups.indexOf(group) === -1) {
|
if (movingSelectedGroups.indexOf(group) === -1) {
|
||||||
// Don't use RED.group.removeFromGroup as that emits
|
// Don't use RED.group.removeFromGroup as that emits
|
||||||
// a change event on the node - but we're deleting it
|
// a change event on the node - but we're deleting it
|
||||||
var index = group.nodes.indexOf(node);
|
var index = group.nodes.indexOf(node);
|
||||||
@ -2733,7 +2731,7 @@ RED.view = (function() {
|
|||||||
removedLinks = removedLinks.concat(result.links);
|
removedLinks = removedLinks.concat(result.links);
|
||||||
if (node.g) {
|
if (node.g) {
|
||||||
var group = RED.nodes.group(node.g);
|
var group = RED.nodes.group(node.g);
|
||||||
if (selectedGroups.indexOf(group) === -1) {
|
if (movingSelectedGroups.indexOf(group) === -1) {
|
||||||
// Don't use RED.group.removeFromGroup as that emits
|
// Don't use RED.group.removeFromGroup as that emits
|
||||||
// a change event on the node - but we're deleting it
|
// a change event on the node - but we're deleting it
|
||||||
var index = group.nodes.indexOf(node);
|
var index = group.nodes.indexOf(node);
|
||||||
@ -2755,8 +2753,8 @@ RED.view = (function() {
|
|||||||
|
|
||||||
// Groups must be removed in the right order - from inner-most
|
// Groups must be removed in the right order - from inner-most
|
||||||
// to outermost.
|
// to outermost.
|
||||||
for (i = selectedGroups.length-1; i>=0; i--) {
|
for (i = movingSelectedGroups.length-1; i>=0; i--) {
|
||||||
var g = selectedGroups[i];
|
var g = movingSelectedGroups[i];
|
||||||
removedGroups.push(g);
|
removedGroups.push(g);
|
||||||
RED.nodes.removeGroup(g);
|
RED.nodes.removeGroup(g);
|
||||||
}
|
}
|
||||||
@ -3057,8 +3055,8 @@ RED.view = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function disableQuickJoinEventHandler(evt) {
|
function disableQuickJoinEventHandler(evt) {
|
||||||
// Check for ctrl (all browsers), "Meta" (Chrome/FF), keyCode 91 (Safari)
|
// Check for ctrl (all browsers), "Meta" (Chrome/FF), keyCode 91 (Safari), or Escape
|
||||||
if (evt.keyCode === 17 || evt.key === "Meta" || evt.keyCode === 91) {
|
if (evt.keyCode === 17 || evt.key === "Meta" || evt.keyCode === 91 || evt.keyCode === 27) {
|
||||||
resetMouseVars();
|
resetMouseVars();
|
||||||
hideDragLines();
|
hideDragLines();
|
||||||
redraw();
|
redraw();
|
||||||
@ -5173,8 +5171,8 @@ RED.view = (function() {
|
|||||||
var delta = Infinity;
|
var delta = Infinity;
|
||||||
for (var i = 0; i < lineLength; i++) {
|
for (var i = 0; i < lineLength; i++) {
|
||||||
var linePos = pathLine.getPointAtLength(i);
|
var linePos = pathLine.getPointAtLength(i);
|
||||||
var posDeltaX = Math.abs(linePos.x-d3.event.offsetX)
|
var posDeltaX = Math.abs(linePos.x-(d3.event.offsetX / scaleFactor))
|
||||||
var posDeltaY = Math.abs(linePos.y-d3.event.offsetY)
|
var posDeltaY = Math.abs(linePos.y-(d3.event.offsetY / scaleFactor))
|
||||||
var posDelta = posDeltaX*posDeltaX + posDeltaY*posDeltaY
|
var posDelta = posDeltaX*posDeltaX + posDeltaY*posDeltaY
|
||||||
if (posDelta < delta) {
|
if (posDelta < delta) {
|
||||||
pos = linePos
|
pos = linePos
|
||||||
|
@ -920,6 +920,17 @@ RED.workspaces = (function() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
refresh: function() {
|
refresh: function() {
|
||||||
|
var workspace = RED.nodes.workspace(RED.workspaces.active());
|
||||||
|
if (workspace) {
|
||||||
|
document.title = `${documentTitle} : ${workspace.label}`;
|
||||||
|
} else {
|
||||||
|
var subflow = RED.nodes.subflow(RED.workspaces.active());
|
||||||
|
if (subflow) {
|
||||||
|
document.title = `${documentTitle} : ${subflow.name}`;
|
||||||
|
} else {
|
||||||
|
document.title = documentTitle
|
||||||
|
}
|
||||||
|
}
|
||||||
RED.nodes.eachWorkspace(function(ws) {
|
RED.nodes.eachWorkspace(function(ws) {
|
||||||
workspace_tabs.renameTab(ws.id,ws.label);
|
workspace_tabs.renameTab(ws.id,ws.label);
|
||||||
$("#red-ui-tab-"+(ws.id.replace(".","-"))).attr("flowname",ws.label)
|
$("#red-ui-tab-"+(ws.id.replace(".","-"))).attr("flowname",ws.label)
|
||||||
|
@ -168,6 +168,37 @@ RED.user = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (data.prompts) {
|
||||||
|
if (data.loginMessage) {
|
||||||
|
const sessionMessages = $("<div/>",{class:"form-row",style:"text-align: center"}).appendTo("#node-dialog-login-fields");
|
||||||
|
$('<div>').text(data.loginMessage).appendTo(sessionMessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
for (;i<data.prompts.length;i++) {
|
||||||
|
var field = data.prompts[i];
|
||||||
|
var row = $("<div/>",{class:"form-row",style:"text-align: center"}).appendTo("#node-dialog-login-fields");
|
||||||
|
var loginButton = $('<a href="#" class="red-ui-button"></a>',{style: "padding: 10px"}).appendTo(row).on("click", function() {
|
||||||
|
document.location = field.url;
|
||||||
|
});
|
||||||
|
if (field.image) {
|
||||||
|
$("<img>",{src:field.image}).appendTo(loginButton);
|
||||||
|
} else if (field.label) {
|
||||||
|
var label = $('<span></span>').text(field.label);
|
||||||
|
if (field.icon) {
|
||||||
|
$('<i></i>',{class: "fa fa-2x "+field.icon, style:"vertical-align: middle"}).appendTo(loginButton);
|
||||||
|
label.css({
|
||||||
|
"verticalAlign":"middle",
|
||||||
|
"marginLeft":"8px"
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
label.appendTo(loginButton);
|
||||||
|
}
|
||||||
|
loginButton.button();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (opts.cancelable) {
|
if (opts.cancelable) {
|
||||||
$("#node-dialog-login-cancel").button().on("click", function( event ) {
|
$("#node-dialog-login-cancel").button().on("click", function( event ) {
|
||||||
@ -320,10 +351,10 @@ RED.user = (function() {
|
|||||||
userIcon.css({
|
userIcon.css({
|
||||||
backgroundImage: "url("+user.image+")",
|
backgroundImage: "url("+user.image+")",
|
||||||
})
|
})
|
||||||
} else if (user.anonymous) {
|
} else if (user.anonymous || (!user.username && !user.email)) {
|
||||||
$('<i class="fa fa-user"></i>').appendTo(userIcon);
|
$('<i class="fa fa-user"></i>').appendTo(userIcon);
|
||||||
} else {
|
} else {
|
||||||
$('<span>').text(user.username.substring(0,2)).appendTo(userIcon);
|
$('<span>').text((user.username || user.email).substring(0,2)).appendTo(userIcon);
|
||||||
}
|
}
|
||||||
if (user.profileColor !== undefined) {
|
if (user.profileColor !== undefined) {
|
||||||
userIcon.addClass('red-ui-user-profile-color-' + user.profileColor)
|
userIcon.addClass('red-ui-user-profile-color-' + user.profileColor)
|
||||||
|
@ -2,4 +2,15 @@
|
|||||||
&.red-ui-popover-panel {
|
&.red-ui-popover-panel {
|
||||||
border-top: none;
|
border-top: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
.red-ui-autoComplete-completion {
|
||||||
|
font-family: var(--red-ui-monospace-font);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-grow: 1;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
direction: rtl;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
@ -148,7 +148,7 @@ module.exports = function(RED) {
|
|||||||
var st = (typeof output === 'string') ? output : util.inspect(output);
|
var st = (typeof output === 'string') ? output : util.inspect(output);
|
||||||
var fill = "grey";
|
var fill = "grey";
|
||||||
var shape = "dot";
|
var shape = "dot";
|
||||||
if (typeof output === 'object' && hasOwnProperty.call(output, "fill") && hasOwnProperty.call(output, "shape") && hasOwnProperty.call(output, "text")) {
|
if (typeof output === 'object' && output?.fill && output?.shape && output?.text) {
|
||||||
fill = output.fill;
|
fill = output.fill;
|
||||||
shape = output.shape;
|
shape = output.shape;
|
||||||
st = output.text;
|
st = output.text;
|
||||||
|
@ -514,6 +514,7 @@ RED.debug = (function() {
|
|||||||
sourceId: sourceNode && sourceNode.id,
|
sourceId: sourceNode && sourceNode.id,
|
||||||
rootPath: path,
|
rootPath: path,
|
||||||
nodeSelector: config.messageSourceClick,
|
nodeSelector: config.messageSourceClick,
|
||||||
|
enablePinning: true
|
||||||
});
|
});
|
||||||
// Do this in a separate step so the element functions aren't stripped
|
// Do this in a separate step so the element functions aren't stripped
|
||||||
debugMessage.appendTo(el);
|
debugMessage.appendTo(el);
|
||||||
|
@ -111,8 +111,6 @@ module.exports = function(RED) {
|
|||||||
throw new Error(RED._("function.error.externalModuleNotAllowed"));
|
throw new Error(RED._("function.error.externalModuleNotAllowed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var functionText = "var results = null;"+
|
var functionText = "var results = null;"+
|
||||||
"results = (async function(msg,__send__,__done__){ "+
|
"results = (async function(msg,__send__,__done__){ "+
|
||||||
"var __msgid__ = msg._msgid;"+
|
"var __msgid__ = msg._msgid;"+
|
||||||
@ -166,7 +164,13 @@ module.exports = function(RED) {
|
|||||||
Buffer:Buffer,
|
Buffer:Buffer,
|
||||||
Date: Date,
|
Date: Date,
|
||||||
RED: {
|
RED: {
|
||||||
util: RED.util
|
util: {
|
||||||
|
...RED.util,
|
||||||
|
getSetting: function (_node, name, _flow) {
|
||||||
|
// Ensure `node` argument is the Function node and do not allow flow to be overridden.
|
||||||
|
return RED.util.getSetting(node, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
__node__: {
|
__node__: {
|
||||||
id: node.id,
|
id: node.id,
|
||||||
|
@ -352,7 +352,9 @@ module.exports = function(RED) {
|
|||||||
if (msgs.length === 0) {
|
if (msgs.length === 0) {
|
||||||
done()
|
done()
|
||||||
} else {
|
} else {
|
||||||
|
setImmediate(() => {
|
||||||
drainMessageGroup(msgs,count,done);
|
drainMessageGroup(msgs,count,done);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -505,7 +507,9 @@ module.exports = function(RED) {
|
|||||||
if (err) {
|
if (err) {
|
||||||
node.error(err,nextMsg);
|
node.error(err,nextMsg);
|
||||||
}
|
}
|
||||||
|
setImmediate(() => {
|
||||||
processMessageQueue()
|
processMessageQueue()
|
||||||
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,7 +253,13 @@ module.exports = function(RED) {
|
|||||||
if (node.allowrate && m.hasOwnProperty("rate") && !isNaN(parseFloat(m.rate))) {
|
if (node.allowrate && m.hasOwnProperty("rate") && !isNaN(parseFloat(m.rate))) {
|
||||||
node.rate = m.rate;
|
node.rate = m.rate;
|
||||||
}
|
}
|
||||||
send(m);
|
if (msg.hasOwnProperty("reset")) {
|
||||||
|
if (msg.hasOwnProperty("flush")) {
|
||||||
|
node.buffer.push({msg: m, send: send, done: done});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { send(m); }
|
||||||
|
|
||||||
node.reportDepth();
|
node.reportDepth();
|
||||||
node.intervalID = setInterval(sendMsgFromBuffer, node.rate);
|
node.intervalID = setInterval(sendMsgFromBuffer, node.rate);
|
||||||
done();
|
done();
|
||||||
@ -285,25 +291,6 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (!msg.hasOwnProperty("reset")) {
|
else if (!msg.hasOwnProperty("reset")) {
|
||||||
if (maxKeptMsgsCount(node) > 0) {
|
|
||||||
if (node.intervalID === -1) {
|
|
||||||
node.send(msg);
|
|
||||||
node.intervalID = setInterval(sendMsgFromBuffer, node.rate);
|
|
||||||
} else {
|
|
||||||
if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate)) && node.rate !== msg.rate) {
|
|
||||||
node.rate = msg.rate;
|
|
||||||
clearInterval(node.intervalID);
|
|
||||||
node.intervalID = setInterval(sendMsgFromBuffer, node.rate);
|
|
||||||
}
|
|
||||||
if (node.buffer.length < _maxKeptMsgsCount) {
|
|
||||||
var m = RED.util.cloneMessage(msg);
|
|
||||||
node.buffer.push({msg: m, send: send, done: done});
|
|
||||||
} else {
|
|
||||||
node.trace("dropped due to buffer overflow. msg._msgid = " + msg._msgid);
|
|
||||||
node.droppedMsgs++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate))) {
|
if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate))) {
|
||||||
node.rate = msg.rate;
|
node.rate = msg.rate;
|
||||||
}
|
}
|
||||||
@ -318,9 +305,9 @@ module.exports = function(RED) {
|
|||||||
else if ( ( (timeSinceLast[0] * SECONDS_TO_NANOS) + timeSinceLast[1] ) > (node.rate * MILLIS_TO_NANOS) ) {
|
else if ( ( (timeSinceLast[0] * SECONDS_TO_NANOS) + timeSinceLast[1] ) > (node.rate * MILLIS_TO_NANOS) ) {
|
||||||
node.lastSent = process.hrtime();
|
node.lastSent = process.hrtime();
|
||||||
send(msg);
|
send(msg);
|
||||||
} else if (node.outputs === 2) {
|
|
||||||
send([null,msg])
|
|
||||||
}
|
}
|
||||||
|
else if (node.outputs === 2) {
|
||||||
|
send([null,msg])
|
||||||
}
|
}
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,14 @@ module.exports = function(RED) {
|
|||||||
this.op2 = n.op2 || "0";
|
this.op2 = n.op2 || "0";
|
||||||
this.op1type = n.op1type || "str";
|
this.op1type = n.op1type || "str";
|
||||||
this.op2type = n.op2type || "str";
|
this.op2type = n.op2type || "str";
|
||||||
|
// If the op1/2type is 'date', then we need to leave op1/2 alone so that
|
||||||
|
// evaluateNodeProperty works as expected.
|
||||||
|
if (this.op1type === 'date' && this.op1 === '1') {
|
||||||
|
this.op1 = ''
|
||||||
|
}
|
||||||
|
if (this.op2type === 'date' && this.op2 === '0') {
|
||||||
|
this.op2 = ''
|
||||||
|
}
|
||||||
this.second = (n.outputs == 2) ? true : false;
|
this.second = (n.outputs == 2) ? true : false;
|
||||||
this.topic = n.topic || "topic";
|
this.topic = n.topic || "topic";
|
||||||
|
|
||||||
@ -193,7 +201,7 @@ module.exports = function(RED) {
|
|||||||
if (node.op2type !== "nul") {
|
if (node.op2type !== "nul") {
|
||||||
var promise = Promise.resolve();
|
var promise = Promise.resolve();
|
||||||
msg2 = RED.util.cloneMessage(msg);
|
msg2 = RED.util.cloneMessage(msg);
|
||||||
if (node.op2type === "flow" || node.op2type === "global") {
|
if (node.op2type === "flow" || node.op2type === "global" || node.op2type === "date") {
|
||||||
promise = new Promise((resolve,reject) => {
|
promise = new Promise((resolve,reject) => {
|
||||||
RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg,(err,value) => {
|
RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg,(err,value) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -213,7 +221,6 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
msg2.payload = node.topics[topic].m2;
|
msg2.payload = node.topics[topic].m2;
|
||||||
if (node.op2type === "date") { msg2.payload = Date.now(); }
|
|
||||||
if (node.second === true) { msgInfo.send([null,msg2]); }
|
if (node.second === true) { msgInfo.send([null,msg2]); }
|
||||||
else { msgInfo.send(msg2); }
|
else { msgInfo.send(msg2); }
|
||||||
}
|
}
|
||||||
|
@ -104,6 +104,7 @@ module.exports = function(RED) {
|
|||||||
if (this.credentials && this.credentials.passphrase) {
|
if (this.credentials && this.credentials.passphrase) {
|
||||||
opts.passphrase = this.credentials.passphrase;
|
opts.passphrase = this.credentials.passphrase;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (this.servername) {
|
if (this.servername) {
|
||||||
opts.servername = this.servername;
|
opts.servername = this.servername;
|
||||||
}
|
}
|
||||||
@ -111,7 +112,6 @@ module.exports = function(RED) {
|
|||||||
opts.ALPNProtocols = [this.alpnprotocol];
|
opts.ALPNProtocols = [this.alpnprotocol];
|
||||||
}
|
}
|
||||||
opts.rejectUnauthorized = this.verifyservercert;
|
opts.rejectUnauthorized = this.verifyservercert;
|
||||||
}
|
|
||||||
return opts;
|
return opts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,9 +158,16 @@ module.exports = function(RED) {
|
|||||||
if(!keys || !keys.length) return null;
|
if(!keys || !keys.length) return null;
|
||||||
keys.forEach(key => {
|
keys.forEach(key => {
|
||||||
let val = srcUserProperties[key];
|
let val = srcUserProperties[key];
|
||||||
if(typeof val == "string") {
|
if(typeof val === "string") {
|
||||||
count++;
|
count++;
|
||||||
_clone[key] = val;
|
_clone[key] = val;
|
||||||
|
} else if (val !== undefined && val !== null) {
|
||||||
|
try {
|
||||||
|
_clone[key] = JSON.stringify(val)
|
||||||
|
count++;
|
||||||
|
} catch (err) {
|
||||||
|
// Silently drop property
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if(count) properties.userProperties = _clone;
|
if(count) properties.userProperties = _clone;
|
||||||
|
@ -367,20 +367,21 @@ module.exports = function(RED) {
|
|||||||
const sendHeadersAlways = node.hdrout === "all"
|
const sendHeadersAlways = node.hdrout === "all"
|
||||||
const sendHeaders = !dontSendHeaders && (sendHeadersOnce || sendHeadersAlways)
|
const sendHeaders = !dontSendHeaders && (sendHeadersOnce || sendHeadersAlways)
|
||||||
const quoteables = [node.sep, node.quo, "\n", "\r"]
|
const quoteables = [node.sep, node.quo, "\n", "\r"]
|
||||||
const templateQuoteables = [',', '"', "\n", "\r"]
|
const templateQuoteables = [node.sep, node.quo, "\n", "\r"]
|
||||||
|
const templateQuoteablesStrict = [',', '"', "\n", "\r"]
|
||||||
let badTemplateWarnOnce = true
|
let badTemplateWarnOnce = true
|
||||||
|
|
||||||
const columnStringToTemplateArray = function (col, sep) {
|
const columnStringToTemplateArray = function (col, sep) {
|
||||||
// NOTE: enforce strict column template parsing in RFC4180 mode
|
// NOTE: enforce strict column template parsing in RFC4180 mode
|
||||||
const parsed = csv.parse(col, { separator: sep, quote: node.quo, outputStyle: 'array', strict: true })
|
const parsed = csv.parse(col, { separator: sep, quote: node.quo, outputStyle: 'array', strict: true })
|
||||||
if (parsed.headers.length > 0) { node.goodtmpl = true } else { node.goodtmpl = false }
|
if (parsed.data?.length === 1) { node.goodtmpl = true } else { node.goodtmpl = false }
|
||||||
return parsed.headers.length ? parsed.headers : null
|
return node.goodtmpl ? parsed.data[0] : null
|
||||||
}
|
}
|
||||||
const templateArrayToColumnString = function (template, keepEmptyColumns) {
|
const templateArrayToColumnString = function (template, keepEmptyColumns, separator = ',', quotables = templateQuoteablesStrict) {
|
||||||
// NOTE: enforce strict column template parsing in RFC4180 mode
|
// NOTE: defaults to strict column template parsing (commas and double quotes)
|
||||||
const parsed = csv.parse('', {headers: template, headersOnly:true, separator: ',', quote: node.quo, outputStyle: 'array', strict: true })
|
const parsed = csv.parse('', {headers: template, headersOnly:true, separator, quote: node.quo, outputStyle: 'array', strict: true })
|
||||||
return keepEmptyColumns
|
return keepEmptyColumns
|
||||||
? parsed.headers.map(e => addQuotes(e || '', { separator: ',', quoteables: templateQuoteables}))
|
? parsed.headers.map(e => addQuotes(e || '', { separator, quoteables: quotables })).join(separator)
|
||||||
: parsed.header // exclues empty columns
|
: parsed.header // exclues empty columns
|
||||||
// TODO: resolve inconsistency between CSV->JSON and JSON->CSV
|
// TODO: resolve inconsistency between CSV->JSON and JSON->CSV
|
||||||
// CSV->JSON: empty columns are excluded
|
// CSV->JSON: empty columns are excluded
|
||||||
@ -447,7 +448,7 @@ module.exports = function(RED) {
|
|||||||
template = Object.keys(inputData[0]) || ['']
|
template = Object.keys(inputData[0]) || ['']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stringBuilder.push(templateArrayToColumnString(template, true))
|
stringBuilder.push(templateArrayToColumnString(template, true, node.sep, templateQuoteables)) // use user set separator for output data.
|
||||||
if (sendHeadersOnce) { node.hdrSent = true }
|
if (sendHeadersOnce) { node.hdrSent = true }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -483,6 +484,7 @@ module.exports = function(RED) {
|
|||||||
node.warn(RED._("csv.errors.obj_csv"))
|
node.warn(RED._("csv.errors.obj_csv"))
|
||||||
badTemplateWarnOnce = false
|
badTemplateWarnOnce = false
|
||||||
}
|
}
|
||||||
|
template = Object.keys(row) || ['']
|
||||||
const rowData = []
|
const rowData = []
|
||||||
for (let header in inputData[0]) {
|
for (let header in inputData[0]) {
|
||||||
if (row.hasOwnProperty(header)) {
|
if (row.hasOwnProperty(header)) {
|
||||||
@ -518,7 +520,7 @@ module.exports = function(RED) {
|
|||||||
|
|
||||||
// join lines, don't forget to add the last new line
|
// join lines, don't forget to add the last new line
|
||||||
msg.payload = stringBuilder.join(node.ret) + node.ret
|
msg.payload = stringBuilder.join(node.ret) + node.ret
|
||||||
msg.columns = templateArrayToColumnString(template)
|
msg.columns = templateArrayToColumnString(template) // always strict commas + double quotes for
|
||||||
if (msg.payload !== '') { send(msg) }
|
if (msg.payload !== '') { send(msg) }
|
||||||
done()
|
done()
|
||||||
}
|
}
|
||||||
@ -615,16 +617,15 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
if (msg.parts.index + 1 === msg.parts.count) {
|
if (msg.parts.index + 1 === msg.parts.count) {
|
||||||
msg.payload = node.store
|
msg.payload = node.store
|
||||||
msg.columns = csvParseResult.header
|
// msg.columns = csvParseResult.header
|
||||||
// msg._mode = 'RFC4180 mode'
|
msg.columns = templateArrayToColumnString(csvParseResult.headers) // always strict commas + double quotes for msg.columns
|
||||||
delete msg.parts
|
delete msg.parts
|
||||||
send(msg)
|
send(msg)
|
||||||
node.store = []
|
node.store = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
msg.columns = csvParseResult.header
|
msg.columns = templateArrayToColumnString(csvParseResult.headers) // always strict commas + double quotes for msg.columns
|
||||||
// msg._mode = 'RFC4180 mode'
|
|
||||||
msg.payload = data
|
msg.payload = data
|
||||||
send(msg); // finally send the array
|
send(msg); // finally send the array
|
||||||
}
|
}
|
||||||
@ -633,7 +634,8 @@ module.exports = function(RED) {
|
|||||||
const len = data.length
|
const len = data.length
|
||||||
for (let row = 0; row < len; row++) {
|
for (let row = 0; row < len; row++) {
|
||||||
const newMessage = RED.util.cloneMessage(msg)
|
const newMessage = RED.util.cloneMessage(msg)
|
||||||
newMessage.columns = csvParseResult.header
|
// newMessage.columns = csvParseResult.header
|
||||||
|
newMessage.columns = templateArrayToColumnString(csvParseResult.headers) // always strict commas + double quotes for msg.columns
|
||||||
newMessage.payload = data[row]
|
newMessage.payload = data[row]
|
||||||
if (!has_parts) {
|
if (!has_parts) {
|
||||||
newMessage.parts = {
|
newMessage.parts = {
|
||||||
|
@ -339,7 +339,7 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
msg.filename = filename;
|
msg.filename = filename;
|
||||||
var lines = Buffer.from([]);
|
const bufferArray = [];
|
||||||
var spare = "";
|
var spare = "";
|
||||||
var count = 0;
|
var count = 0;
|
||||||
var type = "buffer";
|
var type = "buffer";
|
||||||
@ -397,7 +397,7 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
lines = Buffer.concat([lines,chunk]);
|
bufferArray.push(chunk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -413,10 +413,11 @@ module.exports = function(RED) {
|
|||||||
})
|
})
|
||||||
.on('end', function() {
|
.on('end', function() {
|
||||||
if (node.chunk === false) {
|
if (node.chunk === false) {
|
||||||
|
const buffer = Buffer.concat(bufferArray);
|
||||||
if (node.format === "utf8") {
|
if (node.format === "utf8") {
|
||||||
msg.payload = decode(lines, node.encoding);
|
msg.payload = decode(buffer, node.encoding);
|
||||||
}
|
}
|
||||||
else { msg.payload = lines; }
|
else { msg.payload = buffer; }
|
||||||
nodeSend(msg);
|
nodeSend(msg);
|
||||||
}
|
}
|
||||||
else if (node.format === "lines") {
|
else if (node.format === "lines") {
|
||||||
|
@ -40,9 +40,35 @@
|
|||||||
</dl>
|
</dl>
|
||||||
<h3>Details</h3>
|
<h3>Details</h3>
|
||||||
<p>Das abonnierte Topic darf MQTT-Platzhalterzeichen (wildcards) enthalten (+ für eine Ebene und # für mehrere Ebenen).</p>
|
<p>Das abonnierte Topic darf MQTT-Platzhalterzeichen (wildcards) enthalten (+ für eine Ebene und # für mehrere Ebenen).</p>
|
||||||
<p>Dieser Node erfordert eine Verbindung zu einem MQTT-Broker, der über die Auswahlliste selektiert werden kann.
|
<p>Diese Node erfordert eine Verbindung zu einem MQTT-Broker, der über die Auswahlliste selektiert werden kann. Eine neue Verbindung wird durch Klicken auf das Stiftsymbol erstellt.</p>
|
||||||
Eine neue Verbindung wird durch Klicken auf das Stiftsymbol erstellt.</p>
|
|
||||||
<p>Mehrere MQTT-Nodes (in oder out) können bei Bedarf dieselbe Broker-Verbindung nutzen.</p>
|
<p>Mehrere MQTT-Nodes (in oder out) können bei Bedarf dieselbe Broker-Verbindung nutzen.</p>
|
||||||
|
<h4>Dynamische Steuerung</h4>
|
||||||
|
Die von der Node genutzte Verbindung kann dynamisch gesteuert werden, wenn die MQTT-Node eine der folgenden Nachrichten erhält. Die Payload dieser Nachrichten werden nicht veröffentlicht.
|
||||||
|
<h4>Eingangsdaten</h4>
|
||||||
|
<p>Nur Verfügbar, wenn die Node für dynamische Abonnements konfiguriert wurde.</p>
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>action <span class="property-type">string</span></dt>
|
||||||
|
<dd>Der Name der Aktion, die die MQTT-Node ausführen soll. Verfügbare Aktionen sind: <code>"connect"</code>, <code>"disconnect"</code>, <code>"getSubscriptions"</code>, <code>"subscribe"</code> und <code>"unsubscribe"</code>.</dd>
|
||||||
|
<dt class="optional">topic <span class="property-type">string|object|array</span></dt>
|
||||||
|
<dd>Bei den Aktionen <code>"subscribe"</code> und <code>"unsubscribe"</code> gibt diese Eigenschaft die MQTT-Topic an. Dabei kann es sich um Folgendes handeln:
|
||||||
|
<ul>
|
||||||
|
<li>eine Zeichenfolge, die den Topic-Filter enthält</li>
|
||||||
|
<li>ein Objekt mit den Eigenschaften <code>topic</code> und <code>qos</code></li>
|
||||||
|
<li>ein Array aus Zeichenfolgen oder Objekten, um mehrere Topics gleichzeitig zu verwalten</li>
|
||||||
|
</ul>
|
||||||
|
</dd>
|
||||||
|
<dt class="optional">broker <span class="property-type">broker</span> </dt>
|
||||||
|
<dd>Für die Aktion <code>"connect"</code> kann diese Eigenschaft jede der einzelnen Broker-Konfigurationseinstellungen überschreiben, einschließlich: <ul>
|
||||||
|
<li><code>broker</code></li>
|
||||||
|
<li><code>port</code></li>
|
||||||
|
<li><code>url</code> - überschreibt Broker/Port, um eine vollständige Verbindungs-URL bereitzustellen</li>
|
||||||
|
<li><code>username</code></li>
|
||||||
|
<li><code>password</code></li>
|
||||||
|
</ul>
|
||||||
|
<p>Wenn diese Eigenschaft gesetzt ist und der Broker bereits verbunden ist, wird ein Fehler protokolliert, es sei denn, die Eigenschaft <code>force</code> gesetzt - in diesem Fall wird die Verbindung zum Broker getrennt, die neuen Einstellungen angewendet und erneut verbunden.</p>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/html" data-help-name="mqtt out">
|
<script type="text/html" data-help-name="mqtt out">
|
||||||
|
@ -460,7 +460,7 @@
|
|||||||
"staticTopic": "Subscribe to single topic",
|
"staticTopic": "Subscribe to single topic",
|
||||||
"dynamicTopic": "Dynamic subscription",
|
"dynamicTopic": "Dynamic subscription",
|
||||||
"auto-connect": "Connect automatically",
|
"auto-connect": "Connect automatically",
|
||||||
"auto-mode-depreciated": "This option is depreciated. Please use the new auto-detect mode.",
|
"auto-mode-depreciated": "This option is deprecated. Please use the new auto-detect mode.",
|
||||||
"none": "none",
|
"none": "none",
|
||||||
"other": "other"
|
"other": "other"
|
||||||
},
|
},
|
||||||
|
@ -48,7 +48,8 @@
|
|||||||
<dl class="message-properties">
|
<dl class="message-properties">
|
||||||
<dt>action <span class="property-type">string</span></dt>
|
<dt>action <span class="property-type">string</span></dt>
|
||||||
<dd>the name of the action the node should perform. Available actions are: <code>"connect"</code>,
|
<dd>the name of the action the node should perform. Available actions are: <code>"connect"</code>,
|
||||||
<code>"disconnect"</code>, <code>"subscribe"</code> and <code>"unsubscribe"</code>.</dd>
|
<code>"disconnect"</code>, <code>"getSubscriptions"</code>, <code>"subscribe"</code> and
|
||||||
|
<code>"unsubscribe"</code>.</dd>
|
||||||
<dt class="optional">topic <span class="property-type">string|object|array</span></dt>
|
<dt class="optional">topic <span class="property-type">string|object|array</span></dt>
|
||||||
<dd>For the <code>"subscribe"</code> and <code>"unsubscribe"</code> actions, this property
|
<dd>For the <code>"subscribe"</code> and <code>"unsubscribe"</code> actions, this property
|
||||||
provides the topic. It can be set as either:<ul>
|
provides the topic. It can be set as either:<ul>
|
||||||
|
@ -513,15 +513,15 @@
|
|||||||
"method": "Método",
|
"method": "Método",
|
||||||
"url": "URL",
|
"url": "URL",
|
||||||
"doc": "Docs",
|
"doc": "Docs",
|
||||||
"return": "Return",
|
"return": "Devolver",
|
||||||
"upload": "Accept file uploads?",
|
"upload": "¿Aceptar cargas de archivos?",
|
||||||
"status": "Status code",
|
"status": "Código de estado",
|
||||||
"headers": "Headers",
|
"headers": "Encabezados",
|
||||||
"other": "otro",
|
"other": "otro",
|
||||||
"paytoqs": {
|
"paytoqs": {
|
||||||
"ignore": "Ignore",
|
"ignore": "Ignorar",
|
||||||
"query": "Append to query-string parameters",
|
"query": "Agregar a los parámetros de la cadena de consulta",
|
||||||
"body": "Send as request body"
|
"body": "Enviar como cuerpo de la solicitud"
|
||||||
},
|
},
|
||||||
"utf8String": "texto UTF8",
|
"utf8String": "texto UTF8",
|
||||||
"binaryBuffer": "buffer binario",
|
"binaryBuffer": "buffer binario",
|
||||||
@ -529,45 +529,45 @@
|
|||||||
"authType": "Tipo",
|
"authType": "Tipo",
|
||||||
"bearerToken": "Token"
|
"bearerToken": "Token"
|
||||||
},
|
},
|
||||||
"setby": "- set by msg.method -",
|
"setby": "- establecido por msg.method -",
|
||||||
"basicauth": "Use authentication",
|
"basicauth": "Usar autenticación",
|
||||||
"use-tls": "Enable secure (SSL/TLS) connection",
|
"use-tls": "Habilitar conexión segura (SSL/TLS)",
|
||||||
"tls-config": "TLS Configuration",
|
"tls-config": "Configuración TLS",
|
||||||
"basic": "basic authentication",
|
"basic": "autenticación básica",
|
||||||
"digest": "digest authentication",
|
"digest": "autenticación digest",
|
||||||
"bearer": "bearer authentication",
|
"bearer": "autenticación bearer",
|
||||||
"use-proxy": "Use proxy",
|
"use-proxy": "Usar proxy",
|
||||||
"persist": "Enable connection keep-alive",
|
"persist": "Habilitar conexión activa (keep-alive)",
|
||||||
"proxy-config": "Proxy Configuration",
|
"proxy-config": "Configuración Proxy",
|
||||||
"use-proxyauth": "Use proxy authentication",
|
"use-proxyauth": "Usar autenticación de proxy",
|
||||||
"noproxy-hosts": "Ignore hosts",
|
"noproxy-hosts": "Ignorar hosts",
|
||||||
"senderr": "Only send non-2xx responses to Catch node",
|
"senderr": "Enviar solo respuestas que no sean 2xx al nodo Catch",
|
||||||
"utf8": "a UTF-8 string",
|
"utf8": "una cadena UTF-8",
|
||||||
"binary": "a binary buffer",
|
"binary": "un búfer binario",
|
||||||
"json": "a parsed JSON object",
|
"json": "un objeto JSON analizado",
|
||||||
"tip": {
|
"tip": {
|
||||||
"in": "The url will be relative to ",
|
"in": "La URL será relativa a ",
|
||||||
"res": "The messages sent to this node <b>must</b> originate from an <i>http input</i> node",
|
"res": "Los mensajes enviados a este nodo <b>deben</b> originarse desde un nodo de <i>http input</i>",
|
||||||
"req": "Tip: If the JSON parse fails the fetched string is returned as-is."
|
"req": "Tip: If the JSON parse fails the fetched string is returned as-is."
|
||||||
},
|
},
|
||||||
"httpreq": "http request",
|
"httpreq": "solicitud http",
|
||||||
"errors": {
|
"errors": {
|
||||||
"not-created": "Cannot create http-in node when httpNodeRoot set to false",
|
"not-created": "No se puede crear el nodo http-in cuando httpNodeRoot está establecido en falso",
|
||||||
"missing-path": "missing path",
|
"missing-path": "falta la ruta",
|
||||||
"no-response": "No response object",
|
"no-response": "No hay objeto de respuesta",
|
||||||
"json-error": "JSON parse error",
|
"json-error": "Error de análisis en JSON",
|
||||||
"no-url": "No url specified",
|
"no-url": "No se especificó ninguna URL",
|
||||||
"deprecated-call": "Deprecated call to __method__",
|
"deprecated-call": "Llamada obsoleta a __method__",
|
||||||
"invalid-transport": "non-http transport requested",
|
"invalid-transport": "protocolo no-http solicitado",
|
||||||
"timeout-isnan": "Timeout value is not a valid number, ignoring",
|
"timeout-isnan": "El valor de tiempo de espera no es un número válido, se ignora",
|
||||||
"timeout-isnegative": "Timeout value is negative, ignoring",
|
"timeout-isnegative": "El valor de tiempo de espera es negativo, se ignora",
|
||||||
"invalid-payload": "Invalid payload",
|
"invalid-payload": "payload Invalido",
|
||||||
"invalid-url": "Invalid url"
|
"invalid-url": "URL Inválida"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"requesting": "requesting"
|
"requesting": "solicitando"
|
||||||
},
|
},
|
||||||
"insecureHTTPParser": "Disable strict HTTP parsing"
|
"insecureHTTPParser": "Deshabilitar el análisis estricto de HTTP"
|
||||||
},
|
},
|
||||||
"websocket": {
|
"websocket": {
|
||||||
"label": {
|
"label": {
|
||||||
@ -576,41 +576,42 @@
|
|||||||
"url": "URL",
|
"url": "URL",
|
||||||
"subprotocol": "Subprotocolo"
|
"subprotocol": "Subprotocolo"
|
||||||
},
|
},
|
||||||
"listenon": "Listen on",
|
"listenon": "Escuchar",
|
||||||
"connectto": "Connect to",
|
"connectto": "Conectar a",
|
||||||
"sendrec": "Send/Receive",
|
"sendrec": "Enviar/Recibir",
|
||||||
"payload": "payload",
|
"payload": "payload",
|
||||||
"message": "entire message",
|
"message": "mensaje completo",
|
||||||
"sendheartbeat": "Send heartbeat",
|
"sendheartbeat": "Enviar latido",
|
||||||
"tip": {
|
"tip": {
|
||||||
"path1": "By default, <code>payload</code> will contain the data to be sent over, or received from a websocket. The listener can be configured to send or receive the entire message object as a JSON formatted string.",
|
"path1": "De manera predeterminada, <code>payload</code> contendrá los datos que se enviarán o recibirán de un websocket. El receptor puede configurarse para enviar o recibir el objeto de mensaje completo como una cadena en formato JSON.",
|
||||||
"path2": "This path will be relative to <code>__path__</code>.",
|
"path2": "Esta ruta será relativa a <code>__path__</code>.",
|
||||||
"url1": "URL should use ws:// or wss:// scheme and point to an existing websocket listener.",
|
"url1": "La URL debe usar el esquema ws:// o wss:// y apuntar a un receptor de websocket existente.",
|
||||||
"url2": "By default, <code>payload</code> will contain the data to be sent over, or received from a websocket. The client can be configured to send or receive the entire message object as a JSON formatted string."
|
"url2": "De manera predeterminada, <code>payload</code> contendrá los datos que se enviarán o recibirán de un websocket. El cliente puede configurarse para enviar o recibir el objeto de mensaje completo como una cadena en formato JSON",
|
||||||
|
"headers": "Los encabezados solo se envían durante el mecanismo de actualización del protocolo, de HTTP al protocolo WS/WSS."
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"connected": "connected __count__",
|
"connected": "__count__ conectado",
|
||||||
"connected_plural": "connected __count__"
|
"connected_plural": "__count__ conectados"
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"connect-error": "An error occurred on the ws connection: ",
|
"connect-error": "Se produjo un error en la conexión ws:",
|
||||||
"send-error": "An error occurred while sending: ",
|
"send-error": "Se produjo un error al enviar: ",
|
||||||
"missing-conf": "Missing server configuration",
|
"missing-conf": "Falta la configuración del servidor",
|
||||||
"duplicate-path": "Cannot have two WebSocket listeners on the same path: __path__",
|
"duplicate-path": "No se pueden tener dos escuchas de WebSocket en la misma ruta: __path__",
|
||||||
"missing-server": "Missing server configuration",
|
"missing-server": "Falta la configuración del servidor",
|
||||||
"missing-client": "Missing client configuration"
|
"missing-client": "Falta la configuración del cliente"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"watch": {
|
"watch": {
|
||||||
"watch": "watch",
|
"watch": "observar",
|
||||||
"label": {
|
"label": {
|
||||||
"files": "File(s)",
|
"files": "Fichero(s)",
|
||||||
"recursive": "Watch sub-directories recursively"
|
"recursive": "Observar subdirectorios recursivamente"
|
||||||
},
|
},
|
||||||
"placeholder": {
|
"placeholder": {
|
||||||
"files": "Comma-separated list of files and/or directories"
|
"files": "Lista de archivos y/o directorios separados por comas"
|
||||||
},
|
},
|
||||||
"tip": "On Windows you must use double back-slashes \\\\ in any directory names."
|
"tip": "En Windows, debes utilizar barras invertidas dobles \\\\ en cualquier nombre de directorio."
|
||||||
},
|
},
|
||||||
"tcpin": {
|
"tcpin": {
|
||||||
"label": {
|
"label": {
|
||||||
@ -849,7 +850,13 @@
|
|||||||
"newline": "Nueva línea",
|
"newline": "Nueva línea",
|
||||||
"usestrings": "analizar valores numéricos",
|
"usestrings": "analizar valores numéricos",
|
||||||
"include_empty_strings": "incluir cadenas vacías",
|
"include_empty_strings": "incluir cadenas vacías",
|
||||||
"include_null_values": "incluir valores nulos"
|
"include_null_values": "incluir valores nulos",
|
||||||
|
"spec": "Analizador"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"rfc": "RFC4180",
|
||||||
|
"legacy": "Legado",
|
||||||
|
"legacy_warning": "El modo legado se eliminará en una versión futura."
|
||||||
},
|
},
|
||||||
"placeholder": {
|
"placeholder": {
|
||||||
"columns": "nombres de columnas separados por comas"
|
"columns": "nombres de columnas separados por comas"
|
||||||
@ -878,6 +885,7 @@
|
|||||||
"once": "enviar encabezados una vez, hasta msg.reset"
|
"once": "enviar encabezados una vez, hasta msg.reset"
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
|
"bad_template": "Plantilla de columnas mal formada.",
|
||||||
"csv_js": "Este nodo solo maneja cadenas CSV u objetos JS.",
|
"csv_js": "Este nodo solo maneja cadenas CSV u objetos JS.",
|
||||||
"obj_csv": "No se ha especificado ninguna plantilla de columnas para el objeto -> CSV.",
|
"obj_csv": "No se ha especificado ninguna plantilla de columnas para el objeto -> CSV.",
|
||||||
"bad_csv": "Datos CSV con formato incorrecto: la salida probablemente esté corrupta."
|
"bad_csv": "Datos CSV con formato incorrecto: la salida probablemente esté corrupta."
|
||||||
@ -887,12 +895,14 @@
|
|||||||
"label": {
|
"label": {
|
||||||
"select": "Selector",
|
"select": "Selector",
|
||||||
"output": "Salida",
|
"output": "Salida",
|
||||||
"in": "en"
|
"in": "en",
|
||||||
|
"prefix": "Nombre de la propiedad para el contenido HTML"
|
||||||
},
|
},
|
||||||
"output": {
|
"output": {
|
||||||
"html": "el contenido HTML de los elementos",
|
"html": "el contenido HTML de los elementos",
|
||||||
"text": "sólo el contenido textual de los elementos",
|
"text": "sólo el contenido textual de los elementos",
|
||||||
"attr": "un objeto de cualquier atributo de los elementos"
|
"attr": "un objeto de cualquier atributo de los elementos",
|
||||||
|
"compl": "un objeto de cualquier atributo de los elementos y contenidos html"
|
||||||
},
|
},
|
||||||
"format": {
|
"format": {
|
||||||
"single": "como un mensaje único que contiene una matriz",
|
"single": "como un mensaje único que contiene una matriz",
|
||||||
@ -1007,6 +1017,7 @@
|
|||||||
"objectSend": "Enviar un mensaje para cada par clave/valor",
|
"objectSend": "Enviar un mensaje para cada par clave/valor",
|
||||||
"strBuff": "<b>Texto</b> / <b>Buffer</b>",
|
"strBuff": "<b>Texto</b> / <b>Buffer</b>",
|
||||||
"array": "<b>Array</b>",
|
"array": "<b>Array</b>",
|
||||||
|
"splitThe": "Dividir el",
|
||||||
"splitUsing": "Dividir usando",
|
"splitUsing": "Dividir usando",
|
||||||
"splitLength": "Longitud fija de",
|
"splitLength": "Longitud fija de",
|
||||||
"stream": "Manejar como un flujo de mensajes",
|
"stream": "Manejar como un flujo de mensajes",
|
||||||
@ -1036,6 +1047,7 @@
|
|||||||
"joinedUsing": "se unió usando",
|
"joinedUsing": "se unió usando",
|
||||||
"send": "Enviar el mensaje:",
|
"send": "Enviar el mensaje:",
|
||||||
"afterCount": "Después de varias partes del mensaje",
|
"afterCount": "Después de varias partes del mensaje",
|
||||||
|
"useparts": "Usar la propiedad msg.parts existente",
|
||||||
"count": "contar",
|
"count": "contar",
|
||||||
"subsequent": "y cada mensaje posterior.",
|
"subsequent": "y cada mensaje posterior.",
|
||||||
"afterTimeout": "Después de un tiempo de espera trás el primer mensaje",
|
"afterTimeout": "Después de un tiempo de espera trás el primer mensaje",
|
||||||
@ -1102,6 +1114,7 @@
|
|||||||
"too-many": "demasiados mensajes pendientes en el nodo de lotes",
|
"too-many": "demasiados mensajes pendientes en el nodo de lotes",
|
||||||
"unexpected": "modo inesperado",
|
"unexpected": "modo inesperado",
|
||||||
"no-parts": "ninguna propiedad 'parte' en el mensaje",
|
"no-parts": "ninguna propiedad 'parte' en el mensaje",
|
||||||
|
"honourParts": "Permitir que msg.parts también complete la operación por lotes.",
|
||||||
"error": {
|
"error": {
|
||||||
"invalid-count": "Recuento no válido",
|
"invalid-count": "Recuento no válido",
|
||||||
"invalid-overlap": "Solapamiento no válido",
|
"invalid-overlap": "Solapamiento no válido",
|
||||||
|
@ -24,12 +24,14 @@
|
|||||||
<p>Solo se envía el <code>msg.payload</code>.</p>
|
<p>Solo se envía el <code>msg.payload</code>.</p>
|
||||||
<p>Si <code>msg.payload</code> es una cadena que contiene una codificación Base64 de datos binarios, la opción de decodificación Base64 hará que se vuelva a convertir a binario antes de enviarse.</p>
|
<p>Si <code>msg.payload</code> es una cadena que contiene una codificación Base64 de datos binarios, la opción de decodificación Base64 hará que se vuelva a convertir a binario antes de enviarse.</p>
|
||||||
<p>Si <code>msg._session</code> no está presente, la carga se envía a <b>todos</b> los clientes conectados.</p>
|
<p>Si <code>msg._session</code> no está presente, la carga se envía a <b>todos</b> los clientes conectados.</p>
|
||||||
|
<p>En el modo Responder a, configurar <code>msg.reset = true</code> restablecerá la conexión especificada por _session.id, o todas las conexiones si no se especifica _session.id.</p>
|
||||||
<p><b>Nota: </b>En algunos sistemas, es posible que necesites acceso raíz o de administrador para acceder a los puertos inferiores a 1024.</p>
|
<p><b>Nota: </b>En algunos sistemas, es posible que necesites acceso raíz o de administrador para acceder a los puertos inferiores a 1024.</p>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/html" data-help-name="tcp request">
|
<script type="text/html" data-help-name="tcp request">
|
||||||
<p>Un nodo de solicitud TCP simple: envía el <code>msg.payload</code> a un puerto tcp del servidor y espera una respuesta.</p>
|
<p>Un nodo de solicitud TCP simple: envía el <code>msg.payload</code> a un puerto tcp del servidor y espera una respuesta.</p>
|
||||||
<p>Se conecta, envía la "solicitud" y lee la "respuesta". Puede contar una cantidad de caracteres devueltos en un búfer fijo, hacer coincidir un carácter específico antes de regresar, esperar un tiempo de espera fijo desde la primera respuesta y luego regresar, esperar datos, o enviar y luego cerrar la conexión inmediatamente, sin esperar una respuesta.</p>
|
<p>Se conecta, envía la "solicitud" y lee la "respuesta". Puede contar una cantidad de caracteres devueltos en un búfer fijo, hacer coincidir un carácter específico antes de regresar, esperar un tiempo de espera fijo desde la primera respuesta y luego regresar, esperar datos, o enviar y luego cerrar la conexión inmediatamente, sin esperar una respuesta.</p>
|
||||||
|
<p>Si está en modo sentado y esperando (permanecer conectado), puede enviar <code>msg.reset = true</code> o <code>msg.reset = "host:port"</code> para forzar una interrupción en la conexión y una reconexión automática.</p>
|
||||||
<p>La respuesta se generará en <code>msg.payload</code> como un búfer, por lo que es posible que quieras utilizar .toString().</p>
|
<p>La respuesta se generará en <code>msg.payload</code> como un búfer, por lo que es posible que quieras utilizar .toString().</p>
|
||||||
<p>Si dejas el host TCP o el puerto en blanco, debes configurarlos utilizando las propiedades <code>msg.host</code> y <code>msg.port</code> en cada mensaje enviado al nodo.</p>
|
<p>Si dejas el host TCP o el puerto en blanco, debes configurarlos utilizando las propiedades <code>msg.host</code> y <code>msg.port</code> en cada mensaje enviado al nodo.</p>
|
||||||
</script>
|
</script>
|
||||||
|
@ -35,7 +35,9 @@
|
|||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
<h3>Detalles</h3>
|
<h3>Detalles</h3>
|
||||||
<p>La plantilla de columnas puede contener una lista ordenada de nombres de columnas. Al convertir CSV en un objeto, los nombres de las columnas se utilizarán como nombres de propiedades. Alternativamente, los nombres de las columnas se pueden tomar de la primera fila del CSV.</p>
|
<p>La plantilla de columnas puede contener una lista ordenada de nombres de columnas. Al convertir CSV en un objeto, los nombres de las columnas se utilizarán como nombres de propiedades. Alternativamente, los nombres de las columnas se pueden tomar de la primera fila del CSV.
|
||||||
|
<p>Cuando se selecciona el analizador RFC, la plantilla de columna debe ser compatible con RFC4180.</p>
|
||||||
|
</p>
|
||||||
<p>Al convertir a CSV, la plantilla de columnas se utiliza para identificar qué propiedades extraer del objeto y en qué orden.</p>
|
<p>Al convertir a CSV, la plantilla de columnas se utiliza para identificar qué propiedades extraer del objeto y en qué orden.</p>
|
||||||
<p>Si la plantilla de columnas está en blanco, puede utilizar una lista simple de propiedades separadas por comas proporcionada en <code>msg.columns</code> para determinar qué extraer y en qué orden. Si ninguno de los dos está presente, todas las propiedades del objeto se muestran en el orden en que se encuentran en la primera fila.</p>
|
<p>Si la plantilla de columnas está en blanco, puede utilizar una lista simple de propiedades separadas por comas proporcionada en <code>msg.columns</code> para determinar qué extraer y en qué orden. Si ninguno de los dos está presente, todas las propiedades del objeto se muestran en el orden en que se encuentran en la primera fila.</p>
|
||||||
<p>Si la entrada es una matriz, entonces la plantilla de columnas solo se usa para generar opcionalmente una fila de títulos de columnas.</p>
|
<p>Si la entrada es una matriz, entonces la plantilla de columnas solo se usa para generar opcionalmente una fila de títulos de columnas.</p>
|
||||||
@ -46,4 +48,5 @@
|
|||||||
<p>Si genera varios mensajes, tendrán su propiedad <code>parts</code> configurada y formarán una secuencia de mensajes completa.</p>
|
<p>Si genera varios mensajes, tendrán su propiedad <code>parts</code> configurada y formarán una secuencia de mensajes completa.</p>
|
||||||
<p>Si el nodo está configurado para enviar encabezados de columna solo una vez, si se configura <code>msg.reset</code> en cualquier valor hará que el nodo reenvíe los encabezados.</p>
|
<p>Si el nodo está configurado para enviar encabezados de columna solo una vez, si se configura <code>msg.reset</code> en cualquier valor hará que el nodo reenvíe los encabezados.</p>
|
||||||
<p><b>Nota:</b> la plantilla de columna debe estar separada por comas, incluso si se elige un separador diferente para los datos.</p>
|
<p><b>Nota:</b> la plantilla de columna debe estar separada por comas, incluso si se elige un separador diferente para los datos.</p>
|
||||||
|
<p><b>Nota:</b> en el modo RFC, se generarán errores detectables para encabezados CSV mal formados y datos de carga útil de entrada no válidos</p>
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@node-red/nodes",
|
"name": "@node-red/nodes",
|
||||||
"version": "4.0.2",
|
"version": "4.0.9",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -15,20 +15,20 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"acorn": "8.11.3",
|
"acorn": "8.12.1",
|
||||||
"acorn-walk": "8.3.2",
|
"acorn-walk": "8.3.4",
|
||||||
"ajv": "8.14.0",
|
"ajv": "8.17.1",
|
||||||
"body-parser": "1.20.2",
|
"body-parser": "1.20.3",
|
||||||
"cheerio": "1.0.0-rc.10",
|
"cheerio": "1.0.0-rc.10",
|
||||||
"content-type": "1.0.5",
|
"content-type": "1.0.5",
|
||||||
"cookie-parser": "1.4.6",
|
"cookie-parser": "1.4.7",
|
||||||
"cookie": "0.6.0",
|
"cookie": "0.7.2",
|
||||||
"cors": "2.8.5",
|
"cors": "2.8.5",
|
||||||
"cronosjs": "1.7.1",
|
"cronosjs": "1.7.1",
|
||||||
"denque": "2.1.0",
|
"denque": "2.1.0",
|
||||||
"form-data": "4.0.0",
|
"form-data": "4.0.0",
|
||||||
"fs-extra": "11.2.0",
|
"fs-extra": "11.2.0",
|
||||||
"got": "12.6.0",
|
"got": "12.6.1",
|
||||||
"hash-sum": "2.0.0",
|
"hash-sum": "2.0.0",
|
||||||
"hpagent": "1.2.0",
|
"hpagent": "1.2.0",
|
||||||
"https-proxy-agent": "5.0.1",
|
"https-proxy-agent": "5.0.1",
|
||||||
@ -40,8 +40,8 @@
|
|||||||
"mustache": "4.2.0",
|
"mustache": "4.2.0",
|
||||||
"node-watch": "0.7.4",
|
"node-watch": "0.7.4",
|
||||||
"on-headers": "1.0.2",
|
"on-headers": "1.0.2",
|
||||||
"raw-body": "2.5.2",
|
"raw-body": "3.0.0",
|
||||||
"tough-cookie": "4.1.4",
|
"tough-cookie": "^5.0.0",
|
||||||
"uuid": "9.0.1",
|
"uuid": "9.0.1",
|
||||||
"ws": "7.5.10",
|
"ws": "7.5.10",
|
||||||
"xml2js": "0.6.2",
|
"xml2js": "0.6.2",
|
||||||
|
@ -144,7 +144,7 @@ async function installModule(module,version,url) {
|
|||||||
if (url) {
|
if (url) {
|
||||||
if (pkgurlRe.test(url) || localtgzRe.test(url)) {
|
if (pkgurlRe.test(url) || localtgzRe.test(url)) {
|
||||||
// Git remote url or Tarball url - check the valid package url
|
// Git remote url or Tarball url - check the valid package url
|
||||||
installName = url;
|
installName = localtgzRe.test(url) && slashRe.test(url) ? `"${url}"` : url;
|
||||||
isRegistryPackage = false;
|
isRegistryPackage = false;
|
||||||
} else {
|
} else {
|
||||||
log.warn(log._("server.install.install-failed-url",{name:module,url:url}));
|
log.warn(log._("server.install.install-failed-url",{name:module,url:url}));
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@node-red/registry",
|
"name": "@node-red/registry",
|
||||||
"version": "4.0.2",
|
"version": "4.0.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": "4.0.2",
|
"@node-red/util": "4.0.9",
|
||||||
"clone": "2.1.2",
|
"clone": "2.1.2",
|
||||||
"fs-extra": "11.2.0",
|
"fs-extra": "11.2.0",
|
||||||
"semver": "7.5.4",
|
"semver": "7.6.3",
|
||||||
"tar": "7.2.0",
|
"tar": "7.4.3",
|
||||||
"uglify-js": "3.17.4"
|
"uglify-js": "3.17.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,21 +96,37 @@ var api = module.exports = {
|
|||||||
} else if (scope === 'node') {
|
} else if (scope === 'node') {
|
||||||
var node = runtime.nodes.getNode(id);
|
var node = runtime.nodes.getNode(id);
|
||||||
if (node) {
|
if (node) {
|
||||||
|
if (/^subflow:/.test(node.type)) {
|
||||||
|
ctx = runtime.nodes.getContext(node.id);
|
||||||
|
} else {
|
||||||
ctx = node.context();
|
ctx = node.context();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (ctx) {
|
if (ctx) {
|
||||||
if (key) {
|
if (key) {
|
||||||
store = store || availableStores.default;
|
store = store || availableStores.default;
|
||||||
ctx.get(key,store,function(err, v) {
|
ctx.get(key,store,function(err, v) {
|
||||||
if (opts.keysOnly) {
|
if (opts.keysOnly) {
|
||||||
|
const result = {}
|
||||||
if (Array.isArray(v)) {
|
if (Array.isArray(v)) {
|
||||||
resolve({ [store]: { format: `array[${v.length}]`}})
|
result.format = `array[${v.length}]`
|
||||||
} else if (typeof v === 'object') {
|
} else if (typeof v === 'object') {
|
||||||
resolve({ [store]: { keys: Object.keys(v), format: 'Object' } })
|
result.keys = Object.keys(v).map(k => {
|
||||||
|
if (Array.isArray(v[k])) {
|
||||||
|
return { key: k, format: `array[${v[k].length}]`, length: v[k].length }
|
||||||
|
} else if (typeof v[k] === 'object') {
|
||||||
|
return { key: k, format: 'object' }
|
||||||
} else {
|
} else {
|
||||||
resolve({ [store]: { keys: [] }})
|
return { key: k }
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
result.format = 'object'
|
||||||
|
} else {
|
||||||
|
result.keys = []
|
||||||
|
}
|
||||||
|
resolve({ [store]: result })
|
||||||
|
return
|
||||||
}
|
}
|
||||||
var encoded = util.encodeObject({msg:v});
|
var encoded = util.encodeObject({msg:v});
|
||||||
if (store !== availableStores.default) {
|
if (store !== availableStores.default) {
|
||||||
@ -147,7 +163,7 @@ var api = module.exports = {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
result[store] = { keys }
|
result[store] = { keys: keys.map(key => { return { key }}) }
|
||||||
c--;
|
c--;
|
||||||
if (c === 0) {
|
if (c === 0) {
|
||||||
if (!errorReported) {
|
if (!errorReported) {
|
||||||
|
@ -719,6 +719,14 @@ class Flow {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getContext(scope) {
|
||||||
|
if (scope === 'flow') {
|
||||||
|
return this.context
|
||||||
|
} else if (scope === 'global') {
|
||||||
|
return context.get('global')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dump() {
|
dump() {
|
||||||
console.log("==================")
|
console.log("==================")
|
||||||
console.log(this.TYPE, this.id);
|
console.log(this.TYPE, this.id);
|
||||||
|
@ -49,6 +49,14 @@ class Group {
|
|||||||
}
|
}
|
||||||
return this.parent.getSetting(key);
|
return this.parent.getSetting(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
error(msg) {
|
||||||
|
this.parent.error(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
getContext(scope) {
|
||||||
|
return this.parent.getContext(scope);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -100,7 +100,24 @@ async function evaluateEnvProperties(flow, env, credentials) {
|
|||||||
}
|
}
|
||||||
} else if (type ==='jsonata') {
|
} else if (type ==='jsonata') {
|
||||||
pendingEvaluations.push(new Promise((resolve, _) => {
|
pendingEvaluations.push(new Promise((resolve, _) => {
|
||||||
redUtil.evaluateNodeProperty(value, 'jsonata', {_flow: flow}, null, (err, result) => {
|
redUtil.evaluateNodeProperty(value, 'jsonata',{
|
||||||
|
// Fake a node object to provide access to _flow and context
|
||||||
|
_flow: flow,
|
||||||
|
context: () => {
|
||||||
|
return {
|
||||||
|
flow: {
|
||||||
|
get: (value, store, callback) => {
|
||||||
|
return flow.getContext('flow').get(value, store, callback)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
get: (value, store, callback) => {
|
||||||
|
return flow.getContext('global').get(value, store, callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, null, (err, result) => {
|
||||||
if (!err) {
|
if (!err) {
|
||||||
if (typeof result === 'object') {
|
if (typeof result === 'object') {
|
||||||
result = { value: result, __clone__: true}
|
result = { value: result, __clone__: true}
|
||||||
@ -113,6 +130,10 @@ async function evaluateEnvProperties(flow, env, credentials) {
|
|||||||
resolve()
|
resolve()
|
||||||
});
|
});
|
||||||
}))
|
}))
|
||||||
|
} else if (type === "conf-type" && /^\${[^}]+}$/.test(value)) {
|
||||||
|
// Get the config node from the parent subflow
|
||||||
|
const name = value.substring(2, value.length - 1);
|
||||||
|
value = flow.getSetting(name);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
value = redUtil.evaluateNodeProperty(value, type, {_flow: flow}, null, null);
|
value = redUtil.evaluateNodeProperty(value, type, {_flow: flow}, null, null);
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
"removing-modules": "Eliminando módulos de la configuración",
|
"removing-modules": "Eliminando módulos de la configuración",
|
||||||
"added-types": "Tipos de nodos añadidos:",
|
"added-types": "Tipos de nodos añadidos:",
|
||||||
"removed-types": "Tipos de nodos eliminados:",
|
"removed-types": "Tipos de nodos eliminados:",
|
||||||
|
"removed-plugins": "Extensiones eliminadas:",
|
||||||
"install": {
|
"install": {
|
||||||
"invalid": "Nombre de módulo no válido",
|
"invalid": "Nombre de módulo no válido",
|
||||||
"installing": "Instalando módulo: __name__, versión: __version__",
|
"installing": "Instalando módulo: __name__, versión: __version__",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@node-red/runtime",
|
"name": "@node-red/runtime",
|
||||||
"version": "4.0.2",
|
"version": "4.0.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": "4.0.2",
|
"@node-red/registry": "4.0.9",
|
||||||
"@node-red/util": "4.0.2",
|
"@node-red/util": "4.0.9",
|
||||||
"async-mutex": "0.5.0",
|
"async-mutex": "0.5.0",
|
||||||
"clone": "2.1.2",
|
"clone": "2.1.2",
|
||||||
"express": "4.19.2",
|
"express": "4.21.2",
|
||||||
"fs-extra": "11.2.0",
|
"fs-extra": "11.2.0",
|
||||||
"json-stringify-safe": "5.0.1",
|
"json-stringify-safe": "5.0.1",
|
||||||
"rfdc": "^1.3.1"
|
"rfdc": "^1.3.1"
|
||||||
|
22
packages/node_modules/@node-red/util/lib/log.js
vendored
22
packages/node_modules/@node-red/util/lib/log.js
vendored
@ -75,12 +75,28 @@ LogHandler.prototype.shouldReportMessage = function(msglevel) {
|
|||||||
msglevel <= this.logLevel;
|
msglevel <= this.logLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Older versions of Node-RED used the deprecated util.log function.
|
||||||
|
// With Node.js 22, use of that function causes warnings. So here we
|
||||||
|
// are replicating the same format output to ensure we don't break any
|
||||||
|
// log parsing that happens in the real world.
|
||||||
|
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||||
|
const utilLog = function (msg) {
|
||||||
|
const d = new Date();
|
||||||
|
const time = [
|
||||||
|
d.getHours().toString().padStart(2, '0'),
|
||||||
|
d.getMinutes().toString().padStart(2, '0'),
|
||||||
|
d.getSeconds().toString().padStart(2, '0')
|
||||||
|
].join(':');
|
||||||
|
console.log(`${d.getDate()} ${months[d.getMonth()]} ${time} - ${msg}`)
|
||||||
|
}
|
||||||
|
|
||||||
var consoleLogger = function(msg) {
|
var consoleLogger = function(msg) {
|
||||||
if (msg.level == log.METRIC || msg.level == log.AUDIT) {
|
if (msg.level == log.METRIC || msg.level == log.AUDIT) {
|
||||||
util.log("["+levelNames[msg.level]+"] "+JSON.stringify(msg));
|
utilLog("["+levelNames[msg.level]+"] "+JSON.stringify(msg));
|
||||||
} else {
|
} else {
|
||||||
if (verbose && msg.msg && msg.msg.stack) {
|
if (verbose && msg.msg && msg.msg.stack) {
|
||||||
util.log("["+levelNames[msg.level]+"] "+(msg.type?"["+msg.type+":"+(msg.name||msg.id)+"] ":"")+msg.msg.stack);
|
utilLog("["+levelNames[msg.level]+"] "+(msg.type?"["+msg.type+":"+(msg.name||msg.id)+"] ":"")+msg.msg.stack);
|
||||||
} else {
|
} else {
|
||||||
var message = msg.msg;
|
var message = msg.msg;
|
||||||
try {
|
try {
|
||||||
@ -91,7 +107,7 @@ var consoleLogger = function(msg) {
|
|||||||
message = 'Exception trying to log: '+util.inspect(message);
|
message = 'Exception trying to log: '+util.inspect(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
util.log("["+levelNames[msg.level]+"] "+(msg.type?"["+msg.type+":"+(msg.name||msg.id)+"] ":"")+message);
|
utilLog("["+levelNames[msg.level]+"] "+(msg.type?"["+msg.type+":"+(msg.name||msg.id)+"] ":"")+message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@node-red/util",
|
"name": "@node-red/util",
|
||||||
"version": "4.0.2",
|
"version": "4.0.9",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -21,6 +21,6 @@
|
|||||||
"jsonata": "2.0.5",
|
"jsonata": "2.0.5",
|
||||||
"lodash.clonedeep": "^4.5.0",
|
"lodash.clonedeep": "^4.5.0",
|
||||||
"moment": "2.30.1",
|
"moment": "2.30.1",
|
||||||
"moment-timezone": "0.5.45"
|
"moment-timezone": "0.5.46"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
16
packages/node_modules/node-red/package.json
vendored
16
packages/node_modules/node-red/package.json
vendored
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "node-red",
|
"name": "node-red",
|
||||||
"version": "4.0.2",
|
"version": "4.0.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,18 +31,18 @@
|
|||||||
"flow"
|
"flow"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@node-red/editor-api": "4.0.2",
|
"@node-red/editor-api": "4.0.9",
|
||||||
"@node-red/runtime": "4.0.2",
|
"@node-red/runtime": "4.0.9",
|
||||||
"@node-red/util": "4.0.2",
|
"@node-red/util": "4.0.9",
|
||||||
"@node-red/nodes": "4.0.2",
|
"@node-red/nodes": "4.0.9",
|
||||||
"basic-auth": "2.0.1",
|
"basic-auth": "2.0.1",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"cors": "2.8.5",
|
"cors": "2.8.5",
|
||||||
"express": "4.19.2",
|
"express": "4.21.2",
|
||||||
"fs-extra": "11.2.0",
|
"fs-extra": "11.2.0",
|
||||||
"node-red-admin": "^4.0.0",
|
"node-red-admin": "^4.0.1",
|
||||||
"nopt": "5.0.0",
|
"nopt": "5.0.0",
|
||||||
"semver": "7.5.4"
|
"semver": "7.6.3"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@node-rs/bcrypt": "1.10.4"
|
"@node-rs/bcrypt": "1.10.4"
|
||||||
|
@ -36,10 +36,12 @@ function generateScript() {
|
|||||||
packages.forEach(name => {
|
packages.forEach(name => {
|
||||||
const tarName = name.replace(/@/,"").replace(/\//,"-")
|
const tarName = name.replace(/@/,"").replace(/\//,"-")
|
||||||
lines.push(`npm publish ${tarName}-${version}.tgz ${tagArg}\n`);
|
lines.push(`npm publish ${tarName}-${version}.tgz ${tagArg}\n`);
|
||||||
if (updateNextToLatest) {
|
|
||||||
lines.push(`npm dist-tag add ${name}@${version} next\n`);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
if (updateNextToLatest) {
|
||||||
|
packages.forEach(name => {
|
||||||
|
lines.push(`npm dist-tag add ${name}@${version} next\n`);
|
||||||
|
})
|
||||||
|
}
|
||||||
resolve(lines.join(""))
|
resolve(lines.join(""))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1009,6 +1009,29 @@ describe('delay Node', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('sending a msg with reset to empty queue doesnt send anything', function(done) {
|
||||||
|
this.timeout(2000);
|
||||||
|
var flow = [{"id":"delayNode1","type":"delay","name":"delayNode","pauseType":"rate","timeout":1,"timeoutUnits":"seconds","rate":2,"rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"wires":[["helperNode1"]]},
|
||||||
|
{id:"helperNode1", type:"helper", wires:[]}];
|
||||||
|
helper.load(delayNode, flow, function() {
|
||||||
|
var delayNode1 = helper.getNode("delayNode1");
|
||||||
|
var helperNode1 = helper.getNode("helperNode1");
|
||||||
|
var t = Date.now();
|
||||||
|
var c = 0;
|
||||||
|
helperNode1.on("input", function(msg) {
|
||||||
|
console.log("Shold not get here")
|
||||||
|
done(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout( function() {
|
||||||
|
if (c === 0) { done(); }
|
||||||
|
}, 250);
|
||||||
|
|
||||||
|
// send test messages
|
||||||
|
delayNode1.receive({payload:1,topic:"foo",reset:true}); // send something with blank topic
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
/* Messaging API support */
|
/* Messaging API support */
|
||||||
function mapiDoneTestHelper(done, pauseType, drop, msgAndTimings) {
|
function mapiDoneTestHelper(done, pauseType, drop, msgAndTimings) {
|
||||||
const completeNode = require("nr-test-utils").require("@node-red/nodes/core/common/24-complete.js");
|
const completeNode = require("nr-test-utils").require("@node-red/nodes/core/common/24-complete.js");
|
||||||
|
@ -111,8 +111,16 @@ describe('trigger node', function() {
|
|||||||
try {
|
try {
|
||||||
if (rval) {
|
if (rval) {
|
||||||
msg.should.have.property("payload");
|
msg.should.have.property("payload");
|
||||||
|
if (type == "date" && val == "1") {
|
||||||
|
should.deepEqual(Math.round(msg.payload/1000000), Math.round(Date.now()/1000000));
|
||||||
|
}
|
||||||
|
else if (type == "date" && val == "iso") {
|
||||||
|
should.deepEqual(msg.payload.substr(0,11), rval.substr(0,11));
|
||||||
|
}
|
||||||
|
else {
|
||||||
should.deepEqual(msg.payload, rval);
|
should.deepEqual(msg.payload, rval);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
msg.should.have.property("payload", val);
|
msg.should.have.property("payload", val);
|
||||||
}
|
}
|
||||||
@ -126,6 +134,7 @@ describe('trigger node', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should output 2st value when triggered ('+type+')', function(done) {
|
it('should output 2st value when triggered ('+type+')', function(done) {
|
||||||
|
if (type == "date" && val == "1") { val = "0"; }
|
||||||
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1:"foo", op1type:"str", op2:val, op2type:type, duration:"20", wires:[["n2"]] },
|
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1:"foo", op1type:"str", op2:val, op2type:type, duration:"20", wires:[["n2"]] },
|
||||||
{id:"n2", type:"helper"} ];
|
{id:"n2", type:"helper"} ];
|
||||||
process.env[val] = rval;
|
process.env[val] = rval;
|
||||||
@ -142,8 +151,16 @@ describe('trigger node', function() {
|
|||||||
else {
|
else {
|
||||||
if (rval) {
|
if (rval) {
|
||||||
msg.should.have.property("payload");
|
msg.should.have.property("payload");
|
||||||
|
if (type == "date" && val == "0") {
|
||||||
|
;(Math.round(msg.payload/1000000)).should.be.approximately(parseInt(Date.now()/1000000), 1);
|
||||||
|
}
|
||||||
|
else if (type == "date" && val == "iso") {
|
||||||
|
should.deepEqual(msg.payload.substr(0,11), rval.substr(0,11));
|
||||||
|
}
|
||||||
|
else {
|
||||||
should.deepEqual(msg.payload, rval);
|
should.deepEqual(msg.payload, rval);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
msg.should.have.property("payload", val);
|
msg.should.have.property("payload", val);
|
||||||
}
|
}
|
||||||
@ -166,6 +183,9 @@ describe('trigger node', function() {
|
|||||||
var val_buf = "[1,2,3,4,5]";
|
var val_buf = "[1,2,3,4,5]";
|
||||||
basicTest("bin", val_buf, Buffer.from(JSON.parse(val_buf)));
|
basicTest("bin", val_buf, Buffer.from(JSON.parse(val_buf)));
|
||||||
basicTest("env", "NR-TEST", "env-val");
|
basicTest("env", "NR-TEST", "env-val");
|
||||||
|
basicTest("date", "1", Date.now());
|
||||||
|
basicTest("date", "iso", (new Date()).toISOString());
|
||||||
|
// basicTest("date", "object", Date.now());
|
||||||
|
|
||||||
it('should output 1 then 0 when triggered (default)', function(done) {
|
it('should output 1 then 0 when triggered (default)', function(done) {
|
||||||
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", duration:"20", wires:[["n2"]] },
|
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", duration:"20", wires:[["n2"]] },
|
||||||
|
@ -2067,6 +2067,27 @@ describe('CSV node (RFC Mode)', function () {
|
|||||||
n2.on("input", function (msg) {
|
n2.on("input", function (msg) {
|
||||||
try {
|
try {
|
||||||
msg.should.have.property('payload', '1\tfoo\t"ba""r"\tdi,ng\n');
|
msg.should.have.property('payload', '1\tfoo\t"ba""r"\tdi,ng\n');
|
||||||
|
msg.should.have.property('columns', 'd,b,c,a'); // Strict RFC columns
|
||||||
|
done();
|
||||||
|
} catch (e) {
|
||||||
|
done(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const testJson = { d: 1, b: "foo", c: "ba\"r", a: "di,ng" };
|
||||||
|
n1.emit("input", { payload: testJson });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert a simple object back to a tsv with headers using a tab as a separator', function (done) {
|
||||||
|
const flow = [{ id: "n1", type: "csv", spec: "rfc", temp: "", sep: "\t", ret: '\n', hdrout: "all", wires: [["n2"]] }, // RFC-vs-Legacy difference - use line separator \n to satisfy original test
|
||||||
|
{ id: "n2", type: "helper" }];
|
||||||
|
helper.load(csvNode, flow, function () {
|
||||||
|
const n1 = helper.getNode("n1");
|
||||||
|
const n2 = helper.getNode("n2");
|
||||||
|
n2.on("input", function (msg) {
|
||||||
|
try {
|
||||||
|
msg.should.have.property('payload', 'd\tb\tc\ta\n1\tfoo\t"ba""r"\tdi,ng\n');
|
||||||
|
msg.should.have.property('columns', 'd,b,c,a'); // Strict RFC columns
|
||||||
done();
|
done();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
done(e);
|
done(e);
|
||||||
@ -2086,6 +2107,7 @@ describe('CSV node (RFC Mode)', function () {
|
|||||||
n2.on("input", function (msg) {
|
n2.on("input", function (msg) {
|
||||||
try {
|
try {
|
||||||
msg.should.have.property('payload', '4,foo,true,,0\n');
|
msg.should.have.property('payload', '4,foo,true,,0\n');
|
||||||
|
msg.should.have.property('columns', 'a,b o,c p,e'); // Strict RFC columns
|
||||||
done();
|
done();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
done(e);
|
done(e);
|
||||||
@ -2106,6 +2128,7 @@ describe('CSV node (RFC Mode)', function () {
|
|||||||
try {
|
try {
|
||||||
// 'payload', 'a"a,b\'b\nA1,B1\nA2,B2\n'); // Legacy
|
// 'payload', 'a"a,b\'b\nA1,B1\nA2,B2\n'); // Legacy
|
||||||
msg.should.have.property('payload', '"a""a",b\'b\nA1,B1\nA2,B2\n'); // RFC-vs-Legacy difference - RFC4180 Section 2.6, 2.7 quote handling
|
msg.should.have.property('payload', '"a""a",b\'b\nA1,B1\nA2,B2\n'); // RFC-vs-Legacy difference - RFC4180 Section 2.6, 2.7 quote handling
|
||||||
|
msg.should.have.property('columns', '"a""a",b\'b'); // RCF compliant column names
|
||||||
done();
|
done();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
done(e);
|
done(e);
|
||||||
@ -2171,6 +2194,7 @@ describe('CSV node (RFC Mode)', function () {
|
|||||||
n2.on("input", function (msg) {
|
n2.on("input", function (msg) {
|
||||||
try {
|
try {
|
||||||
msg.should.have.property('payload', '1,3,2,4\n4,2,3,1\n');
|
msg.should.have.property('payload', '1,3,2,4\n4,2,3,1\n');
|
||||||
|
msg.should.have.property('columns', 'd,b,c,a'); // Strict RFC columns
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
catch (e) { done(e); }
|
catch (e) { done(e); }
|
||||||
@ -2189,6 +2213,7 @@ describe('CSV node (RFC Mode)', function () {
|
|||||||
n2.on("input", function (msg) {
|
n2.on("input", function (msg) {
|
||||||
try {
|
try {
|
||||||
msg.should.have.property('payload', 'd,b,c,a\n1,3,2,4\n4,"f\ng",3,1\n');
|
msg.should.have.property('payload', 'd,b,c,a\n1,3,2,4\n4,"f\ng",3,1\n');
|
||||||
|
msg.should.have.property('columns', 'd,b,c,a'); // Strict RFC columns
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
catch (e) { done(e); }
|
catch (e) { done(e); }
|
||||||
@ -2208,6 +2233,7 @@ describe('CSV node (RFC Mode)', function () {
|
|||||||
try {
|
try {
|
||||||
// 'payload', ',0,1,foo,"ba""r","di,ng","fa\nba"\n');
|
// 'payload', ',0,1,foo,"ba""r","di,ng","fa\nba"\n');
|
||||||
msg.should.have.property('payload', ',0,1,foo\n'); // RFC-vs-Legacy difference - respect that user has specified a template with 4 columns
|
msg.should.have.property('payload', ',0,1,foo\n'); // RFC-vs-Legacy difference - respect that user has specified a template with 4 columns
|
||||||
|
msg.should.have.property('columns', 'a,b,c,d');
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
catch (e) { done(e); }
|
catch (e) { done(e); }
|
||||||
@ -2327,6 +2353,7 @@ describe('CSV node (RFC Mode)', function () {
|
|||||||
n2.on("input", function (msg) {
|
n2.on("input", function (msg) {
|
||||||
try {
|
try {
|
||||||
msg.should.have.property('payload', '{},"text,with,commas","This ""is"" a banana","{""sub"":""object""}"\n');
|
msg.should.have.property('payload', '{},"text,with,commas","This ""is"" a banana","{""sub"":""object""}"\n');
|
||||||
|
msg.should.have.property('columns', 'a,b,c,d');
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
catch (e) { done(e); }
|
catch (e) { done(e); }
|
||||||
|
@ -258,6 +258,29 @@ describe('nodes/registry/installer', function() {
|
|||||||
}).catch(done);
|
}).catch(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("succeeds when file path is valid node-red module", function(done) {
|
||||||
|
var nodeInfo = {nodes:{module:"foo",types:["a"]}};
|
||||||
|
|
||||||
|
var res = {
|
||||||
|
code: 0,
|
||||||
|
stdout:"",
|
||||||
|
stderr:""
|
||||||
|
}
|
||||||
|
var p = Promise.resolve(res);
|
||||||
|
p.catch((err)=>{});
|
||||||
|
execResponse = p;
|
||||||
|
|
||||||
|
var addModule = sinon.stub(registry,"addModule").callsFake(function(md) {
|
||||||
|
return Promise.resolve(nodeInfo);
|
||||||
|
});
|
||||||
|
|
||||||
|
installer.installModule("foo",null,"/example path/foo-0.1.1.tgz").then(function(info) {
|
||||||
|
exec.run.lastCall.args[1].should.eql([ 'install', '--no-audit', '--no-update-notifier', '--no-fund', '--save', '--save-prefix=~', '--omit=dev', '--engine-strict', '"/example path/foo-0.1.1.tgz"' ]);
|
||||||
|
info.should.eql(nodeInfo);
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
it("triggers preInstall and postInstall hooks", function(done) {
|
it("triggers preInstall and postInstall hooks", function(done) {
|
||||||
let receivedPreEvent,receivedPostEvent;
|
let receivedPreEvent,receivedPostEvent;
|
||||||
hooks.add("preInstall", function(event) { event.args = ["a"]; receivedPreEvent = event; })
|
hooks.add("preInstall", function(event) { event.args = ["a"]; receivedPreEvent = event; })
|
||||||
|
@ -24,38 +24,38 @@ var log = NR_TEST_UTILS.require("@node-red/util").log;
|
|||||||
|
|
||||||
describe("@node-red/util/log", function() {
|
describe("@node-red/util/log", function() {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
var spy = sinon.stub(util, 'log').callsFake(function(arg){});
|
var spy = sinon.stub(console, 'log').callsFake(function(arg){});
|
||||||
var settings = {logging: { console: { level: 'metric', metrics: true } } };
|
var settings = {logging: { console: { level: 'metric', metrics: true } } };
|
||||||
log.init(settings);
|
log.init(settings);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
util.log.restore();
|
console.log.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('it can raise an error', function() {
|
it('it can raise an error', function() {
|
||||||
var ret = log.error("This is an error");
|
var ret = log.error("This is an error");
|
||||||
sinon.assert.calledWithMatch(util.log,"[error] This is an error");
|
sinon.assert.calledWithMatch(console.log,"[error] This is an error");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('it can raise a trace', function() {
|
it('it can raise a trace', function() {
|
||||||
var ret = log.trace("This is a trace");
|
var ret = log.trace("This is a trace");
|
||||||
sinon.assert.calledWithMatch(util.log,"[trace] This is a trace");
|
sinon.assert.calledWithMatch(console.log,"[trace] This is a trace");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('it can raise a debug', function() {
|
it('it can raise a debug', function() {
|
||||||
var ret = log.debug("This is a debug");
|
var ret = log.debug("This is a debug");
|
||||||
sinon.assert.calledWithMatch(util.log,"[debug] This is a debug");
|
sinon.assert.calledWithMatch(console.log,"[debug] This is a debug");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('it can raise a info', function() {
|
it('it can raise a info', function() {
|
||||||
var ret = log.info("This is an info");
|
var ret = log.info("This is an info");
|
||||||
sinon.assert.calledWithMatch(util.log,"[info] This is an info");
|
sinon.assert.calledWithMatch(console.log,"[info] This is an info");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('it can raise a warn', function() {
|
it('it can raise a warn', function() {
|
||||||
var ret = log.warn("This is a warn");
|
var ret = log.warn("This is a warn");
|
||||||
sinon.assert.calledWithMatch(util.log,"[warn] This is a warn");
|
sinon.assert.calledWithMatch(console.log,"[warn] This is a warn");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('it can raise a metric', function() {
|
it('it can raise a metric', function() {
|
||||||
@ -66,9 +66,10 @@ describe("@node-red/util/log", function() {
|
|||||||
metrics.msgid = "12345";
|
metrics.msgid = "12345";
|
||||||
metrics.value = "the metric payload";
|
metrics.value = "the metric payload";
|
||||||
var ret = log.log(metrics);
|
var ret = log.log(metrics);
|
||||||
util.log.calledOnce.should.be.true();
|
console.log.calledOnce.should.be.true();
|
||||||
util.log.firstCall.args[0].indexOf("[metric] ").should.equal(0);
|
console.log.firstCall.args[0].indexOf("[metric]").should.not.equal(-1);
|
||||||
var body = JSON.parse(util.log.firstCall.args[0].substring(9));
|
const parts = console.log.firstCall.args[0].split("[metric] ")
|
||||||
|
var body = JSON.parse(parts[1])
|
||||||
body.should.have.a.property("nodeid","testid");
|
body.should.have.a.property("nodeid","testid");
|
||||||
body.should.have.a.property("event","node.test.testevent");
|
body.should.have.a.property("event","node.test.testevent");
|
||||||
body.should.have.a.property("msgid","12345");
|
body.should.have.a.property("msgid","12345");
|
||||||
@ -86,13 +87,13 @@ describe("@node-red/util/log", function() {
|
|||||||
|
|
||||||
it('it logs node type and name if provided',function() {
|
it('it logs node type and name if provided',function() {
|
||||||
log.log({level:log.INFO,type:"nodeType",msg:"test",name:"nodeName",id:"nodeId"});
|
log.log({level:log.INFO,type:"nodeType",msg:"test",name:"nodeName",id:"nodeId"});
|
||||||
util.log.calledOnce.should.be.true();
|
console.log.calledOnce.should.be.true();
|
||||||
util.log.firstCall.args[0].indexOf("[nodeType:nodeName]").should.not.equal(-1);
|
console.log.firstCall.args[0].indexOf("[nodeType:nodeName]").should.not.equal(-1);
|
||||||
});
|
});
|
||||||
it('it logs node type and id if no name provided',function() {
|
it('it logs node type and id if no name provided',function() {
|
||||||
log.log({level:log.INFO,type:"nodeType",msg:"test",id:"nodeId"});
|
log.log({level:log.INFO,type:"nodeType",msg:"test",id:"nodeId"});
|
||||||
util.log.calledOnce.should.be.true();
|
console.log.calledOnce.should.be.true();
|
||||||
util.log.firstCall.args[0].indexOf("[nodeType:nodeId]").should.not.equal(-1);
|
console.log.firstCall.args[0].indexOf("[nodeType:nodeId]").should.not.equal(-1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('ignores lower level messages and metrics', function() {
|
it('ignores lower level messages and metrics', function() {
|
||||||
@ -104,12 +105,12 @@ describe("@node-red/util/log", function() {
|
|||||||
log.debug("This is a debug");
|
log.debug("This is a debug");
|
||||||
log.trace("This is a trace");
|
log.trace("This is a trace");
|
||||||
log.log({level:log.METRIC,msg:"testMetric"});
|
log.log({level:log.METRIC,msg:"testMetric"});
|
||||||
sinon.assert.calledWithMatch(util.log,"[error] This is an error");
|
sinon.assert.calledWithMatch(console.log,"[error] This is an error");
|
||||||
sinon.assert.calledWithMatch(util.log,"[warn] This is a warn");
|
sinon.assert.calledWithMatch(console.log,"[warn] This is a warn");
|
||||||
sinon.assert.neverCalledWithMatch(util.log,"[info] This is an info");
|
sinon.assert.neverCalledWithMatch(console.log,"[info] This is an info");
|
||||||
sinon.assert.neverCalledWithMatch(util.log,"[debug] This is a debug");
|
sinon.assert.neverCalledWithMatch(console.log,"[debug] This is a debug");
|
||||||
sinon.assert.neverCalledWithMatch(util.log,"[trace] This is a trace");
|
sinon.assert.neverCalledWithMatch(console.log,"[trace] This is a trace");
|
||||||
sinon.assert.neverCalledWithMatch(util.log,"[metric] ");
|
sinon.assert.neverCalledWithMatch(console.log,"[metric] ");
|
||||||
});
|
});
|
||||||
it('ignores lower level messages but accepts metrics', function() {
|
it('ignores lower level messages but accepts metrics', function() {
|
||||||
var settings = {logging: { console: { level: 'log', metrics: true } } };
|
var settings = {logging: { console: { level: 'log', metrics: true } } };
|
||||||
@ -120,12 +121,12 @@ describe("@node-red/util/log", function() {
|
|||||||
log.debug("This is a debug");
|
log.debug("This is a debug");
|
||||||
log.trace("This is a trace");
|
log.trace("This is a trace");
|
||||||
log.log({level:log.METRIC,msg:"testMetric"});
|
log.log({level:log.METRIC,msg:"testMetric"});
|
||||||
sinon.assert.calledWithMatch(util.log,"[error] This is an error");
|
sinon.assert.calledWithMatch(console.log,"[error] This is an error");
|
||||||
sinon.assert.calledWithMatch(util.log,"[warn] This is a warn");
|
sinon.assert.calledWithMatch(console.log,"[warn] This is a warn");
|
||||||
sinon.assert.calledWithMatch(util.log,"[info] This is an info");
|
sinon.assert.calledWithMatch(console.log,"[info] This is an info");
|
||||||
sinon.assert.neverCalledWithMatch(util.log,"[debug] This is a debug");
|
sinon.assert.neverCalledWithMatch(console.log,"[debug] This is a debug");
|
||||||
sinon.assert.neverCalledWithMatch(util.log,"[trace] This is a trace");
|
sinon.assert.neverCalledWithMatch(console.log,"[trace] This is a trace");
|
||||||
sinon.assert.calledWithMatch(util.log,"[metric] ");
|
sinon.assert.calledWithMatch(console.log,"[metric] ");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('default settings set to INFO and metrics off', function() {
|
it('default settings set to INFO and metrics off', function() {
|
||||||
@ -136,12 +137,12 @@ describe("@node-red/util/log", function() {
|
|||||||
log.debug("This is a debug");
|
log.debug("This is a debug");
|
||||||
log.trace("This is a trace");
|
log.trace("This is a trace");
|
||||||
log.log({level:log.METRIC,msg:"testMetric"});
|
log.log({level:log.METRIC,msg:"testMetric"});
|
||||||
sinon.assert.calledWithMatch(util.log,"[error] This is an error");
|
sinon.assert.calledWithMatch(console.log,"[error] This is an error");
|
||||||
sinon.assert.calledWithMatch(util.log,"[warn] This is a warn");
|
sinon.assert.calledWithMatch(console.log,"[warn] This is a warn");
|
||||||
sinon.assert.calledWithMatch(util.log,"[info] This is an info");
|
sinon.assert.calledWithMatch(console.log,"[info] This is an info");
|
||||||
sinon.assert.neverCalledWithMatch(util.log,"[debug] This is a debug");
|
sinon.assert.neverCalledWithMatch(console.log,"[debug] This is a debug");
|
||||||
sinon.assert.neverCalledWithMatch(util.log,"[trace] This is a trace");
|
sinon.assert.neverCalledWithMatch(console.log,"[trace] This is a trace");
|
||||||
sinon.assert.neverCalledWithMatch(util.log,"[metric] ");
|
sinon.assert.neverCalledWithMatch(console.log,"[metric] ");
|
||||||
});
|
});
|
||||||
it('no logger used if custom logger handler does not exist', function() {
|
it('no logger used if custom logger handler does not exist', function() {
|
||||||
var settings = {logging: { customLogger: { level: 'trace', metrics: true } } };
|
var settings = {logging: { customLogger: { level: 'trace', metrics: true } } };
|
||||||
@ -152,12 +153,12 @@ describe("@node-red/util/log", function() {
|
|||||||
log.debug("This is a debug");
|
log.debug("This is a debug");
|
||||||
log.trace("This is a trace");
|
log.trace("This is a trace");
|
||||||
log.log({level:log.METRIC,msg:"testMetric"});
|
log.log({level:log.METRIC,msg:"testMetric"});
|
||||||
sinon.assert.neverCalledWithMatch(util.log,"[error] This is an error");
|
sinon.assert.neverCalledWithMatch(console.log,"[error] This is an error");
|
||||||
sinon.assert.neverCalledWithMatch(util.log,"[warn] This is a warn");
|
sinon.assert.neverCalledWithMatch(console.log,"[warn] This is a warn");
|
||||||
sinon.assert.neverCalledWithMatch(util.log,"[info] This is an info");
|
sinon.assert.neverCalledWithMatch(console.log,"[info] This is an info");
|
||||||
sinon.assert.neverCalledWithMatch(util.log,"[debug] This is a debug");
|
sinon.assert.neverCalledWithMatch(console.log,"[debug] This is a debug");
|
||||||
sinon.assert.neverCalledWithMatch(util.log,"[trace] This is a trace");
|
sinon.assert.neverCalledWithMatch(console.log,"[trace] This is a trace");
|
||||||
sinon.assert.neverCalledWithMatch(util.log,"[metric] ");
|
sinon.assert.neverCalledWithMatch(console.log,"[metric] ");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('add a custom log handler directly', function() {
|
it('add a custom log handler directly', function() {
|
||||||
@ -244,7 +245,7 @@ describe("@node-red/util/log", function() {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
var ret = log.info(msg.msg);
|
var ret = log.info(msg.msg);
|
||||||
sinon.assert.calledWithMatch(util.log,"my special message");
|
sinon.assert.calledWithMatch(console.log,"my special message");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user