diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8f3c8a6ce..85fc1f92a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,6 +5,9 @@ on: release: types: [published] +permissions: + contents: read + jobs: generate: name: 'Update node-red-docker image' diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0db909da6..b7f54c5f1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,8 +6,14 @@ on: pull_request: branches: [ master, dev ] +permissions: + contents: read + jobs: build: + permissions: + checks: write # for coverallsapp/github-action to create new checks + contents: read # for actions/checkout to fetch code runs-on: ubuntu-latest strategy: matrix: diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c6f52aeb..630d91b4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,37 @@ +#### 3.0.2: Maintenance Release + +Editor + + - Fix workspace chart bottom property (#3812) @bonanitech + - Update german translation (#3802) @Dennis14e + - Support color reset to the default in subflow and group (#3801) @kazuhitoyokoi + - Allow generateNodeNames to handle names containing regex control chars (#3817) @knolleary + - Hide scrollbars until they're needed (#3808) @bonanitech + - Include junctions/groups when exporting subflows plus related fixes (#3816) @knolleary + - remove console.log (#3820) @Steve-Mcl + +Runtime + + - Register subflow module instance node with parent flow (#3818) @knolleary + +Nodes + + - HTTP Request: Allow HTTP Headers not in spec (#3776) @hardillb + +#### 3.0.1: Maintenance Release + +Editor + + - Allow codeEditor theme to be set even if `codeEditor` is not set in settings.js (#3794) @Steve-Mcl + - Sys info (diagnostics report) amendments (#3793) @Steve-Mcl + - Allow `mode` and `title` to be omitted in `options` argument for `createEditor` (#3791) @Steve-Mcl + - Fix focus issues (#3789) @Steve-Mcl + - Ensure all typedInput buttons have button type set (#3788) @knolleary + - Do not flag hasUsers=false nodes as unused in search (#3787) @knolleary + - Properly position quick-add dialog in all cases (#3786) @knolleary + - Ensure quick-add dialog does not obscure ghost node when shifted (#3785) @knolleary + - Remove use of Object.hasOwn (#3784) @knolleary + #### 3.0.0: Milestone Release Editor diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/theme.js b/packages/node_modules/@node-red/editor-api/lib/editor/theme.js index 7be8868d3..c21a7e6e7 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/theme.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/theme.js @@ -327,9 +327,8 @@ module.exports = { themeContext.header.url = themePlugin.header.url || themeContext.header.url } } - if(theme.codeEditor) { - theme.codeEditor.options = Object.assign({}, themePlugin.monacoOptions, theme.codeEditor.options); - } + theme.codeEditor = theme.codeEditor || {} + theme.codeEditor.options = Object.assign({}, themePlugin.monacoOptions, theme.codeEditor.options); } activeThemeInitialised = true; } diff --git a/packages/node_modules/@node-red/editor-client/locales/de/editor.json b/packages/node_modules/@node-red/editor-client/locales/de/editor.json old mode 100755 new mode 100644 index 41fe1459a..93a2f5946 --- a/packages/node_modules/@node-red/editor-client/locales/de/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/de/editor.json @@ -105,7 +105,7 @@ "search": "Flows durchsuchen", "searchInput": "Flows durchsuchen", "subflows": "Subflow", - "createSubflow": "Subflow", + "createSubflow": "Hinzufügen", "selectionToSubflow": "Auswahl in Subflow umwandeln", "flows": "Flow", "add": "Hinzufügen", @@ -152,7 +152,8 @@ "zoom-in": "Vergrößern", "search-flows": "Flows durchsuchen", "search-prev": "Vorherige", - "search-next": "Nächste" + "search-next": "Nächste", + "search-counter": "\"__term__\" __result__ von __count__" }, "user": { "loggedInAs": "Angemeldet als __name__", @@ -168,7 +169,11 @@ } }, "notification": { - "warning": "Warnung: __message__", + "state": { + "flowsStopped": "Flows gestoppt", + "flowsStarted": "Flows gestartet" + }, + "warning": "Warnung: __message__", "warnings": { "undeployedChanges": "Node hat nicht übernommene (deploy) Änderungen", "nodeActionDisabled": "Node-Aktionen deaktiviert", @@ -177,15 +182,15 @@ "missing-modules": "

Flows angehalten aufgrund fehlender Module

", "safe-mode": "

Flows sind im abgesicherten Modus gestoppt.

Flows können bearbeitet und übernommen (deploy) werden, um sie neu zu starten.

", "restartRequired": "Node-RED muss neu gestartet werden, damit die Module nach Upgrade aktiviert werden", - "credentials_load_failed": "

Flows gestoppt, da die Berechtigungen nicht entschlüsselt werden konnten.

Die Datei mit dem Flow-Berechtigungen ist verschlüsselt, aber der Schlüssel des Projekts fehlt oder ist ungültig.

", - "credentials_load_failed_reset": "

Die Berechtigungen konnten nicht entschlüsselt werden.

Die Datei mit den Flow-Berechtigungen ist verschlüsselt, aber der Schlüssel des Projekts fehlt oder ist ungültig.

Die Datei mit den Flow-Berechtigungen wird bei der nächsten Übernahme (deploy) zurückgesetzt. Alle vorhandenen Flow-Berechtigungen werden gelöscht.

", + "credentials_load_failed": "

Flows gestoppt, da die Credentials nicht entschlüsselt werden konnten.

Die Datei mit den Flow-Credentials ist verschlüsselt, aber der Schlüssel des Projekts fehlt oder ist ungültig.

", + "credentials_load_failed_reset": "

Die Credentials konnten nicht entschlüsselt werden.

Die Datei mit den Flow-Credentials ist verschlüsselt, aber der Schlüssel des Projekts fehlt oder ist ungültig.

Die Datei mit den Flow-Credentials wird bei der nächsten Übernahme (deploy) zurückgesetzt. Alle vorhandenen Flow-Credentials werden gelöscht.

", "missing_flow_file": "

Die Flow-Datei des Projekts wurde nicht gefunden.

Das Projekt ist nicht mit einer Flow-Datei konfiguriert.

", "missing_package_file": "

Die Paket-Datei des Projekts wurde nicht gefunden.

In dem Projekt fehlt die 'package.json'-Datei.

", "project_empty": "

Das Projekt ist leer.

Soll ein Standardsatz an Projektdateien erstellen werden?
Andernfalls müssen die Dateien manuell außerhalb des Editors dem Projekt hinzugefügt werden.

", "project_not_found": "

Das Projekt '__project__' wurde nicht gefunden.

", "git_merge_conflict": "

Der automatische Merge der Änderungen ist fehlgeschlagen.

Die Merge-Konflikte müssen behoben und die Ergebnisse ins Repository übertragen werden (commit).

" }, - "error": "Fehler: __message__", + "error": "Fehler: __message__", "errors": { "lostConnection": "Verbindung zum Server verloren. Verbindung wird erneut hergestellt ...", "lostConnectionReconnect": "Verbindung zum Server verloren. Wiederherstellung der Verbindung in __time__s.", @@ -203,7 +208,7 @@ "pull": "Projekt '__project__' erneut geladen", "revert": "Änderungen im Projekt '__project__' rückgängig gemacht", "merge-complete": "Git-Merge abgeschlossen", - "setupCredentials": "Berechtigungen einrichten", + "setupCredentials": "Credentials einrichten", "setupProjectFiles": "Projektdateien einrichten", "no": "Nein, Danke", "createDefault": "Standardprojektdateien erstellen", @@ -211,7 +216,7 @@ }, "label": { "manage-project-dep": "Projektabhängigkeiten verwalten", - "setup-cred": "Berechtigungen einrichten", + "setup-cred": "Credentials einrichten", "setup-project": "Projektdateien einrichten", "create-default-package": "Standardpaketdatei erstellen", "no-thanks": "Nein, Danke", @@ -295,6 +300,10 @@ "modifiedFlowsDesc": "Übernimmt nur Flows, die geänderte Nodes enthalten", "modifiedNodes": "Geänderte Nodes", "modifiedNodesDesc": "Übernimmt nur Nodes, die sich geändert haben", + "startFlows": "Start", + "startFlowsDesc": "Flows starten", + "stopFlows": "Stop", + "stopFlowsDesc": "Flows stoppen", "restartFlows": "Flows neustarten", "restartFlowsDesc": "Startet die aktuell übernommenen Flows (ohne vorheriges Deploy)", "successfulDeploy": "Erfolgreich übernommen (deploy)", @@ -376,7 +385,7 @@ "confirmDelete": "Sind Sie sicher mit dem Löschen dieses Subflows?", "info": "Beschreibung", "category": "Kategorie", - "module": "Module", + "module": "Modul", "license": "Lizenz", "licenseNone": "Keine", "licenseOther": "Andere", @@ -434,7 +443,7 @@ "icon": "Icon", "inputType": "Eingangstyp", "selectType": "Wähle Typen ...", - "loadCredentials": "Lade Node-Berechtigungen", + "loadCredentials": "Lade Node-Credentials", "inputs": { "input": "Eingang", "select": "Auswahl", @@ -450,7 +459,7 @@ "json": "JSON", "bin": "buffer", "env": "Umgebungsvariable", - "cred": "Berechtigung" + "cred": "Credentials" }, "menu": { "input": "Eingang", @@ -470,7 +479,7 @@ "errors": { "scopeChange": "Wenn Sie den Geltungsbereich (scope) ändern, wird er für Nodes in anderen Flows nicht verfügbar sein", "invalidProperties": "Ungültige Eigenschaften:", - "credentialLoadFailed": "Laden der Node-Berechtigungen fehlgeschlagen" + "credentialLoadFailed": "Laden der Node-Credentials fehlgeschlagen" } }, "keyboard": { @@ -683,7 +692,8 @@ "showHelp": "Hilfe zeigen", "showInOutline": "Zeige im Editor", "showTopics": "Zeige Hilfethemen", - "noHelp": "Kein Hilfethema ausgewählt" + "noHelp": "Kein Hilfethema ausgewählt", + "changeLog": "Änderungsprotokoll" }, "config": { "name": "Konfigurations-Node", @@ -737,7 +747,7 @@ "addToProject": "Zu Projekt hinzufügen", "files": "Dateien", "flow": "Flow", - "credentials": "Berechtigungen", + "credentials": "Credentials", "package": "Paket", "packageCreate": "Datei wird erstellt beim Speichern der Änderungen", "fileNotExist": "Datei existiert nicht", @@ -750,7 +760,7 @@ "changeTheEncryptionKey": "Schlüssel ändern", "currentKey": "Aktueller Schlüssel", "newKey": "Neuer Schlüssel", - "credentialsAlert": "Dadurch werden alle vorhandenen Berechtigungen gelöscht", + "credentialsAlert": "Dadurch werden alle vorhandenen Credentials gelöscht", "versionControl": "Versionsverwaltung (Git)", "branches": "Branches", "noBranches": "Keine Branches", @@ -886,7 +896,7 @@ "date": "timestamp", "jsonata": "JSONata", "env": "Umgebungsvariable", - "cred": "Berechtigung" + "cred": "Credentials" } }, "editableList": { @@ -1026,7 +1036,7 @@ "passphrase": "Passphrase", "ssh-key-desc": "Bevor Sie ein Repository über SSH lokal klonen können, müssen Sie einen SSH-Schlüssel hinzufügen, um auf diesen zugreifen zu können", "ssh-key-add": "SSH-Schlüssel hinzufügen", - "credential-key": "Schlüssel für Berechtigungen", + "credential-key": "Schlüssel für Credentials", "cant-get-ssh-key": "Fehler! Der ausgewählte SSH-Schlüsselpfad kann nicht abgerufen werden", "already-exists2": "bereits vorhanden", "git-error": "Git-Fehler", @@ -1038,27 +1048,27 @@ "create": "Erstellen Sie Ihre Projektdateien", "desc0": "Ein Projekt enthält Ihre Flow-Dateien, eine README-Datei und die 'package.json'-Datei.", "desc1": "Es kann alle anderen Dateien enthalten, die im Git-Repository verwaltet werden sollen.", - "desc2": "Ihre vorhandenen Flow- und Berechtigungs-Dateien werden in das Projekt kopiert.", + "desc2": "Ihre vorhandenen Flow- und Credential-Dateien werden in das Projekt kopiert.", "flow-file": "Flow-Datei", - "credentials-file": "Datei mit Berechtigungen" + "credentials-file": "Datei mit Credentials" }, "encryption-config": { - "setup": "Einrichtung der Verschlüsselung Ihrer Datei mit den Berechtigungen", - "desc0": "Die Datei mit den Flow-Berechtigungen kann verschlüsselt werden, um ihren Inhalt zu schützen.", - "desc1": "Wenn Sie diese Berechtigungen in einem öffentlichen Repository speichern möchten, müssen Sie sie mit einen geheimen Schlüsselausdruck verschlüsseln.", - "desc2": "Die Datei mit den Flow-Berechtigungen ist derzeit nicht verschlüsselt.", + "setup": "Einrichtung der Verschlüsselung Ihrer Datei mit den Credentials", + "desc0": "Die Datei mit den Flow-Credentials kann verschlüsselt werden, um ihren Inhalt zu schützen.", + "desc1": "Wenn Sie diese Credentials in einem öffentlichen Repository speichern möchten, müssen Sie sie mit einen geheimen Schlüsselausdruck verschlüsseln.", + "desc2": "Die Datei mit den Flow-Credentials ist derzeit nicht verschlüsselt.", "desc3": "D.h. ihr Inhalt (z.B. Passwörter und Zugriffs-Tokens) kann von jedem mit Zugriff auf die Datei gelesen werden.", - "desc4": "Wenn Sie diese Berechtigungen in einen öffentlichen Repository speichern möchten, müssen Sie diese verschlüsseln, indem Sie einen geheimen Schlüsselausdruck eingeben.", - "desc5": "Ihre Datei mit den Flow-Berechtigungen wird derzeit mit dem Eintrag 'credentialSecret' Ihrer Einstellungsdatei als Schlüssel verschlüsselt.", - "desc6": "Die Datei mit den Flow-Berechtigungen wird derzeit mit einem vom System generierten Schlüssel verschlüsselt. Sie sollten einen neuen geheimen Schlüssel für dieses Projekt vorgeben.", + "desc4": "Wenn Sie diese Credentials in einen öffentlichen Repository speichern möchten, müssen Sie diese verschlüsseln, indem Sie einen geheimen Schlüsselausdruck eingeben.", + "desc5": "Ihre Datei mit den Flow-Credentials wird derzeit mit dem Eintrag 'credentialSecret' Ihrer Einstellungsdatei als Schlüssel verschlüsselt.", + "desc6": "Die Datei mit den Flow-Credentials wird derzeit mit einem vom System generierten Schlüssel verschlüsselt. Sie sollten einen neuen geheimen Schlüssel für dieses Projekt vorgeben.", "desc7": "Der Schlüssel wird separat von den Projektdateien gespeichert. Sie müssen den Schlüssel angeben, damit dieses Projekt auch in einem anderen Node-RED-System verwendet werden kann.", - "credentials": "Berechtigung", + "credentials": "Credentials", "enable": "Verschlüsselung aktivieren", "disable": "Verschlüsselung deaktivieren", "disabled": "deaktiviert", "copy": "Vorhandenen Schlüssel ersetzen", "use-custom": "Eigenen Schlüssel verwenden", - "desc8": "Die Datei mit den Berechtigungen wird nicht verschlüsselt, und ihr Inhalt kann leicht gelesen werden", + "desc8": "Die Datei mit den Credentials wird nicht verschlüsselt und ihr Inhalt kann leicht gelesen werden", "create-project-files": "Projektdateien erstellen", "create-project": "Projekt erstellen", "already-exists": "bereits vorhanden", @@ -1083,12 +1093,12 @@ "desc": "Beschreibung", "opt": "Optional", "flow-file": "Flow-Datei", - "credentials": "Berechtigungen", + "credentials": "Credentials", "enable-encryption": "Verschlüsselung aktivieren", "disable-encryption": "Verschlüsselung deaktivieren", "encryption-key": "Schlüssel", - "desc0": "Eine Floskel, mit der Sie Ihre Berechtigungen schützen", - "desc1": "Die Datei mit den Berechtigungen wird nicht verschlüsselt, und ihr Inhalt kann leicht gelesen werden", + "desc0": "Eine Ausdruck, mit der Sie Ihre Credentials schützen", + "desc1": "Die Datei mit den Credentials wird nicht verschlüsselt und ihr Inhalt kann leicht gelesen werden", "git-url": "Git-Repository-URL", "protocols": "https://, ssh:// oder file://", "auth-failed": "Authentifizierung fehlgeschlagen", @@ -1098,7 +1108,7 @@ "passphrase": "Passphrase", "desc2": "Bevor Sie ein Repository über SSH klonen können, müssen Sie einen SSH-Schlüssel hinzufügen, um auf diesen zu zugreifen", "add-ssh-key": "Einen SSH-Schlüssel hinzufügen", - "credentials-encryption-key": "Schlüssel für Berechtigungen", + "credentials-encryption-key": "Schlüssel für Credentials", "already-exists-2": "bereits vorhanden", "git-error": "Git-Fehler", "con-failed": "Verbindung fehlgeschlagen", @@ -1156,7 +1166,8 @@ "tourGuide": { "takeATour": "Tour starten", "start": "Start", - "next": "Nächste" + "next": "Nächste", + "welcomeTours": "Welcome Tours" }, "diagnostics": { "title": "System-Informationen" diff --git a/packages/node_modules/@node-red/editor-client/locales/de/infotips.json b/packages/node_modules/@node-red/editor-client/locales/de/infotips.json old mode 100755 new mode 100644 diff --git a/packages/node_modules/@node-red/editor-client/locales/de/jsonata.json b/packages/node_modules/@node-red/editor-client/locales/de/jsonata.json old mode 100755 new mode 100644 diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json old mode 100755 new mode 100644 index baaf338de..f5008b330 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -944,6 +944,9 @@ "invalid-expr": "Invalid JSONata expression:\n __message__", "invalid-msg": "Invalid example JSON message:\n __message__", "context-unsupported": "Cannot test context functions\n $flowContext or $globalContext", + "env-unsupported": "Cannot test $env function", + "moment-unsupported": "Cannot test $moment function", + "clone-unsupported": "Cannot test $clone function", "eval": "Error evaluating expression:\n __message__" } }, diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/infotips.json b/packages/node_modules/@node-red/editor-client/locales/en-US/infotips.json old mode 100755 new mode 100644 diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/jsonata.json b/packages/node_modules/@node-red/editor-client/locales/en-US/jsonata.json old mode 100755 new mode 100644 diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index 478d2b950..6e78b195f 100644 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -943,8 +943,11 @@ "errors": { "invalid-expr": "不正なJSONata式:\n __message__", "invalid-msg": "不正なJSONメッセージ例:\n __message__", - "context-unsupported": "$flowContext や $globalContextの\nコンテキスト機能をテストできません", - "eval": "表現評価エラー:\n __message__" + "context-unsupported": "$flowContext や $globalContextの\nコンテキスト関数をテストできません", + "env-unsupported": "$env関数はテストできません", + "moment-unsupported": "$moment関数はテストできません", + "clone-unsupported": "$clone関数はテストできません", + "eval": "式評価エラー:\n __message__" } }, "monaco": { diff --git a/packages/node_modules/@node-red/editor-client/locales/ko/editor.json b/packages/node_modules/@node-red/editor-client/locales/ko/editor.json old mode 100755 new mode 100644 diff --git a/packages/node_modules/@node-red/editor-client/locales/ko/infotips.json b/packages/node_modules/@node-red/editor-client/locales/ko/infotips.json old mode 100755 new mode 100644 diff --git a/packages/node_modules/@node-red/editor-client/locales/ko/jsonata.json b/packages/node_modules/@node-red/editor-client/locales/ko/jsonata.json old mode 100755 new mode 100644 diff --git a/packages/node_modules/@node-red/editor-client/locales/ru/editor.json b/packages/node_modules/@node-red/editor-client/locales/ru/editor.json old mode 100755 new mode 100644 diff --git a/packages/node_modules/@node-red/editor-client/locales/ru/infotips.json b/packages/node_modules/@node-red/editor-client/locales/ru/infotips.json old mode 100755 new mode 100644 diff --git a/packages/node_modules/@node-red/editor-client/locales/ru/jsonata.json b/packages/node_modules/@node-red/editor-client/locales/ru/jsonata.json old mode 100755 new mode 100644 diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 6dd500581..6223b218c 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -868,14 +868,7 @@ RED.nodes = (function() { var node; if (allNodes.hasTab(id)) { - removedNodes = allNodes.getNodes(id).filter(n => { - if (n.type === 'junction') { - removedJunctions.push(n) - return false - } else { - return true - } - }) + removedNodes = allNodes.getNodes(id).slice() } for (i in configNodes) { if (configNodes.hasOwnProperty(i)) { @@ -885,6 +878,7 @@ RED.nodes = (function() { } } } + removedJunctions = RED.nodes.junctions(id) for (i=0;i 0) { var reimportList = []; @@ -2753,6 +2751,12 @@ RED.nodes = (function() { } else { allNodes.removeNode(n); } + if (n.g) { + // reimporting a node *without* including its group object + // will cause the g property to be cleared. Cache it + // here so we can restore it + nodeGroupMap[n.id] = n.g + } reimportList.push(convertNode(n)); RED.events.emit('nodes:remove',n); }); @@ -2774,6 +2778,18 @@ RED.nodes = (function() { var newNodeMap = {}; result.nodes.forEach(function(n) { newNodeMap[n.id] = n; + if (nodeGroupMap[n.id]) { + // This node is in a group - need to substitute the + // node reference inside the group + n.g = nodeGroupMap[n.id] + const group = RED.nodes.group(n.g) + if (group) { + var index = group.nodes.findIndex(gn => gn.id === n.id) + if (index > -1) { + group.nodes[index] = n + } + } + } }); RED.nodes.eachLink(function(l) { if (newNodeMap.hasOwnProperty(l.source.id)) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/red.js b/packages/node_modules/@node-red/editor-client/src/js/red.js index 6c0adf21b..a8b8dfe86 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/red.js +++ b/packages/node_modules/@node-red/editor-client/src/js/red.js @@ -348,6 +348,8 @@ var RED = (function() { loader.end() RED.notify($("

").text(message)); RED.sidebar.info.refresh() + RED.menu.setDisabled('menu-item-projects-open',false); + RED.menu.setDisabled('menu-item-projects-settings',false); }); }); return; @@ -794,7 +796,7 @@ var RED = (function() { $('

').appendTo(header); $('
'+ '
'+ - '
'+ + '
'+ '
'+ '
'+ '
'+ diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js index ea1938e5c..f308614d7 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js @@ -160,7 +160,7 @@ this.element.css("maxHeight",null); } if (this.options.height !== 'auto') { - this.uiContainer.css("overflow-y","scroll"); + this.uiContainer.css("overflow-y","auto"); if (!isNaN(this.options.height)) { this.uiHeight = this.options.height; } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js index 549479d85..7440b464e 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js @@ -90,10 +90,10 @@ optEl.append(generateSpans(srcMatch)); optEl.appendTo(element); } - matches.push({ - value: optVal, - label: element, - i: (valMatch.found ? valMatch.index : srcMatch.index) + matches.push({ + value: optVal, + label: element, + i: (valMatch.found ? valMatch.index : srcMatch.index) }); } }) @@ -146,7 +146,7 @@ { value: "reset", source: ["delay","trigger","join","rbe"] }, { value: "responseCookies", source: ["http request"] }, { value: "responseTopic", source: ["mqtt"] }, - { value: "responseURL", source: ["http request"] }, + { value: "responseUrl", source: ["http request"] }, { value: "restartTimeout", source: ["join"] }, { value: "retain", source: ["mqtt"] }, { value: "schema", source: ["json"] }, @@ -501,7 +501,7 @@ this.options.types = this.options.types||Object.keys(allOptions); } - this.selectTrigger = $('').prependTo(this.uiSelect); + this.selectTrigger = $('').prependTo(this.uiSelect); $('').toggle(this.options.types.length > 1).appendTo(this.selectTrigger); this.selectLabel = $('').appendTo(this.selectTrigger); @@ -570,7 +570,7 @@ }) // explicitly set optionSelectTrigger display to inline-block otherwise jQ sets it to 'inline' - this.optionSelectTrigger = $('').appendTo(this.uiSelect); + this.optionSelectTrigger = $('').appendTo(this.uiSelect); this.optionSelectLabel = $('').prependTo(this.optionSelectTrigger); // RED.popover.tooltip(this.optionSelectLabel,function() { // return that.optionValue; @@ -591,7 +591,7 @@ that.uiSelect.addClass('red-ui-typedInput-focus'); }); - this.optionExpandButton = $('').appendTo(this.uiSelect); + this.optionExpandButton = $('').appendTo(this.uiSelect); this.optionExpandButtonIcon = $('').appendTo(this.optionExpandButton); this.type(this.typeField.val() || this.options.default||this.typeList[0].value); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js index fb4c200f5..da7954625 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js @@ -238,6 +238,7 @@ RED.editor = (function() { var valid = validateNodeProperty(node, defaults, property,value); if (((typeof valid) === "string") || !valid) { input.addClass("input-error"); + input.next(".red-ui-typedInput-container").addClass("input-error"); if ((typeof valid) === "string") { var tooltip = input.data("tooltip"); if (tooltip) { @@ -250,6 +251,7 @@ RED.editor = (function() { } } else { input.removeClass("input-error"); + input.next(".red-ui-typedInput-container").removeClass("input-error"); var tooltip = input.data("tooltip"); if (tooltip) { input.data("tooltip", null); @@ -1105,6 +1107,10 @@ RED.editor = (function() { if (editing_node) { RED.sidebar.info.refresh(editing_node); RED.sidebar.help.show(editing_node.type, false); + //ensure focused element is NOT body (for keyboard scope to operate correctly) + if (document.activeElement.tagName === 'BODY') { + $('#red-ui-editor-stack').trigger('focus') + } } } } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js index 701e3da44..68b9a487e 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js @@ -100,7 +100,7 @@ RED.editor.codeEditor.monaco = (function() { "node-red-util": {package: "node-red", module: "util", path: "node-red/util.d.ts" }, "node-red-func": {package: "node-red", module: "func", path: "node-red/func.d.ts" }, } - const defaultServerSideTypes = [ knownModules["node-red-util"], knownModules["node-red-func"], knownModules["globals"], knownModules["console"], knownModules["buffer"] ]; + const defaultServerSideTypes = [ knownModules["node-red-util"], knownModules["node-red-func"], knownModules["globals"], knownModules["console"], knownModules["buffer"] , knownModules["util"] ]; const modulesCache = {}; @@ -764,7 +764,7 @@ RED.editor.codeEditor.monaco = (function() { if(!options.stateId && options.stateId !== false) { - options.stateId = RED.editor.generateViewStateId("monaco", options, (options.mode || options.title).split("/").pop()); + options.stateId = RED.editor.generateViewStateId("monaco", options, (options.mode || options.title || "").split("/").pop()); } var el = options.element || $("#"+options.id)[0]; var toolbarRow = $("
").appendTo(el); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/colorPicker.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/colorPicker.js index 4b2e19e5c..5b76d020b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/colorPicker.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/colorPicker.js @@ -76,6 +76,9 @@ RED.editor.colorPicker = RED.colorPicker = (function() { var focusTarget = colorInput; colorInput.on("change", function (e) { var color = colorInput.val(); + if (options.defaultValue && !color.match(/^([a-z]+|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3})$/)) { + color = options.defaultValue; + } colorHiddenInput.val(color).trigger('change'); refreshDisplay(color); }); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/expression.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/expression.js index b3c4c3848..d470e14f2 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/expression.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/expression.js @@ -255,6 +255,9 @@ var currentExpression = expressionEditor.getValue(); var expr; var usesContext = false; + var usesEnv = false; + var usesMoment = false; + var usesClone = false; var legacyMode = /(^|[^a-zA-Z0-9_'".])msg([^a-zA-Z0-9_'"]|$)/.test(currentExpression); $(".red-ui-editor-type-expression-legacy").toggle(legacyMode); try { @@ -267,6 +270,18 @@ usesContext = true; return null; }); + expr.assign("env", function(name) { + usesEnv = true; + return null; + }); + expr.assign("moment", function(name) { + usesMoment = true; + return null; + }); + expr.assign("clone", function(name) { + usesClone = true; + return null; + }); } catch(err) { testResultEditor.setValue(RED._("expressionEditor.errors.invalid-expr",{message:err.message}),-1); return; @@ -284,6 +299,18 @@ testResultEditor.setValue(RED._("expressionEditor.errors.context-unsupported"),-1); return; } + if (usesEnv) { + testResultEditor.setValue(RED._("expressionEditor.errors.env-unsupported"),-1); + return; + } + if (usesMoment) { + testResultEditor.setValue(RED._("expressionEditor.errors.moment-unsupported"),-1); + return; + } + if (usesClone) { + testResultEditor.setValue(RED._("expressionEditor.errors.clone-unsupported"),-1); + return; + } var formattedResult; if (result !== undefined) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js index 912fa3528..e1e6b2ba3 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js @@ -235,6 +235,7 @@ RED.editor.colorPicker.create({ id: "red-ui-editor-node-color", value: color, + defaultValue: "#DDAA99", palette: recommendedColors, sortPalette: function (a, b) {return a.l - b.l;} }).appendTo(colorRow); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/group.js b/packages/node_modules/@node-red/editor-client/src/js/ui/group.js index add6da6c9..a35098e52 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/group.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/group.js @@ -101,6 +101,7 @@ RED.group = (function() { RED.editor.colorPicker.create({ id:"node-input-style-stroke", value: style.stroke || defaultGroupStyle.stroke || "#a4a4a4", + defaultValue: "#a4a4a4", palette: colorPalette, cellPerRow: colorCount, cellWidth: 16, @@ -112,6 +113,7 @@ RED.group = (function() { RED.editor.colorPicker.create({ id:"node-input-style-fill", value: style.fill || defaultGroupStyle.fill ||"none", + defaultValue: "none", palette: colorPalette, cellPerRow: colorCount, cellWidth: 16, @@ -129,6 +131,7 @@ RED.group = (function() { RED.editor.colorPicker.create({ id:"node-input-style-color", value: style.color || defaultGroupStyle.color ||"#a4a4a4", + defaultValue: "#a4a4a4", palette: colorPalette, cellPerRow: colorCount, cellWidth: 16, diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/library.js b/packages/node_modules/@node-red/editor-client/src/js/ui/library.js old mode 100755 new mode 100644 diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js old mode 100755 new mode 100644 index 9f20cc674..5499cb672 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js @@ -175,9 +175,19 @@ RED.palette = (function() { $('').appendTo(popOverContent) } - var safeType = type.replace(/'/g,"\\'"); + const safeType = type.replace(/'/g,"\\'"); + const wrapStr = function (str) { + if(str.indexOf(' ') >= 0) { + return '"' + str + '"' + } + return str + } - $('').appendTo(popOverContent) + $('') + .appendTo(popOverContent) + .on('click', function() { + RED.search.show('type:' + wrapStr(safeType)) + }) $('').appendTo(popOverContent) $('

',{style:"font-size: 0.8em"}).text(metaData).appendTo(popOverContent); @@ -422,6 +432,7 @@ RED.palette = (function() { categoryNode.find(".red-ui-palette-content").slideToggle(); categoryNode.find("i").toggleClass("expanded"); } + categoryNode.hide(); } } @@ -500,6 +511,7 @@ RED.palette = (function() { currentCategoryNode.find(".red-ui-palette-content").slideToggle(); currentCategoryNode.find("i").toggleClass("expanded"); } + currentCategoryNode.hide(); } } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js old mode 100755 new mode 100644 index 190561e15..c198b34db --- a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js @@ -545,7 +545,7 @@ RED.projects = (function() { var sshwarningRow = $('

').hide().appendTo(subrow); $('
'+RED._("projects.clone-project.ssh-key-desc")+'
').appendTo(sshwarningRow); subrow = $('
').appendTo(sshwarningRow); - $('').appendTo(subrow).on("click", function(e) { + $('').appendTo(subrow).on("click", function(e) { e.preventDefault(); dialog.dialog( "close" ); RED.userSettings.show('gitconfig'); @@ -1171,11 +1171,11 @@ RED.projects = (function() { row = $('
').appendTo(container); - var openProject = $('').appendTo(row); - var createAsEmpty = $('').appendTo(row); - // var createAsCopy = $('').appendTo(row); - var createAsClone = $('').appendTo(row); - // var createAsClone = $('').appendTo(row); + var openProject = $('').appendTo(row); + var createAsEmpty = $('').appendTo(row); + // var createAsCopy = $('').appendTo(row); + var createAsClone = $('').appendTo(row); + // var createAsClone = $('').appendTo(row); row.find(".red-ui-projects-dialog-screen-create-type").on("click", function(evt) { evt.preventDefault(); container.find(".red-ui-projects-dialog-screen-create-type").removeClass('selected'); @@ -1271,7 +1271,7 @@ RED.projects = (function() { var credentialsLeftBox = $('
').appendTo(credentialsBox); var credentialsEnabledBox = $('
').appendTo(credentialsLeftBox); - $('').appendTo(credentialsEnabledBox); + $('').appendTo(credentialsEnabledBox); var credentialsDisabledBox = $('
').appendTo(credentialsLeftBox); $('').appendTo(credentialsDisabledBox); @@ -1397,7 +1397,7 @@ RED.projects = (function() { var sshwarningRow = $('
').hide().appendTo(subrow); $('
'+RED._("projects.create.desc2")+'
').appendTo(sshwarningRow); subrow = $('
').appendTo(sshwarningRow); - $('').appendTo(subrow).on("click", function(e) { + $('').appendTo(subrow).on("click", function(e) { e.preventDefault(); $('#red-ui-projects-dialog-cancel').trigger("click"); RED.userSettings.show('gitconfig'); @@ -1631,14 +1631,14 @@ RED.projects = (function() { function deleteProject(row,name,done) { var cover = $('
').on("click", function(evt) { evt.stopPropagation(); }).appendTo(row); $('').text(RED._("projects.delete.confirm")).appendTo(cover); - $('') + $('') .appendTo(cover) .on("click", function(e) { e.stopPropagation(); cover.remove(); done(true); }); - $('') + $('') .appendTo(cover) .on("click", function(e) { e.stopPropagation(); @@ -1822,7 +1822,7 @@ RED.projects = (function() { header.addClass("selectable"); var tools = $('
').appendTo(header); - $('') + $('') .appendTo(tools) .on("click", function(e) { e.stopPropagation(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/search.js b/packages/node_modules/@node-red/editor-client/src/js/ui/search.js index 217fb5a4e..3903a4a0a 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/search.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/search.js @@ -106,38 +106,51 @@ RED.search = (function() { return val; } - function search(val) { - var results = []; - var typeFilter; - var m = /(?:^| )type:([^ ]+)/.exec(val); - if (m) { - val = val.replace(/(?:^| )type:[^ ]+/,""); - typeFilter = m[1]; + function extractType(val, flags) { + // extracts: type:XYZ & type:"X Y Z" + const regEx = /(?:type):\s*(?:"([^"]+)"|([^" ]+))/; + let m + while ((m = regEx.exec(val)) !== null) { + // avoid infinite loops with zero-width matches + if (m.index === regEx.lastIndex) { + regEx.lastIndex++; + } + val = val.replace(m[0]," ").trim() + const flag = m[2] || m[1] // quoted entries in capture group 1, unquoted in capture group 2 + flags.type = flags.type || []; + flags.type.push(flag); } - var flags = {}; + return val; + } + + function search(val) { + const results = []; + const flags = {}; val = extractFlag(val,"invalid",flags); val = extractFlag(val,"unused",flags); val = extractFlag(val,"config",flags); val = extractFlag(val,"subflow",flags); val = extractFlag(val,"hidden",flags); val = extractFlag(val,"modified",flags); - val = extractValue(val,"flow",flags);// flow:active or flow: + val = extractValue(val,"flow",flags);// flow:current or flow: val = extractValue(val,"uses",flags);// uses: + val = extractType(val,flags);// type: val = val.trim(); - var hasFlags = Object.keys(flags).length > 0; + const hasFlags = Object.keys(flags).length > 0; + const hasTypeFilter = flags.type && flags.type.length > 0 if (flags.flow && flags.flow.indexOf("current") >= 0) { let idx = flags.flow.indexOf("current"); - flags.flow[idx] = RED.workspaces.active();//convert active to flow ID + flags.flow[idx] = RED.workspaces.active();//convert 'current' to active flow ID } if (flags.flow && flags.flow.length) { flags.flow = [ ...new Set(flags.flow) ]; //deduplicate } - if (val.length > 0 || typeFilter || hasFlags) { + if (val.length > 0 || hasFlags) { val = val.toLowerCase(); - var i; - var j; - var list = []; - var nodes = {}; + let i; + let j; + let list = []; + const nodes = {}; let keys = []; if (flags.uses) { keys = flags.uses; @@ -145,10 +158,10 @@ RED.search = (function() { keys = Object.keys(index); } for (i=0;i -1) { - var ids = Object.keys(index[key]||{}); + const key = keys[i]; + const kpos = val ? keys[i].indexOf(val) : -1; + if (kpos > -1 || (val === "" && hasFlags)) { + const ids = Object.keys(index[key]||{}); for (j=0;j -1) { + nodes[node.node.id] = nodes[node.node.id] || { node: node.node, label: node.label }; - nodes[node.node.id].index = Math.min(nodes[node.node.id].index||Infinity,kpos); + nodes[node.node.id].index = Math.min(nodes[node.node.id].index || Infinity, typeIndex > -1 ? typeIndex : kpos); } } } @@ -538,7 +555,7 @@ RED.search = (function() { $(previousActiveElement).trigger("focus"); } previousActiveElement = null; - } + } if(!keepSearchToolbar) { clearActiveSearch(); } @@ -630,7 +647,7 @@ RED.search = (function() { $("#red-ui-sidebar-shade").on('mousedown',hide); $("#red-ui-view-searchtools-close").on("click", function close() { - clearActiveSearch(); + clearActiveSearch(); updateSearchToolbar(); }); $("#red-ui-view-searchtools-close").trigger("click"); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js index e979adf65..95e030be1 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js @@ -510,6 +510,13 @@ RED.subflow = (function() { RED.nodes.groups(id).forEach(function(n) { removedGroups.push(n); }) + + var removedJunctions = RED.nodes.junctions(id) + for (var i=0;i", {class: "red-ui-sidebar-help-toc"}).appendTo(stackContainer); var helpPanel = $("
").css({ - "overflow-y": "scroll" + "overflow-y": "auto" }).appendTo(stackContainer); panels = RED.panels.create({ diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js index 07818ffd2..e38b0b67e 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js @@ -108,7 +108,7 @@ RED.sidebar.info = (function() { propertiesPanelContent = $("
").css({ "flex":"1 1 auto", - "overflow-y":"scroll", + "overflow-y":"auto", }).appendTo(propertiesPanel); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js index fc5b8e99e..989cb78ab 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js @@ -269,8 +269,8 @@ RED.typeSearch = (function() { moveCallback = opts.move; RED.events.emit("type-search:open"); //shade.show(); - if ($("#red-ui-main-container").height() - opts.y - 150 < 0) { - opts.y = opts.y - 235; + if ($("#red-ui-main-container").height() - opts.y - 195 < 0) { + opts.y = opts.y - 275; } dialog.css({left:opts.x+"px",top:opts.y+"px"}).show(); searchResultsDiv.slideDown(300); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js index 0f02077d8..ce433a3c6 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js @@ -1015,7 +1015,7 @@ RED.view.tools = (function() { const nodeDef = n._def || RED.nodes.getType(n.type) if (nodeDef && nodeDef.defaults && nodeDef.defaults.name) { const paletteLabel = RED.utils.getPaletteLabel(n.type, nodeDef) - const defaultNodeNameRE = new RegExp('^'+paletteLabel+' (\\d+)$') + const defaultNodeNameRE = new RegExp('^'+paletteLabel.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')+' (\\d+)$') if (!typeIndex.hasOwnProperty(n.type)) { const existingNodes = RED.nodes.filterNodes({type: n.type}) let maxNameNumber = 0; diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js old mode 100755 new mode 100644 index dafcddda7..8fb54d379 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -1072,12 +1072,15 @@ RED.view = (function() { RED.view.redraw(); } + // `point` is the place in the workspace the mouse has clicked. + // This takes into account scrolling and scaling of the workspace. var ox = point[0]; var oy = point[1]; + // Need to map that to browser location to position the pop-up const offset = $("#red-ui-workspace-chart").offset() - var clientX = ox + offset.left - $("#red-ui-workspace-chart").scrollLeft() - var clientY = oy + offset.top - $("#red-ui-workspace-chart").scrollTop() + var clientX = (ox * scaleFactor) + offset.left - $("#red-ui-workspace-chart").scrollLeft() + var clientY = (oy * scaleFactor) + offset.top - $("#red-ui-workspace-chart").scrollTop() if (RED.settings.get("editor").view['view-snap-grid']) { // eventLayer.append("circle").attr("cx",point[0]).attr("cy",point[1]).attr("r","2").attr('fill','red') @@ -3367,6 +3370,9 @@ RED.view = (function() { } if (dblClickPrimed && mousedown_node == d && clickElapsed > 0 && clickElapsed < dblClickInterval) { mouse_mode = RED.state.DEFAULT; + // Avoid dbl click causing text selection. + d3.event.preventDefault() + document.getSelection().removeAllRanges() if (d.type != "subflow") { if (/^subflow:/.test(d.type) && (d3.event.ctrlKey || d3.event.metaKey)) { RED.workspaces.show(d.type.substring(8)); @@ -4907,7 +4913,7 @@ RED.view = (function() { if (d._def.button) { var buttonEnabled = isButtonEnabled(d); this.__buttonGroup__.classList.toggle("red-ui-flow-node-button-disabled", !buttonEnabled); - if (RED.runtime && Object.hasOwn(RED.runtime,'started')) { + if (RED.runtime && RED.runtime.started !== undefined) { this.__buttonGroup__.classList.toggle("red-ui-flow-node-button-stopped", !RED.runtime.started); } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/debug.scss b/packages/node_modules/@node-red/editor-client/src/sass/debug.scss index 58099877f..eb550c6f5 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/debug.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/debug.scss @@ -30,7 +30,7 @@ bottom: 0px; left:0px; right: 0px; - overflow-y: scroll; + overflow-y: auto; } .red-ui-debug-filter-box { position:absolute; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/editor.scss b/packages/node_modules/@node-red/editor-client/src/sass/editor.scss index 1730b9e35..b6d12faf2 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/editor.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/editor.scss @@ -368,7 +368,7 @@ button.red-ui-button-small border:1px solid var(--red-ui-secondary-border-color); border-radius:5px; height: calc(100% - 21px); - overflow-y: scroll; + overflow-y: auto; background: var(--red-ui-secondary-background); } @@ -562,7 +562,7 @@ div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle { .red-ui-icon-list { width: 308px; height: 200px; - overflow-y: scroll; + overflow-y: auto; line-height: 0px; position: relative; &.red-ui-icon-list-dark { diff --git a/packages/node_modules/@node-red/editor-client/src/sass/notifications.scss b/packages/node_modules/@node-red/editor-client/src/sass/notifications.scss index efae432b2..7d7544e2e 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/notifications.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/notifications.scss @@ -32,7 +32,8 @@ color: var(--red-ui-primary-text-color); border: 1px solid var(--red-ui-notification-border-default); border-left-width: 16px; - overflow: hidden; + overflow: scroll; + max-height: 80vh; .ui-dialog-buttonset { margin-top: 20px; margin-bottom: 10px; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/projects.scss b/packages/node_modules/@node-red/editor-client/src/sass/projects.scss index ee43c7a87..a32fd53b4 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/projects.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/projects.scss @@ -26,7 +26,7 @@ } } #red-ui-project-settings-tab-settings { - overflow-y: scroll; + overflow-y: auto; } .red-ui-sidebar-vc-shade { background: var(--red-ui-primary-background); @@ -183,7 +183,7 @@ } .red-ui-projects-dialog-project-list-inner-container { flex-grow: 1 ; - overflow-y: scroll; + overflow-y: auto; position:relative; .red-ui-editableList-border { border: none; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/radialMenu.scss b/packages/node_modules/@node-red/editor-client/src/sass/radialMenu.scss index 3348e945a..db1bba3bb 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/radialMenu.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/radialMenu.scss @@ -41,6 +41,7 @@ height: 50px; background: var(--red-ui-secondary-background); border: 2px solid var(--red-ui-primary-border-color); + color: var(--red-ui-primary-text-color); text-align: center; line-height:50px; @@ -51,7 +52,7 @@ .red-ui-editor-radial-menu-opt-disabled { border-color: var(--red-ui-tertiary-border-color); - color: var(--red-ui-tertiary-border-color); + color: var(--red-ui-secondary-text-color-disabled); } .red-ui-editor-radial-menu-opt-active { background: var(--red-ui-secondary-background-hover); diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-context.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-context.scss index fc4c78afb..94450f337 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tab-context.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-context.scss @@ -20,7 +20,7 @@ bottom: 0; left: 0; right: 0; - overflow-y: scroll; + overflow-y: auto; .red-ui-palette-category { &:not(.expanded) button { diff --git a/packages/node_modules/@node-red/editor-client/src/sass/userSettings.scss b/packages/node_modules/@node-red/editor-client/src/sass/userSettings.scss index 36ab67e3f..5e0c7fa47 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/userSettings.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/userSettings.scss @@ -67,7 +67,7 @@ left: 0; bottom: 0; padding: 8px 20px 20px; - overflow-y: scroll; + overflow-y: auto; } .red-ui-settings-row { padding: 5px 10px 2px; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss b/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss index 24e156b1e..a5734570d 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss @@ -29,7 +29,7 @@ #red-ui-workspace-chart { overflow: auto; position: absolute; - bottom:25px; + bottom:26px; top: 35px; left:0px; right:0px; diff --git a/packages/node_modules/@node-red/editor-client/src/types/node-red/func.d.ts b/packages/node_modules/@node-red/editor-client/src/types/node-red/func.d.ts index ae411f33c..fd2adcbd8 100644 --- a/packages/node_modules/@node-red/editor-client/src/types/node-red/func.d.ts +++ b/packages/node_modules/@node-red/editor-client/src/types/node-red/func.d.ts @@ -14,6 +14,9 @@ declare var msg: NodeMessage; /** @type {string} the id of the incoming `msg` (alias of msg._msgid) */ declare const __msgid__:string; +declare const util:typeof import('util') +declare const promisify:typeof import('util').promisify + /** * @typedef NodeStatus * @type {object} diff --git a/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js b/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js index 3d1af605c..2431a8bbd 100644 --- a/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js +++ b/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js @@ -160,6 +160,7 @@ '$base64encode':{ args:[ ]}, '$boolean':{ args:[ 'arg' ]}, '$ceil':{ args:[ 'number' ]}, + '$clone': { args:[ 'arg' ]}, '$contains':{ args:[ 'str', 'pattern' ]}, '$count':{ args:[ 'array' ]}, '$decodeUrl':{ args:[ 'str' ]}, diff --git a/packages/node_modules/@node-red/nodes/core/common/20-inject.js b/packages/node_modules/@node-red/nodes/core/common/20-inject.js index 734bce765..3f2992cd5 100644 --- a/packages/node_modules/@node-red/nodes/core/common/20-inject.js +++ b/packages/node_modules/@node-red/nodes/core/common/20-inject.js @@ -95,45 +95,64 @@ module.exports = function(RED) { } this.on("input", function(msg, send, done) { - var errors = []; - var props = this.props; + const errors = []; + let props = this.props; if (msg.__user_inject_props__ && Array.isArray(msg.__user_inject_props__)) { props = msg.__user_inject_props__; } delete msg.__user_inject_props__; - props.forEach(p => { - var property = p.p; - var value = p.v ? p.v : ''; - var valueType = p.vt ? p.vt : 'str'; + props = [...props] + function evaluateProperty(doneEvaluating) { + if (props.length === 0) { + doneEvaluating() + return + } + const p = props.shift() + const property = p.p; + const value = p.v ? p.v : ''; + const valueType = p.vt ? p.vt : 'str'; - if (!property) return; - - if (valueType === "jsonata") { - if (p.v) { - try { - var exp = RED.util.prepareJSONataExpression(p.v, node); - var val = RED.util.evaluateJSONataExpression(exp, msg); - RED.util.setMessageProperty(msg, property, val, true); + if (property) { + if (valueType === "jsonata") { + if (p.v) { + try { + var exp = RED.util.prepareJSONataExpression(p.v, node); + var val = RED.util.evaluateJSONataExpression(exp, msg); + RED.util.setMessageProperty(msg, property, val, true); + } + catch (err) { + errors.push(err.message); + } } - catch (err) { - errors.push(err.message); + evaluateProperty(doneEvaluating) + } else { + try { + RED.util.evaluateNodeProperty(value, valueType, node, msg, (err, newValue) => { + if (err) { + errors.push(err.toString()) + } else { + RED.util.setMessageProperty(msg,property,newValue,true); + } + evaluateProperty(doneEvaluating) + }) + } catch (err) { + errors.push(err.toString()); + evaluateProperty(doneEvaluating) } } - return; + } else { + evaluateProperty(doneEvaluating) } - try { - RED.util.setMessageProperty(msg,property,RED.util.evaluateNodeProperty(value, valueType, this, msg),true); - } catch (err) { - errors.push(err.toString()); - } - }); - - if (errors.length) { - done(errors.join('; ')); - } else { - send(msg); - done(); } + + evaluateProperty(() => { + if (errors.length) { + done(errors.join('; ')); + } else { + send(msg); + done(); + } + }) }); } diff --git a/packages/node_modules/@node-red/nodes/core/common/24-complete.html b/packages/node_modules/@node-red/nodes/core/common/24-complete.html index a6a7a2a45..b2d406bb5 100644 --- a/packages/node_modules/@node-red/nodes/core/common/24-complete.html +++ b/packages/node_modules/@node-red/nodes/core/common/24-complete.html @@ -1,6 +1,6 @@ diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/function/80-template.html b/packages/node_modules/@node-red/nodes/locales/en-US/function/80-template.html index 07027e76e..52b6c2920 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/function/80-template.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/function/80-template.html @@ -52,4 +52,7 @@ used to mark the templated sections. For example, to use [[ ]] instead, add the following line to the top of the template:

{{=[[ ]]=}}
+

Using environment variables

+

The template node can access environment variables using the syntax:

+
My favourite colour is {{env.COLOUR}}.
diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index 3ba344ae0..248bb3415 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -446,7 +446,9 @@ "staticTopic": "Subscribe to single topic", "dynamicTopic": "Dynamic subscription", "auto-connect": "Connect automatically", - "auto-mode-depreciated": "This option is depreciated. Please use the new auto-detect mode." + "auto-mode-depreciated": "This option is depreciated. Please use the new auto-detect mode.", + "none": "none", + "other": "other" }, "sections-label": { "birth-message": "Message sent on connection (birth message)", @@ -554,7 +556,8 @@ }, "status": { "requesting": "requesting" - } + }, + "insecureHTTPParser": "Disable strict HTTP parsing" }, "websocket": { "label": { diff --git a/packages/node_modules/@node-red/nodes/locales/ja/function/80-template.html b/packages/node_modules/@node-red/nodes/locales/ja/function/80-template.html index 1a9c17453..9b64003a5 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/function/80-template.html +++ b/packages/node_modules/@node-red/nodes/locales/ja/function/80-template.html @@ -48,4 +48,7 @@

注: デフォルトでは、mustache形式は置換対象のHTML要素をエスケープします。これを抑止するには{{{三重}}}括弧形式を使います。

もし、コンテンツの中で{{ }}を出力する必要がある場合は、テンプレートで使われる記号文字を変えることもできます。例えば、[[ ]]を代わりに用いるには、テンプレートの先頭に以下の行を追加します。

{{=[[ ]]=}}
+

環境変数の利用

+

templateノードでは、次の構文を用いると環境変数にアクセスできます:

+
私の好きな色は{{env.COLOUR}}です。
diff --git a/packages/node_modules/@node-red/nodes/locales/ja/messages.json b/packages/node_modules/@node-red/nodes/locales/ja/messages.json index 2365f7f7a..263fbce97 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ja/messages.json @@ -446,7 +446,9 @@ "staticTopic": "1つのトピックを購読", "dynamicTopic": "動的な購読", "auto-connect": "自動接続", - "auto-mode-depreciated": "本オプションは非推奨になりました。新しい自動判定モードを使用してください。" + "auto-mode-depreciated": "本オプションは非推奨になりました。新しい自動判定モードを使用してください。", + "none": "なし", + "other": "その他" }, "sections-label": { "birth-message": "接続時の送信メッセージ(Birthメッセージ)", @@ -554,7 +556,8 @@ }, "status": { "requesting": "要求中" - } + }, + "insecureHTTPParser": "厳密なHTTPパース処理を無効化" }, "websocket": { "label": { diff --git a/packages/node_modules/@node-red/nodes/locales/ko/messages.json b/packages/node_modules/@node-red/nodes/locales/ko/messages.json old mode 100755 new mode 100644 diff --git a/packages/node_modules/@node-red/nodes/locales/ru/messages.json b/packages/node_modules/@node-red/nodes/locales/ru/messages.json old mode 100755 new mode 100644 diff --git a/packages/node_modules/@node-red/registry/lib/loader.js b/packages/node_modules/@node-red/registry/lib/loader.js index 61f28ab86..d125156ab 100644 --- a/packages/node_modules/@node-red/registry/lib/loader.js +++ b/packages/node_modules/@node-red/registry/lib/loader.js @@ -43,37 +43,40 @@ function load(disableNodePathScan) { return loadModuleFiles(modules); } +function splitPath(p) { + return path.posix.normalize((p || '').replace(/\\/g, path.sep)).split(path.sep) +} function loadModuleTypeFiles(module, type) { const things = module[type]; - var first = true; - var promises = []; - for (var thingName in things) { + let first = true; + const promises = []; + for (let thingName in things) { /* istanbul ignore else */ if (things.hasOwnProperty(thingName)) { if (module.name != "node-red" && first) { // Check the module directory exists first = false; - var fn = things[thingName].file; - var parts = fn.split("/"); - var i = parts.length-1; - for (;i>=0;i--) { - if (parts[i] == "node_modules") { - break; - } + let moduleFn = module.path + const fn = things[thingName].file + const parts = splitPath(fn) + const nmi = parts.indexOf('node_modules') + if(nmi > -1) { + moduleFn = parts.slice(0,nmi+2).join(path.sep); + } + if (!moduleFn) { + // shortcut - skip calling statSync on empty string + break; // Module not found, don't attempt to load its nodes } - var moduleFn = parts.slice(0,i+2).join("/"); - try { - var stat = fs.statSync(moduleFn); + const stat = fs.statSync(moduleFn); } catch(err) { - // Module not found, don't attempt to load its nodes - break; + break; // Module not found, don't attempt to load its nodes } } try { - var promise; + let promise; if (type === "nodes") { promise = loadNodeConfig(things[thingName]); } else if (type === "plugins") { @@ -82,8 +85,7 @@ function loadModuleTypeFiles(module, type) { promises.push( promise.then( (function() { - var m = module.name; - var n = thingName; + const n = thingName; return function(nodeSet) { things[n] = nodeSet; return nodeSet; @@ -93,7 +95,6 @@ function loadModuleTypeFiles(module, type) { ); } catch(err) { console.log(err) - // } } } @@ -125,38 +126,24 @@ function loadModuleFiles(modules) { } var pluginList; var nodeList; - return Promise.all(pluginPromises).then(function(results) { pluginList = results.filter(r => !!r); - // Initial plugin load has happened. Ensure modules that provide - // plugins are in the registry now. - for (var module in modules) { - if (modules.hasOwnProperty(module)) { - if (modules[module].plugins && Object.keys(modules[module].plugins).length > 0) { - // Add the modules for plugins - if (!modules[module].err) { - registry.addModule(modules[module]); - } - } - } - } - return loadNodeSetList(pluginList); - }).then(function() { - return Promise.all(nodePromises); + return Promise.all(nodePromises) }).then(function(results) { nodeList = results.filter(r => !!r); // Initial node load has happened. Ensure remaining modules are in the registry for (var module in modules) { if (modules.hasOwnProperty(module)) { - if (!modules[module].plugins || Object.keys(modules[module].plugins).length === 0) { - if (!modules[module].err) { - registry.addModule(modules[module]); - } + if (!modules[module].err) { + registry.addModule(modules[module]); } } } + }).then(function() { + return loadNodeSetList(pluginList); + }).then(function() { return loadNodeSetList(nodeList); - }); + }) } async function loadPluginTemplate(plugin) { diff --git a/packages/node_modules/@node-red/registry/lib/localfilesystem.js b/packages/node_modules/@node-red/registry/lib/localfilesystem.js index da4006ecc..0c231552f 100644 --- a/packages/node_modules/@node-red/registry/lib/localfilesystem.js +++ b/packages/node_modules/@node-red/registry/lib/localfilesystem.js @@ -106,8 +106,8 @@ function getLocalNodeFiles(dir, skipValidNodeRedModules) { // when loading local files, if the path is a valid node-red module // dont include it (will be picked up in scanTreeForNodesModules) if(skipValidNodeRedModules && files.indexOf("package.json") >= 0) { - const package = getPackageDetails(dir) - if(package.isNodeRedModule) { + const packageDetails = getPackageDetails(dir) + if(packageDetails.isNodeRedModule) { return {files: [], icons: []}; } } @@ -135,17 +135,17 @@ function getLocalNodeFiles(dir, skipValidNodeRedModules) { return {files: result, icons: icons} } -function scanDirForNodesModules(dir,moduleName,package) { +function scanDirForNodesModules(dir,moduleName,packageDetails) { let results = []; let scopeName; let files try { let isNodeRedModule = false - if(package) { - dir = path.join(package.moduleDir,'..') - files = [path.basename(package.moduleDir)] - moduleName = (package.package ? package.package.name : null) || moduleName - isNodeRedModule = package.isNodeRedModule + if(packageDetails) { + dir = path.join(packageDetails.moduleDir,'..') + files = [path.basename(packageDetails.moduleDir)] + moduleName = (packageDetails.package ? packageDetails.package.name : null) || moduleName + isNodeRedModule = packageDetails.isNodeRedModule } else { files = fs.readdirSync(dir); if (moduleName) { @@ -156,6 +156,16 @@ function scanDirForNodesModules(dir,moduleName,package) { } } } + + // if we have found a package.json, this IS a node_module, lets see if it is a node-red node + if (!isNodeRedModule && files.indexOf('package.json') > -1) { + packageDetails = getPackageDetails(dir) // get package details + if(packageDetails && packageDetails.isNodeRedModule) { + isNodeRedModule = true + files = ['package.json'] // shortcut the file scan + } + } + for (let i=0;i { rs.close(); @@ -419,7 +421,10 @@ module.exports = { }); }, initRepo: function(cwd) { - return runGitCommand(["init"],cwd); + var args = ["init", "--initial-branch", "main"]; + return runGitCommand(args, cwd).catch(function () { + return runGitCommand(["init"], cwd); + }); }, setUpstream: function(cwd,remoteBranch) { var args = ["branch","--set-upstream-to",remoteBranch]; diff --git a/packages/node_modules/@node-red/runtime/locales/de/runtime.json b/packages/node_modules/@node-red/runtime/locales/de/runtime.json old mode 100755 new mode 100644 index 6811ba86a..0249d638d --- a/packages/node_modules/@node-red/runtime/locales/de/runtime.json +++ b/packages/node_modules/@node-red/runtime/locales/de/runtime.json @@ -1,6 +1,6 @@ { "runtime": { - "welcome": "Willkommen bei Node-RED!", + "welcome": "Willkommen bei Node-RED", "version": "__component__ Version: __version__", "unsupported_version": "Nicht unterstützte Version von __component__. Erforderlich: __requires__, jedoch gefunden: __version__", "paths": { @@ -8,7 +8,6 @@ "httpStatic": "HTTP-Statisch: __path__" } }, - "server": { "loading": "Paletten-Nodes werden geladen", "palette-editor": { @@ -34,17 +33,19 @@ "install-failed-not-found": "Das Modul $t(server.install.install-failed-long) wurde nicht gefunden", "install-failed-name": "$t(server.install.install-failed-long). Ungültiger Modulname: __name__", "install-failed-url": "$t(server.install.install-failed-long). Ungültige URL: __url__", + "post-install-error": "Fehler bei der Ausführung des 'postInstall'-Hooks:", "upgrading": "Upgrade von Modul __name__ auf Version __version__ gestartet", "upgraded": "Upgrade von Modul __name__ war erfolgreich. Neustart von Node-RED für die Verwendung der neuen Version erforderlich.", "upgrade-failed-not-found": "Upgrade fehlgeschlagen. $t(server.install.install-failed-long). Version nicht gefunden.", "uninstalling": "Das Modul __name__ wird deinstalliert", "uninstall-failed": "Deinstallation fehlgeschlagen", "uninstall-failed-long": "Die Deinstallation des Moduls __name__ ist fehlgeschlagen:", - "uninstalled": "Das Modul __name__ ist deinstalliert" + "uninstalled": "Das Modul __name__ ist deinstalliert", + "old-ext-mod-dir-warning": "\n\n---------------------------------------------------------------------\nNode-RED 1.3 Verzeichnis externer Module erkannt:\n __oldDir__\nDieses Verzeichnis wird nicht mehr verwendet. Die externen Module werden\nin Ihrem Node-RED-Benutzerverzeichnis neu installiert:\n __newDir__\nLöschen Sie das alte externalModules-Verzeichnis, um diese Meldung abzustellen.\n---------------------------------------------------------------------\n" }, "deprecatedOption": "Die Verwendung von __old__ ist abgekündigt. Stattdessen __new__ verwenden.", "unable-to-listen": "Überwachen (listen) von __listenpath__ nicht möglich", - "port-in-use": "FEHLER: Port wird verwendet", + "port-in-use": "Fehler: Port wird verwendet", "uncaught-exception": "Nicht abgefangene Ausnahmebedingung:", "admin-ui-disabled": "Administrator-Benutzeroberfläche deaktiv", "now-running": "Server wird jetzt auf __listenpath__ ausgeführt", @@ -55,11 +56,10 @@ "refresh-interval": "Erneuerung der https-Einstellungen erfolgt alle __interval__ Stunden", "settings-refreshed": "https-Einstellungen wurden erneuert", "refresh-failed": "Erneuerung der https-Einstellungen fehlgeschlagen: __message__", - "nodejs-version": "httpsRefreshInterval erfordert Node.js 11 or later", + "nodejs-version": "httpsRefreshInterval erfordert Node.js 11 oder höher", "function-required": "httpsRefreshInterval erfordert die https-Eigenschaft in Form einer Funktion" } }, - "api": { "flows": { "error-save": "Fehler beim Speichern der Flows: __message__", @@ -77,17 +77,16 @@ "error-enable": "Der Node konnte nicht aktiviert werden:" } }, - "comms": { "error": "Kommunikationskanal-Fehler: __message__", "error-server": "Kommunikationsserver-Fehler: __message__", "error-send": "Kommunikationsende-Fehler: __message__" }, - "settings": { "user-not-available": "Einstellungen konnten nicht gespeichert werden: __message__", "not-available": "Einstellungen nicht verfügbar", - "property-read-only": "Die Eigenschaft '__prop__ 'ist schreibgeschützt" + "property-read-only": "Die Eigenschaft '__prop__' ist schreibgeschützt", + "readonly-mode": "Laufzeitumgebung im Nur-Lese-Modus. Änderungen werden nicht gespeichert." }, "library": { "unknownLibrary": "Unbekannte Bibliothek (Library): __library__", @@ -98,10 +97,12 @@ }, "nodes": { "credentials": { - "error": "Fehler beim Laden der Berechtigungen: __message__", - "error-saving": "Fehler beim Speichern der Berechtigungen: __message__", - "not-registered": "Der Berechtigung-Typ '__type__' ist nicht registriert", - "system-key-warning": "\n---------------------------------------------------------------------\nDie Datei mit den Flow-Berechtigungen wird mit einem vom System\ngenerierten Schlüssel verschlüsselt.\nWenn der vom System generierte Schlüssel aus irgendeinem Grund\nverloren geht, kann die Datei mit den Berechtigungen nicht\nwiederhergestellt werden. Sie muss dann gelöscht und die\nBerechtigungen müssen erneut eingestellt werden.\nEs sollte ein eigener Schlüssel mit Hilfe der Option\n'credentialSecret' in der Einstellungsdatei vorgegeben werden.\nNode-RED wird dann die Datei mit den Flow-Berechtigungen\nbei der nächsten Übernahme (deploy) einer Änderung erneut\nverschlüsseln.\n---------------------------------------------------------------------" + "error": "Fehler beim Laden der Credentials: __message__", + "error-saving": "Fehler beim Speichern der Credentials: __message__", + "not-registered": "Der Credentials-Typ '__type__' ist nicht registriert", + "system-key-warning": "\n---------------------------------------------------------------------\nDie Datei mit den Flow-Credentials wird mit einem vom System\ngenerierten Schlüssel verschlüsselt.\nWenn der vom System generierte Schlüssel aus irgendeinem Grund\nverloren geht, kann die Datei mit den Credentials nicht\nwiederhergestellt werden. Sie muss dann gelöscht und die\nCredentials müssen erneut eingestellt werden.\nEs sollte ein eigener Schlüssel mit Hilfe der Option\n'credentialSecret' in der Einstellungsdatei vorgegeben werden.\nNode-RED wird dann die Datei mit den Flow-Credentials\nbei der nächsten Übernahme (deploy) einer Änderung erneut\nverschlüsseln.\n---------------------------------------------------------------------", + "unencrypted": "Verwende unverschlüsselte Credentials", + "encryptedNotFound": "Verschlüsselte Credentials nicht gefunden" }, "flows": { "safe-mode": "Die Flows sind gestoppt im abgesicherten Modus. Übernahme (deploy) zum Starten.", @@ -121,6 +122,7 @@ "stopped-flows": "Flows sind gestoppt", "stopped": "gestoppt", "stopping-error": "Fehler beim Stoppen des Nodes: __message__", + "updated-flows": "Flows aktualisiert", "added-flow": "Flow wird hinzugefügt: __label__", "updated-flow": "Aktualisierter Flow: __label__", "removed-flow": "Entfernter Flow: __label__", @@ -145,7 +147,6 @@ } } }, - "storage": { "index": { "forbidden-flow-name": "Unzulässiger Flow-Name" @@ -159,6 +160,7 @@ "restore": "Die '__type__'-Dateisicherung wird wiederhergestellt: __path__", "restore-fail": "Die Wiederherstellung der '__type__'-Dateisicherung ist fehlgeschlagen: __message__", "fsync-fail": "Die Übertragung der Datei __path__ auf das Laufwerk ist fehlgeschlagen: __message__", + "warn_name": "Name der Flows-Datei nicht festgelegt. Name wird unter Verwendung des Hostnamens generiert.", "projects": { "changing-project": "Aktives Projekt wird festgelegt: __project__", "active-project": "Aktives Projekt: __project__", @@ -174,7 +176,6 @@ } } }, - "context": { "log-store-init": "Kontextspeicher: __name__ [__info__]", "error-loading-module": "Fehler beim Laden des Kontextspeichers: __message__", @@ -189,5 +190,4 @@ "error-write": "Fehler beim Schreiben des Kontextes: __message__" } } - } diff --git a/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json b/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json index ecd010abb..46890c26c 100644 --- a/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json +++ b/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json @@ -20,6 +20,7 @@ "errors-help": "Run with -v for details", "missing-modules": "Missing node modules:", "node-version-mismatch": "Node module cannot be loaded on this version. Requires: __version__ ", + "set-has-no-types": "Set does not have any types. name: '__name__', module: '__module__', file: '__file__'", "type-already-registered": "'__type__' already registered by module __module__", "removing-modules": "Removing modules from config", "added-types": "Added node types:", @@ -134,7 +135,8 @@ "flow": { "unknown-type": "Unknown type: __type__", "missing-types": "missing types", - "error-loop": "Message exceeded maximum number of catches" + "error-loop": "Message exceeded maximum number of catches", + "non-message-returned": "Node tried to send a message of type __type__" }, "index": { "unrecognised-id": "Unrecognised id: __id__", diff --git a/packages/node_modules/@node-red/runtime/locales/ja/runtime.json b/packages/node_modules/@node-red/runtime/locales/ja/runtime.json index bb5b0badc..8e4bceebe 100644 --- a/packages/node_modules/@node-red/runtime/locales/ja/runtime.json +++ b/packages/node_modules/@node-red/runtime/locales/ja/runtime.json @@ -20,6 +20,7 @@ "errors-help": "詳細は -v を指定して実行してください", "missing-modules": "不足しているノードモジュール:", "node-version-mismatch": "ノードモジュールはこのバージョンではロードできません。必要なバージョン: __version__ ", + "set-has-no-types": "セットに型がありません。 名前: '__name__', モジュール: '__module__', ファイル: '__file__'", "type-already-registered": "'__type__' はモジュール __module__ で登録済みです", "removing-modules": "設定からモジュールを削除します", "added-types": "追加したノード:", @@ -134,7 +135,8 @@ "flow": { "unknown-type": "不明なノード: __type__", "missing-types": "欠落したノード", - "error-loop": "メッセージの例外補足回数が最大値を超えました" + "error-loop": "メッセージの例外補足回数が最大値を超えました", + "non-message-returned": "ノードが __type__ 型のメッセージの送信を試みました" }, "index": { "unrecognised-id": "不明なID: __id__", diff --git a/packages/node_modules/@node-red/runtime/locales/ko/runtime.json b/packages/node_modules/@node-red/runtime/locales/ko/runtime.json old mode 100755 new mode 100644 diff --git a/packages/node_modules/node-red/settings.js b/packages/node_modules/node-red/settings.js index 5d8a05e44..d6e14f21b 100644 --- a/packages/node_modules/node-red/settings.js +++ b/packages/node_modules/node-red/settings.js @@ -181,7 +181,7 @@ module.exports = { /** Some nodes, such as HTTP In, can be used to listen for incoming http requests. * By default, these are served relative to '/'. The following property - * can be used to specifiy a different root path. If set to false, this is + * can be used to specify a different root path. If set to false, this is * disabled. */ //httpNodeRoot: '/red-nodes', @@ -219,17 +219,17 @@ module.exports = { /** When httpAdminRoot is used to move the UI to a different root path, the * following property can be used to identify a directory of static content * that should be served at http://localhost:1880/. - * When httpStaticRoot is set differently to httpAdminRoot, there is no need + * When httpStaticRoot is set differently to httpAdminRoot, there is no need * to move httpAdminRoot */ //httpStatic: '/home/nol/node-red-static/', //single static source /* OR multiple static sources can be created using an array of objects... */ //httpStatic: [ - // {path: '/home/nol/pics/', root: "/img/"}, - // {path: '/home/nol/reports/', root: "/doc/"}, + // {path: '/home/nol/pics/', root: "/img/"}, + // {path: '/home/nol/reports/', root: "/doc/"}, //], - /** + /** * All static routes will be appended to httpStaticRoot * e.g. if httpStatic = "/home/nol/docs" and httpStaticRoot = "/static/" * then "/home/nol/docs" will be served at "/static/" @@ -256,11 +256,11 @@ module.exports = { */ // lang: "de", - /** Configure diagnostics options + /** Configure diagnostics options * - enabled: When `enabled` is `true` (or unset), diagnostics data will - * be available at http://localhost:1880/diagnostics - * - ui: When `ui` is `true` (or unset), the action `show-system-info` will - * be available to logged in users of node-red editor + * be available at http://localhost:1880/diagnostics + * - ui: When `ui` is `true` (or unset), the action `show-system-info` will + * be available to logged in users of node-red editor */ diagnostics: { /** enable or disable diagnostics endpoint. Must be set to `false` to disable */ @@ -268,10 +268,10 @@ module.exports = { /** enable or disable diagnostics display in the node-red editor. Must be set to `false` to disable */ ui: true, }, - /** Configure runtimeState options - * - enabled: When `enabled` is `true` flows runtime can be Started/Stoped - * by POSTing to available at http://localhost:1880/flows/state - * - ui: When `ui` is `true`, the action `core:start-flows` and + /** Configure runtimeState options + * - enabled: When `enabled` is `true` flows runtime can be Started/Stopped + * by POSTing to available at http://localhost:1880/flows/state + * - ui: When `ui` is `true`, the action `core:start-flows` and * `core:stop-flows` will be available to logged in users of node-red editor * Also, the deploy menu (when set to default) will show a stop or start button */ @@ -528,7 +528,7 @@ module.exports = { */ //tlsConfigDisableLocalFiles: true, - /** The following property can be used to verify websocket connection attempts. + /** The following property can be used to verify WebSocket connection attempts. * This allows, for example, the HTTP request headers to be checked to ensure * they include valid authentication information. */ diff --git a/test/nodes/core/function/15-change_spec.js b/test/nodes/core/function/15-change_spec.js index b8d7be03b..66f02d6d1 100644 --- a/test/nodes/core/function/15-change_spec.js +++ b/test/nodes/core/function/15-change_spec.js @@ -1717,6 +1717,24 @@ describe('change Node', function() { changeNode1.receive({topic:{foo:{bar:1}}, payload:"String"}); }); }); + it('moves the value of a message property object to itself', function(done) { + var flow = [{"id":"changeNode1","type":"change","rules":[{"t":"move","p":"payload","pt":"msg","to":"payload","tot":"msg"}],"name":"changeNode","wires":[["helperNode1"]]}, + {id:"helperNode1", type:"helper", wires:[]}]; + helper.load(changeNode, flow, function() { + var changeNode1 = helper.getNode("changeNode1"); + var helperNode1 = helper.getNode("helperNode1"); + helperNode1.on("input", function(msg) { + try { + msg.should.have.property('payload'); + msg.payload.should.equal("bar"); + done(); + } catch(err) { + done(err); + } + }); + changeNode1.receive({payload:"bar"}); + }); + }); it('moves the value of a message property object to a sub-property', function(done) { var flow = [{"id":"changeNode1","type":"change","rules":[{"t":"move","p":"payload","pt":"msg","to":"payload.foo","tot":"msg"}],"name":"changeNode","wires":[["helperNode1"]]}, {id:"helperNode1", type:"helper", wires:[]}]; diff --git a/test/nodes/core/network/21-httprequest_spec.js b/test/nodes/core/network/21-httprequest_spec.js index 688776289..14f3fd628 100644 --- a/test/nodes/core/network/21-httprequest_spec.js +++ b/test/nodes/core/network/21-httprequest_spec.js @@ -31,6 +31,8 @@ var multer = require("multer"); var RED = require("nr-test-utils").require("node-red/lib/red"); var fs = require('fs-extra'); var auth = require('basic-auth'); +const { version } = require("os"); +const net = require('net') describe('HTTP Request Node', function() { var testApp; @@ -1527,7 +1529,7 @@ describe('HTTP Request Node', function() { msg.payload.headers.should.have.property('Content-Type').which.startWith('application/json'); //msg.dynamicHeaderName should be present in headers with the value of msg.dynamicHeaderValue msg.payload.headers.should.have.property('dyn-header-name').which.startWith('dyn-header-value'); - //static (custom) header set in Flow UI should be present + //static (custom) header set in Flow UI should be present msg.payload.headers.should.have.property('static-header-name').which.startWith('static-header-value'); //msg.headers['location'] should be deleted because Flow UI "Location" header has a blank value //ensures headers with matching characters but different case are eliminated @@ -2265,4 +2267,105 @@ describe('HTTP Request Node', function() { }); }); }); + + describe('should parse broken headers', function() { + + const versions = process.versions.node.split('.') + + if (( versions[0] == 14 && versions[1] >= 20 ) || + ( versions[0] == 16 && versions[1] >= 16 ) || + ( versions[0] == 18 && versions[1] >= 5 ) || + ( versions[0] > 18)) { + // only test if on new enough NodeJS version + + let port = testPort++ + + let server; + + before(function() { + server = net.createServer(function (socket) { + socket.write("HTTP/1.0 200\nContent-Type: text/plain\n\nHelloWorld") + socket.end() + }) + + server.listen(port,'127.0.0.1', function(err) { + }) + }); + + after(function() { + server.close() + }); + + it('should accept broken headers', function (done) { + var flow = [{id:'n1',type:'http request',wires:[['n2']],method:'GET',ret:'obj',url:`http://localhost:${port}/`, insecureHTTPParser: true}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on('input', function(msg) { + try { + msg.payload.should.equal('HelloWorld') + done() + } catch (err) { + done(err) + } + }) + n1.receive({payload: 'foo'}) + }); + }); + + it('should reject broken headers', function (done) { + var flow = [{id:'n1',type:'http request',wires:[['n2']],method:'GET',ret:'obj',url:`http://localhost:${port}/`}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on('input', function(msg) { + try{ + msg.payload.should.match(/RequestError: Parse Error/) + done() + } catch (err) { + done(err) + } + }) + n1.receive({payload: 'foo'}) + + }); + }); + } + }); + + describe('multipart form posts', function() { + it('should send arrays as multiple entries', function (done) { + const flow = [ + { + id: 'n1', type: 'http request', wires: [['n2']], method: 'POST', ret: 'obj', url: getTestURL('/file-upload'), headers: [ + ] + }, + { id: "n2", type: "helper" } + ]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on('input', function(msg){ + try { + msg.payload.body.should.have.property('foo') + msg.payload.body.list.should.deepEqual(['a','b','c']) + done() + } catch (e) { + done(e) + } + }); + n1.receive({ + headers: { + 'content-type': 'multipart/form-data' + }, + payload: { + foo: 'bar', + list: [ 'a', 'b', 'c' ] + } + }); + }) + }); + }) }); diff --git a/test/nodes/core/network/21-mqtt_spec.js b/test/nodes/core/network/21-mqtt_spec.js index 655cc9c73..f97442b50 100644 --- a/test/nodes/core/network/21-mqtt_spec.js +++ b/test/nodes/core/network/21-mqtt_spec.js @@ -27,7 +27,7 @@ describe('MQTT Nodes', function () { } catch (error) { } }); - it('should be loaded and have default values', function (done) { + it('should be loaded and have default values (MQTT V4)', function (done) { this.timeout = 2000; const { flow, nodes } = buildBasicMQTTSendRecvFlow({ id: "mqtt.broker", name: "mqtt_broker", autoConnect: false }, { id: "mqtt.in", topic: "in_topic" }, { id: "mqtt.out", topic: "out_topic" }); helper.load(mqttNodes, flow, function () { @@ -61,6 +61,52 @@ describe('MQTT Nodes', function () { mqttBroker.options.clientId.should.containEql('nodered_'); mqttBroker.options.should.have.property('keepalive').type("number"); mqttBroker.options.should.have.property('reconnectPeriod').type("number"); + //as this is not a v5 connection, ensure v5 properties are not present + mqttBroker.options.should.not.have.property('protocolVersion', 5); + mqttBroker.options.should.not.have.property('properties'); + done(); + } catch (error) { + done(error) + } + }); + }); + it('should be loaded and have default values (MQTT V5)', function (done) { + this.timeout = 2000; + const { flow, nodes } = buildBasicMQTTSendRecvFlow({ id: "mqtt.broker", name: "mqtt_broker", autoConnect: false, cleansession: false, clientid: 'clientid', keepalive: 35, sessionExpiry: '6000', protocolVersion: '5', userProps: {"prop": "val"}}, { id: "mqtt.in", topic: "in_topic" }, { id: "mqtt.out", topic: "out_topic" }); + helper.load(mqttNodes, flow, function () { + try { + const mqttIn = helper.getNode("mqtt.in"); + const mqttOut = helper.getNode("mqtt.out"); + const mqttBroker = helper.getNode("mqtt.broker"); + + should(mqttIn).be.type("object", "mqtt in node should be an object") + mqttIn.should.have.property('broker', nodes.mqtt_broker.id); //should be the id of the broker node + mqttIn.should.have.property('datatype', 'utf8'); //default: 'utf8' + mqttIn.should.have.property('isDynamic', false); //default: false + mqttIn.should.have.property('inputs', 0); //default: 0 + mqttIn.should.have.property('qos', 2); //default: 2 + mqttIn.should.have.property('topic', "in_topic"); + mqttIn.should.have.property('wires', [["helper.node"]]); + + should(mqttOut).be.type("object", "mqtt out node should be an object") + mqttOut.should.have.property('broker', nodes.mqtt_broker.id); //should be the id of the broker node + mqttOut.should.have.property('topic', "out_topic"); + + should(mqttBroker).be.type("object", "mqtt broker node should be an object") + mqttBroker.should.have.property('broker', BROKER_HOST); + mqttBroker.should.have.property('port', BROKER_PORT); + mqttBroker.should.have.property('brokerurl'); + // mqttBroker.should.have.property('autoUnsubscribe', true);//default: true + mqttBroker.should.have.property('autoConnect', false);//Set "autoConnect:false" in brokerOptions + mqttBroker.should.have.property('options'); + mqttBroker.options.should.have.property('clean', false); + mqttBroker.options.should.have.property('clientId', 'clientid'); + mqttBroker.options.should.have.property('keepalive').type("number", 35); + mqttBroker.options.should.have.property('reconnectPeriod').type("number"); + //as this IS a v5 connection, ensure v5 properties are not present + mqttBroker.options.should.have.property('protocolVersion', 5); + mqttBroker.options.should.have.property('properties'); + mqttBroker.options.properties.should.have.property('sessionExpiryInterval'); done(); } catch (error) { done(error) @@ -173,7 +219,7 @@ describe('MQTT Nodes', function () { topic: nextTopic(), payload: '{prop:"value3", "num":3}', // send invalid JSON ... } - const hooks = { done: done, beforeLoad: null, afterLoad: null, afterConnect: null } + const hooks = { done: null, beforeLoad: null, afterLoad: null, afterConnect: null } hooks.afterLoad = (helperNode, mqttBroker, mqttIn, mqttOut) => { helperNode.on("input", function (msg) { try { @@ -299,7 +345,7 @@ describe('MQTT Nodes', function () { topic: nextTopic(), payload: '{prop:"value3", "num":3}', contentType: "application/json", // send invalid JSON ... } - const hooks = { done: done, beforeLoad: null, afterLoad: null, afterConnect: null } + const hooks = { done: null, beforeLoad: null, afterLoad: null, afterConnect: null } hooks.afterLoad = (helperNode, mqttBroker, mqttIn, mqttOut) => { helperNode.on("input", function (msg) { try { @@ -385,7 +431,7 @@ describe('MQTT Nodes', function () { if (skipTests) { return this.skip() } this.timeout = 2000; const options = {} - const hooks = { done: done, beforeLoad: null, afterLoad: null, afterConnect: null } + const hooks = { beforeLoad: null, afterLoad: null, afterConnect: null } hooks.beforeLoad = (flow) => { //add a status node pointed at MQTT Out node (to watch for connection status change) flow.push({ "id": "status.node", "type": "status", "name": "status_node", "scope": ["mqtt.out"], "wires": [["helper.node"]] });//add status node to watch mqtt_out } @@ -416,20 +462,66 @@ describe('MQTT Nodes', function () { this.timeout = 2000; const baseTopic = nextTopic(); const brokerOptions = { + autoConnect: false, protocolVersion: 4, birthTopic: baseTopic + "/birth", - birthPayload: "broker connected", + birthPayload: "broker birth", birthQos: 2, } - const options = {}; - const hooks = { done: done, beforeLoad: null, afterLoad: null, afterConnect: null }; - options.expectMsg = { + const expectMsg = { topic: brokerOptions.birthTopic, payload: brokerOptions.birthPayload, qos: brokerOptions.birthQos }; + const options = { }; + const hooks = { }; + hooks.afterLoad = (helperNode, mqttBroker, mqttIn, mqttOut) => { + helperNode.on("input", function (msg) { + try { + compareMsgToExpected(msg, expectMsg); + done(); + } catch (error) { + done(error) + } + }) + mqttIn.receive({ "action": "connect" }); //now request connect action + return true; //handled + } testSendRecv(brokerOptions, { topic: brokerOptions.birthTopic }, {}, options, hooks); }); + itConditional('should safely discard bad birth topic', function (done) { + if (skipTests) { return this.skip() } + this.timeout = 2000; + const baseTopic = nextTopic(); + const brokerOptions = { + protocolVersion: 4, + birthTopic: baseTopic + "#", // a publish topic should never have a wildcard + birthPayload: "broker connected", + birthQos: 2, + } + const options = {}; + const hooks = { done: null, beforeLoad: null, afterLoad: null, afterConnect: null }; + hooks.afterLoad = (helperNode, mqttBroker, mqttIn, mqttOut) => { + helperNode.on("input", function (msg) { + try { + msg.should.have.a.property("error").type("object"); + msg.error.should.have.a.property("source").type("object"); + msg.error.source.should.have.a.property("id", mqttIn.id); + done(); + } catch (err) { + done(err) + } + }); + return true; //handled + } + options.expectMsg = null; + try { + testSendRecv(brokerOptions, { topic: brokerOptions.birthTopic }, {}, options, hooks); + done() + } catch(err) { + done(e) + } + }); itConditional('should publish close message', function (done) { if (skipTests) { return this.skip() } this.timeout = 2000; @@ -587,12 +679,13 @@ function testSendRecv(brokerOptions, inNodeOptions, outNodeOptions, options, hoo const mqttBroker = helper.getNode(brokerOptions.id); const mqttIn = helper.getNode(nodes.mqtt_in.id); const mqttOut = helper.getNode(nodes.mqtt_out.id); - let afterLoadHandled = false; + let afterLoadHandled = false, finished = false; if (hooks.afterLoad) { afterLoadHandled = hooks.afterLoad(helperNode, mqttBroker, mqttIn, mqttOut) } if (!afterLoadHandled) { helperNode.on("input", function (msg) { + finished = true try { compareMsgToExpected(msg, expectMsg); if (hooks.done) { hooks.done(); } @@ -617,10 +710,12 @@ function testSendRecv(brokerOptions, inNodeOptions, outNodeOptions, options, hoo } }) .catch((e) => { + if(finished) { return } if (hooks.done) { hooks.done(e); } else { throw e; } }); } catch (err) { + if(finished) { return } if (hooks.done) { hooks.done(err); } else { throw err; } } @@ -666,6 +761,7 @@ function buildMQTTBrokerNode(id, name, brokerHost, brokerPort, options) { node.cleansession = String(options.cleansession) == "false" ? false : true; node.autoUnsubscribe = String(options.autoUnsubscribe) == "false" ? false : true; node.autoConnect = String(options.autoConnect) == "false" ? false : true; + node.sessionExpiry = options.sessionExpiry ? options.sessionExpiry : undefined; if (options.birthTopic) { node.birthTopic = options.birthTopic; @@ -760,8 +856,8 @@ function waitBrokerConnect(broker, timeLimit) { let waitConnected = (broker, timeLimit) => { const brokers = Array.isArray(broker) ? broker : [broker]; timeLimit = timeLimit || 1000; - let timer, resolved = false; return new Promise( (resolve, reject) => { + let timer, resolved = false; timer = wait(); function wait() { if (brokers.every(e => e.connected == true)) { diff --git a/test/nodes/core/parsers/70-CSV_spec.js b/test/nodes/core/parsers/70-CSV_spec.js index 93d59a171..681711b3b 100644 --- a/test/nodes/core/parsers/70-CSV_spec.js +++ b/test/nodes/core/parsers/70-CSV_spec.js @@ -693,19 +693,20 @@ describe('CSV node', function() { describe('json object to csv', function() { it('should convert a simple object back to a csv', function(done) { - var flow = [ { id:"n1", type:"csv", temp:"a,b,c,,e,f", wires:[["n2"]] }, + var flow = [ { id:"n1", type:"csv", temp:"a,b,c,,e,f,g,h,i,j,k", wires:[["n2"]] }, {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); n2.on("input", function(msg) { + // console.log("GOT",msg) try { - msg.should.have.property('payload', '4,foo,true,,0,"Hello\nWorld"\n'); + msg.should.have.property('payload', '4,foo,true,,0,"Hello\nWorld",,,undefined,null,null\n'); done(); } catch(e) { done(e); } }); - var testJson = { e:0, d:1, b:"foo", c:true, a:4, f:"Hello\nWorld" }; + var testJson = { e:0, d:1, b:"foo", c:true, a:4, f:"Hello\nWorld", h:undefined, i:"undefined",j:null,k:"null" }; n1.emit("input", {payload:testJson}); }); }); @@ -717,13 +718,14 @@ describe('CSV node', function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); n2.on("input", function(msg) { + // console.log("GOT",msg) try { - msg.should.have.property('payload', '1,foo,"ba""r","di,ng"\n'); + msg.should.have.property('payload', '1,foo,"ba""r","di,ng",,undefined,null\n'); done(); } catch(e) { done(e); } }); - var testJson = { d:1, b:"foo", c:"ba\"r", a:"di,ng" }; + var testJson = { d:1, b:"foo", c:"ba\"r", a:"di,ng", e:undefined, f:"undefined", g:null,h:"null" }; n1.emit("input", {payload:testJson}); }); }); @@ -764,6 +766,33 @@ describe('CSV node', function() { }); }); + it('should handle a template with quotes in the property names', function(done) { + var flow = [ { id:"n1", type:"csv", temp:"", hdrout:"all", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(csvNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload', 'a"a,b\'b\nA1,B1\nA2,B2\n'); + done(); + } + catch(e) { done(e); } + }); + var testJson = [ + { + "a\"a": "A1", + "b'b": "B1" + }, + { + "a\"a": "A2", + "b'b": "B2" + } + ] + n1.emit("input", {payload:testJson}); + }); + }); + it('should convert an array of objects to a multi-line csv', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,d,c,b", wires:[["n2"]] }, {id:"n2", type:"helper"} ]; diff --git a/test/nodes/core/storage/23-watch_spec.js b/test/nodes/core/storage/23-watch_spec.js index a9942d5b1..dd7b94037 100644 --- a/test/nodes/core/storage/23-watch_spec.js +++ b/test/nodes/core/storage/23-watch_spec.js @@ -15,11 +15,14 @@ **/ var fs = require("fs-extra"); +var os = require("os"); var path = require("path"); var should = require("should"); var helper = require("node-red-node-test-helper"); var watchNode = require("nr-test-utils").require("@node-red/nodes/core/storage/23-watch.js"); +var arch = os.arch(); +var platform = os.platform(); describe('watch Node', function() { this.timeout(5000); @@ -89,7 +92,10 @@ describe('watch Node', function() { msg.should.have.property('payload', result.payload); msg.should.have.property('type', result.type); if('size' in result) { - msg.should.have.property('size', result.size); + if (!((arch === "arm64") && (platform === "darwin"))) { + // On OSX/ARM, two change events occur and first event do not reflect file size change. So ignore size field in the case. + msg.should.have.property('size', result.size); + } } count++; if(count === len) { diff --git a/test/unit/@node-red/editor-api/lib/index_spec.js b/test/unit/@node-red/editor-api/lib/index_spec.js index 7308fdc2d..37bb82c13 100644 --- a/test/unit/@node-red/editor-api/lib/index_spec.js +++ b/test/unit/@node-red/editor-api/lib/index_spec.js @@ -61,7 +61,7 @@ describe("api/index", function() { should.not.exist(api.httpAdmin); done(); }); - describe('initalises admin api without adminAuth', function(done) { + describe('initalises admin api without adminAuth', function() { before(function() { beforeEach(); api.init({},{},{},{}); @@ -78,7 +78,7 @@ describe("api/index", function() { }) }); - describe('initalises admin api without editor', function(done) { + describe('initalises admin api without editor', function() { before(function() { beforeEach(); api.init({ disableEditor: true },{},{},{}); @@ -95,7 +95,7 @@ describe("api/index", function() { }) }); - describe('initialises api with admin middleware', function(done) { + describe('initialises api with admin middleware', function() { it('ignores non-function values',function(done) { api.init({ httpAdminRoot: true, httpAdminMiddleware: undefined },{},{},{}); const middlewareFound = api.httpAdmin._router.stack.filter((layer) => layer.name === 'testMiddleware') @@ -112,7 +112,7 @@ describe("api/index", function() { }); }); - describe('initialises api with authentication enabled', function(done) { + describe('initialises api with authentication enabled', function() { it('enables an oauth/openID based authentication mechanism',function(done) { const stub = sinon.stub(apiAuth, 'genericStrategy').callsFake(function(){}); @@ -135,7 +135,7 @@ describe("api/index", function() { }); - describe('initialises api with custom cors config', function (done) { + describe('initialises api with custom cors config', function () { const httpAdminCors = { origin: "*", methods: "GET,PUT,POST,DELETE" @@ -156,7 +156,7 @@ describe("api/index", function() { }) }); - describe('editor start', function (done) { + describe('editor start', function () { it('cannot be started when editor is disabled', function (done) { const stub = sinon.stub(apiEditor, 'start').callsFake(function () { diff --git a/test/unit/@node-red/registry/lib/localfilesystem_spec.js b/test/unit/@node-red/registry/lib/localfilesystem_spec.js index 3b1bb63f3..3ce74c158 100644 --- a/test/unit/@node-red/registry/lib/localfilesystem_spec.js +++ b/test/unit/@node-red/registry/lib/localfilesystem_spec.js @@ -329,17 +329,36 @@ describe("red/nodes/registry/localfilesystem",function() { localfilesystem.init({nodesDir:[nodesDir2]}); const nodeModule = localfilesystem.getModuleFiles(); const loaded = Object.keys(nodeModule) - loaded.should.have.a.property("length", 3) loaded.indexOf('@test/testnode').should.greaterThan(-1, "Should load @test/testnode") + loaded.indexOf('lower-case').should.greaterThan(-1, "Should load lower-case") + loaded.indexOf('@lowercase/lower-case2').should.greaterThan(-1, "Should load @lowercase/lower-case2") loaded.indexOf('testnode2').should.greaterThan(-1, "Should load testnode2") loaded.indexOf('test-theme2').should.greaterThan(-1, "Should load test-theme2") + loaded.should.have.a.property("length", 5) + // scoped module with nodes in same dir as package.json nodeModule['@test/testnode'].should.have.a.property('name','@test/testnode'); nodeModule['@test/testnode'].should.have.a.property('version','1.0.0'); nodeModule['@test/testnode'].should.have.a.property('nodes'); nodeModule['@test/testnode'].should.have.a.property('path'); nodeModule['@test/testnode'].should.have.a.property('user', false); + // node-red module with nodes in sub dir + nodeModule['@lowercase/lower-case2'].should.have.a.property('name','@lowercase/lower-case2'); + nodeModule['@lowercase/lower-case2'].should.have.a.property('version','2.0.0'); + nodeModule['@lowercase/lower-case2'].should.have.a.property('nodes'); + nodeModule['@lowercase/lower-case2'].nodes.should.have.a.property('lower-case'); + nodeModule['@lowercase/lower-case2'].should.have.a.property('path'); + nodeModule['@lowercase/lower-case2'].should.have.a.property('user', false); + + // scoped module with nodes in sub dir + nodeModule['lower-case'].should.have.a.property('name', 'lower-case'); + nodeModule['lower-case'].should.have.a.property('version','1.0.0'); + nodeModule['lower-case'].should.have.a.property('nodes'); + nodeModule['lower-case'].nodes.should.have.a.property('lower-case'); + nodeModule['lower-case'].should.have.a.property('path'); + nodeModule['lower-case'].should.have.a.property('user', false); + nodeModule['testnode2'].should.have.a.property('name','testnode2'); nodeModule['testnode2'].should.have.a.property('version','1.0.0'); nodeModule['testnode2'].should.have.a.property('nodes'); diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/lower-case2/lower-case.html b/test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/lower-case2/lower-case.html new file mode 100644 index 000000000..617f48491 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/lower-case2/lower-case.html @@ -0,0 +1,26 @@ + + + + + \ No newline at end of file diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/lower-case2/lower-case.js b/test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/lower-case2/lower-case.js new file mode 100644 index 000000000..73579ba04 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/lower-case2/lower-case.js @@ -0,0 +1,11 @@ +module.exports = function(RED) { +function LowerCaseNode(config) { + RED.nodes.createNode(this,config); + var node = this; + node.on('input', function(msg) { + msg.payload = msg.payload.toLowerCase(); + node.send(msg); + }); + } + RED.nodes.registerType("lower-case2",LowerCaseNode); +} \ No newline at end of file diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/package.json b/test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/package.json new file mode 100644 index 000000000..6b6ce9aa9 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/package.json @@ -0,0 +1,9 @@ +{ + "name" : "@lowercase/lower-case2", + "node-red" : { + "nodes": { + "lower-case": "lower-case2/lower-case.js" + } + }, + "version": "2.0.0" +} \ No newline at end of file diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/lower-case/lower-case.html b/test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/lower-case/lower-case.html new file mode 100644 index 000000000..e57d51131 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/lower-case/lower-case.html @@ -0,0 +1,26 @@ + + + + + diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/lower-case/lower-case.js b/test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/lower-case/lower-case.js new file mode 100644 index 000000000..006b35eb6 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/lower-case/lower-case.js @@ -0,0 +1,11 @@ +module.exports = function(RED) { + function LowerCaseNode(config) { + RED.nodes.createNode(this,config); + var node = this; + node.on('input', function(msg) { + msg.payload = msg.payload.toLowerCase(); + node.send(msg); + }); + } + RED.nodes.registerType("lower-case",LowerCaseNode); +} diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/package.json b/test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/package.json new file mode 100644 index 000000000..a632eaddd --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/package.json @@ -0,0 +1,9 @@ +{ + "name" : "lower-case", + "node-red" : { + "nodes": { + "lower-case": "lower-case/lower-case.js" + } + }, + "version": "1.0.0" +} diff --git a/test/unit/@node-red/runtime/lib/api/diagnostics_spec.js b/test/unit/@node-red/runtime/lib/api/diagnostics_spec.js index 07e499344..136e4826c 100644 --- a/test/unit/@node-red/runtime/lib/api/diagnostics_spec.js +++ b/test/unit/@node-red/runtime/lib/api/diagnostics_spec.js @@ -33,6 +33,11 @@ describe("runtime-api/diagnostics", function() { flowFile: "flows.json", mqttReconnectTime: 321, serialReconnectTime: 432, + socketReconnectTime: 2222, + socketTimeout: 3333, + tcpMsgQueueSize: 4444, + inboundWebSocketTimeout: 5555, + runtimeState: {enabled: true, ui: false}, adminAuth: {},//should be sanitised to "SET" httpAdminRoot: "/admin/root/", httpAdminCors: {},//should be sanitised to "SET" @@ -45,6 +50,7 @@ describe("runtime-api/diagnostics", function() { uiHost: "something.secret.com",//should be sanitised to "SET" uiPort: 1337,//should be sanitised to "SET" userDir: "/var/super/secret/",//should be sanitised to "SET", + nodesDir: "/var/super/secret/",//should be sanitised to "SET", contextStorage: { default : { module: "memory" }, file: { module: "localfilesystem" }, @@ -73,8 +79,9 @@ describe("runtime-api/diagnostics", function() { //result.runtime.xxxxx const runtimeCount = Object.keys(result.runtime).length; - runtimeCount.should.eql(4);//ensure no more than 4 keys are present in runtime + runtimeCount.should.eql(5);//ensure 5 keys are present in runtime result.runtime.should.have.property('isStarted',true) + result.runtime.should.have.property('flows') result.runtime.should.have.property('modules').type("object"); result.runtime.should.have.property('settings').type("object"); result.runtime.should.have.property('version','7.7.7'); @@ -87,7 +94,7 @@ describe("runtime-api/diagnostics", function() { //result.runtime.settings.xxxxx const settingsCount = Object.keys(result.runtime.settings).length; - settingsCount.should.eql(21);//ensure no more than the 21 settings listed below are present in the settings object + settingsCount.should.eql(27);//ensure no more than the 21 settings listed below are present in the settings object result.runtime.settings.should.have.property('available',true); result.runtime.settings.should.have.property('apiMaxLength', "UNSET");//deliberately disabled to ensure UNSET is returned result.runtime.settings.should.have.property('debugMaxLength', 1111); @@ -96,6 +103,11 @@ describe("runtime-api/diagnostics", function() { result.runtime.settings.should.have.property('flowFile', "flows.json"); result.runtime.settings.should.have.property('mqttReconnectTime', 321); result.runtime.settings.should.have.property('serialReconnectTime', 432); + result.runtime.settings.should.have.property('socketReconnectTime', 2222); + result.runtime.settings.should.have.property('socketTimeout', 3333); + result.runtime.settings.should.have.property('tcpMsgQueueSize', 4444); + result.runtime.settings.should.have.property('inboundWebSocketTimeout', 5555); + result.runtime.settings.should.have.property('runtimeState', {enabled: true, ui: false}); result.runtime.settings.should.have.property("adminAuth", "SET"); //should be sanitised to "SET" result.runtime.settings.should.have.property("httpAdminCors", "SET"); //should be sanitised to "SET" result.runtime.settings.should.have.property('httpAdminRoot', "/admin/root/"); @@ -109,6 +121,7 @@ describe("runtime-api/diagnostics", function() { result.runtime.settings.should.have.property("uiPort", "SET"); //should be sanitised to "SET" result.runtime.settings.should.have.property("userDir", "SET"); //should be sanitised to "SET" result.runtime.settings.should.have.property('contextStorage').type("object"); + result.runtime.settings.should.have.property('nodesDir', "SET") //result.runtime.settings.contextStorage.xxxxx const contextCount = Object.keys(result.runtime.settings.contextStorage).length;