Merge branch 'master' into dev

This commit is contained in:
Nick O'Leary 2023-12-04 15:58:45 +00:00
commit 33cf34f7c7
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
89 changed files with 903 additions and 670 deletions

View File

@ -30,5 +30,5 @@ the [forum](https://discourse.nodered.org) or
- [ ] I have read the [contribution guidelines](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md) - [ ] I have read the [contribution guidelines](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md)
- [ ] For non-bugfix PRs, I have discussed this change on the forum/slack team. - [ ] For non-bugfix PRs, I have discussed this change on the forum/slack team.
- [ ] I have run `grunt` to verify the unit tests pass - [ ] I have run `npm run test` to verify the unit tests pass
- [ ] I have added suitable unit tests to cover the new/changed functionality - [ ] I have added suitable unit tests to cover the new/changed functionality

15
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,15 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "github-actions" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "monthly"
groups:
github-actions:
patterns:
- "*"

View File

@ -14,25 +14,25 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out node-red repository - name: Check out node-red repository
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
path: 'node-red' path: 'node-red'
- name: Check out node-red-docker repository - name: Check out node-red-docker repository
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
repository: 'node-red/node-red-docker' repository: 'node-red/node-red-docker'
path: 'node-red-docker' path: 'node-red-docker'
- name: Check out node-red.github.io repository - name: Check out node-red.github.io repository
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
repository: 'node-red/node-red.github.io' repository: 'node-red/node-red.github.io'
path: 'node-red.github.io' path: 'node-red.github.io'
- uses: actions/setup-node@v1 - uses: actions/setup-node@v4
with: with:
node-version: '16' node-version: '16'
- run: node ./node-red/.github/scripts/update-node-red-docker.js - run: node ./node-red/.github/scripts/update-node-red-docker.js
- name: Create Docker Pull Request - name: Create Docker Pull Request
uses: peter-evans/create-pull-request@v2 uses: peter-evans/create-pull-request@v5
with: with:
token: ${{ secrets.NR_REPO_TOKEN }} token: ${{ secrets.NR_REPO_TOKEN }}
committer: GitHub <noreply@github.com> committer: GitHub <noreply@github.com>
@ -48,7 +48,7 @@ jobs:
This PR was auto-generated by a GitHub Action. Any questions, speak to @knolleary This PR was auto-generated by a GitHub Action. Any questions, speak to @knolleary
- run: node ./node-red/.github/scripts/update-node-red-website.js - run: node ./node-red/.github/scripts/update-node-red-website.js
- name: Create Website Pull Request - name: Create Website Pull Request
uses: peter-evans/create-pull-request@v2 uses: peter-evans/create-pull-request@v5
with: with:
token: ${{ secrets.NR_REPO_TOKEN }} token: ${{ secrets.NR_REPO_TOKEN }}
committer: GitHub <noreply@github.com> committer: GitHub <noreply@github.com>

View File

@ -19,9 +19,9 @@ jobs:
matrix: matrix:
node-version: [16, 18, 20] node-version: [16, 18, 20]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- name: Install Dependencies - name: Install Dependencies

1
.gitignore vendored
View File

@ -27,3 +27,4 @@ docs
.vscode .vscode
.nyc_output .nyc_output
sync.ffs_db sync.ffs_db
package-lock.json

View File

@ -1,3 +1,57 @@
#### 3.1.1: Maintenance Release
Editor
- Fix debug filter (#4461) @knolleary
- Fix various issues with debug pop-out window (#4459) @knolleary
- Ensure subflow instances keep track of their groups (#4457) @knolleary
- Fix `validateNodeProperty` without validator provided (#4455) @GogoVega
- Debounce node-removed notifications (#4453) @knolleary
- Don't try to load the parents of the first commit (#4448) @bonanitech
- Allow a theme to specifiy which theme mermaid should use (#4441) @knolleary
- Update browser title with flow name if set (#4427) @knolleary
- Ensure typeSearch handles undefined node definitions (#4423) @knolleary
- Ensure group w/h are imported if present (#4426) @knolleary
- Hide node status background when there is no status to show (#4425) @knolleary
- Add a close button to the restart-required notification (#4407) @knolleary
- Extend typedInput "num" type validity check to NaN, binary, octal & hex (#4371) @ralphwetzel
- Fix unintended new line in node name (#4399) @kazuhitoyokoi
- Ctrl-Enter does not close tray (Monaco) #4377 (#4382) @hazymat
- fix buffer viewer to handle 0b style binary (#4393) @dceejay
- Rework mermaid integration to support off-DOM rendering (#4364) @knolleary
- Add missing nls labels to context menu (#4365) @knolleary
Runtime
- Bump the github-actions group with 2 updates (#4404) @app/dependabot
- Handle unknown node reference inside subflow module (#4460) @knolleary
- Add modules.install audit event when external module installed (#4452) @knolleary
- Allow import of modules with subpath in specifier (#4451) @knolleary
- Update node-red-admin version (#4438) @knolleary
- Handle false-like env vars properly (#4411) @knolleary
- Only save settings once during node load process (#4409) @knolleary
- Ensure global-config nodes lookup cred values properly (#4405) @knolleary
- Handle credential env var evaluation when no value set (#4362) @knolleary
- Don't commit package-lock.json (#4354) @bonanitech
- Fix env evaluation when one env references another in the same object (#4361) @knolleary
- Add dependabot for Github Actions (#4312) @Rotzbua
- Update outdated Github Actions (#4311) @Rotzbua
- github: Request `npm run test` in PR template (#4348) @ZJvandeWeg
- Add French translation of v3.1.0-beta.4 changes + slight improvements (#4329) @GogoVega
- Handle nodes with multiple input handlers properly (#4332) @knolleary
- Soften the language around unrequited PRs (#4351) @knolleary
Nodes
- CSV: make CSV export way faster by not re-allocating and handling huge string (#4349) @Fadoli
- Delay: Fix regression in delay node to not pass on msg.reset (#4350) @dceejay
- Link Call: Handle undefined linkType value for existing link-call nodes (#4331) @knolleary
- MQTT: Guard against node.broker being undefined (#4454) @knolleary
- MQTT: check topic length > 0 before publish (#4416) @dceejay
- Switch/Change: Improve validation of switch/change node rules (#4368) @knolleary
- Template: Fix height of description editor in template node (#4346) @kazuhitoyokoi
- Various: Add validators to any fields using msg-typed Input (#4440) @knolleary
#### 3.1.0: Milestone Release #### 3.1.0: Milestone Release
Editor Editor

View File

@ -16,6 +16,9 @@ behavior to the project's core team at team@nodered.org.
Please raise any bug reports on the relevant project's issue tracker. Be sure to Please raise any bug reports on the relevant project's issue tracker. Be sure to
search the list to see if your issue has already been raised. search the list to see if your issue has already been raised.
If your issue is more of a question on how to do something with Node-RED, please
consider using the [community forum](https://discourse.nodered.org/).
A good bug report is one that make it easy for us to understand what you were A good bug report is one that make it easy for us to understand what you were
trying to do and what went wrong. trying to do and what went wrong.
@ -35,14 +38,18 @@ For feature requests, please raise them on the [forum](https://discourse.nodered
## Pull-Requests ## Pull-Requests
If you want to raise a pull-request with a new feature, or a refactoring If you want to raise a pull-request with a new feature, or a refactoring
of existing code, it may well get rejected if you haven't discussed it on of existing code, please come and discuss it with us first. We prefer to
the [forum](https://discourse.nodered.org) first. do it that way to make sure your time and effort is well spent on something
that fits with our goals.
If you've got a bug-fix or similar for us, then you are most welcome to
get it raised - just make sure you link back to the issue it's fixing and
try to include some tests!
All contributors need to sign the OpenJS Foundation's Contributor License Agreement. All contributors need to sign the OpenJS Foundation's Contributor License Agreement.
It is an online process and quick to do. If you raise a pull-request without It is an online process and quick to do. If you raise a pull-request without
having signed the CLA, you will be prompted to do so automatically. having signed the CLA, you will be prompted to do so automatically.
### Code Branches ### Code Branches
When raising a PR for a fix or a new feature, it is important to target the right branch. When raising a PR for a fix or a new feature, it is important to target the right branch.

View File

@ -151,7 +151,6 @@ module.exports = function(grunt) {
"packages/node_modules/@node-red/editor-client/src/js/font-awesome.js", "packages/node_modules/@node-red/editor-client/src/js/font-awesome.js",
"packages/node_modules/@node-red/editor-client/src/js/history.js", "packages/node_modules/@node-red/editor-client/src/js/history.js",
"packages/node_modules/@node-red/editor-client/src/js/validators.js", "packages/node_modules/@node-red/editor-client/src/js/validators.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/mermaid.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/utils.js", "packages/node_modules/@node-red/editor-client/src/js/ui/utils.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js", "packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js", "packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js",

View File

@ -64,7 +64,7 @@
"mqtt": "4.3.7", "mqtt": "4.3.7",
"multer": "1.4.5-lts.1", "multer": "1.4.5-lts.1",
"mustache": "4.2.0", "mustache": "4.2.0",
"node-red-admin": "^3.1.0", "node-red-admin": "^3.1.1",
"node-watch": "0.7.4", "node-watch": "0.7.4",
"nopt": "5.0.0", "nopt": "5.0.0",
"oauth2orize": "1.11.1", "oauth2orize": "1.11.1",
@ -109,7 +109,7 @@
"jquery-i18next": "1.2.1", "jquery-i18next": "1.2.1",
"jsdoc-nr-template": "github:node-red/jsdoc-nr-template", "jsdoc-nr-template": "github:node-red/jsdoc-nr-template",
"marked": "4.3.0", "marked": "4.3.0",
"mermaid": "^9.4.3", "mermaid": "^10.4.0",
"minami": "1.2.3", "minami": "1.2.3",
"mocha": "9.2.2", "mocha": "9.2.2",
"node-red-node-test-helper": "^0.3.2", "node-red-node-test-helper": "^0.3.2",

View File

@ -339,6 +339,8 @@ module.exports = {
} }
theme.codeEditor = theme.codeEditor || {} theme.codeEditor = theme.codeEditor || {}
theme.codeEditor.options = Object.assign({}, themePlugin.monacoOptions, theme.codeEditor.options); theme.codeEditor.options = Object.assign({}, themePlugin.monacoOptions, theme.codeEditor.options);
theme.mermaid = Object.assign({}, themePlugin.mermaid, theme.mermaid)
} }
activeThemeInitialised = true; activeThemeInitialised = true;
} }

View File

@ -1215,11 +1215,9 @@
"validator": { "validator": {
"errors": { "errors": {
"invalid-json": "Invalid JSON data: __error__", "invalid-json": "Invalid JSON data: __error__",
"invalid-json-prop": "__prop__: invalid JSON data: __error__", "invalid-expr": "Invalid JSONata expression: __error__",
"invalid-prop": "Invalid property expression", "invalid-prop": "Invalid property expression",
"invalid-prop-prop": "__prop__: invalid property expression",
"invalid-num": "Invalid number", "invalid-num": "Invalid number",
"invalid-num-prop": "__prop__: invalid number",
"invalid-regexp": "Invalid input pattern", "invalid-regexp": "Invalid input pattern",
"invalid-regex-prop": "__prop__: invalid input pattern", "invalid-regex-prop": "__prop__: invalid input pattern",
"missing-required-prop": "__prop__: property value missing", "missing-required-prop": "__prop__: property value missing",

View File

@ -119,7 +119,7 @@
"searchInput": "Rechercher vos flux", "searchInput": "Rechercher vos flux",
"subflows": "Sous-flux", "subflows": "Sous-flux",
"createSubflow": "Créer un sous-flux", "createSubflow": "Créer un sous-flux",
"selectionToSubflow": "Selection d'un sous-flux", "selectionToSubflow": "Convertir en sous-flux",
"flows": "Flux", "flows": "Flux",
"add": "Ajouter", "add": "Ajouter",
"rename": "Renommer", "rename": "Renommer",
@ -274,23 +274,23 @@
"recoveredNodesInfo": "Les noeuds importés sur ce flux contiennent un mauvais identifiant de flux. Ces noeuds ont été ajoutés à ce flux afin que vous puissiez les restaurer ou les supprimer.", "recoveredNodesInfo": "Les noeuds importés sur ce flux contiennent un mauvais identifiant de flux. Ces noeuds ont été ajoutés à ce flux afin que vous puissiez les restaurer ou les supprimer.",
"recoveredNodesNotification": "<p>Noeuds importés sans identifiant de flux valide</p><p>Ils ont été ajoutés à un nouveau flux appelé '__flowName__'.</p>", "recoveredNodesNotification": "<p>Noeuds importés sans identifiant de flux valide</p><p>Ils ont été ajoutés à un nouveau flux appelé '__flowName__'.</p>",
"export": { "export": {
"selected": "noeuds sélectionnés", "selected": "les noeuds sélectionnés",
"current": "flux actuel", "current": "le flux actuel",
"all": "tous les flux", "all": "tous les flux",
"compact": "condensé", "compact": "Condensé",
"formatted": "formaté", "formatted": "Formaté",
"copy": "Copier dans le presse-papier", "copy": "Copier dans le presse-papier",
"export": "Exporter vers la bibliothèque", "export": "Exporter vers la bibliothèque",
"exportAs": "Exporter en tant que", "exportAs": "Exporter comme",
"overwrite": "Remplacer", "overwrite": "Remplacer",
"exists": "<p><b>\"__file__\"</b> existe déjà.</p><p>Voulez-vous le remplacer ?</p>" "exists": "<p><b>\"__file__\"</b> existe déjà.</p><p>Voulez-vous le remplacer ?</p>"
}, },
"import": { "import": {
"import": "Importer vers", "import": "Importer vers",
"importSelected": "Importation sélectionnée", "importSelected": "Importer la sélection",
"importCopy": "Importer une copie", "importCopy": "Importer une copie",
"viewNodes": "Afficher les noeuds...", "viewNodes": "Vérifier ces noeuds",
"newFlow": "Nouveau flux", "newFlow": "un nouveau flux",
"replace": "Remplacer", "replace": "Remplacer",
"errors": { "errors": {
"notArray": "L'entrée n'est pas un tableau JSON", "notArray": "L'entrée n'est pas un tableau JSON",
@ -299,7 +299,7 @@
"missingType": "L'entrée n'est pas un flux valide - l'élément '__index__' n'a pas de propriété 'type'" "missingType": "L'entrée n'est pas un flux valide - l'élément '__index__' n'a pas de propriété 'type'"
}, },
"conflictNotification1": "Certains des noeuds que vous avez importés existent déjà dans votre espace de travail.", "conflictNotification1": "Certains des noeuds que vous avez importés existent déjà dans votre espace de travail.",
"conflictNotification2": "Sélectionner les noeuds à importer et choisir s'il faut remplacer les noeuds existants ou en importer une copie." "conflictNotification2": "Sélectionnez les noeuds à importer et choisissez s'il faut remplacer les noeuds existants ou en importer une copie."
}, },
"copyMessagePath": "Chemin copié", "copyMessagePath": "Chemin copié",
"copyMessageValue": "Valeur copiée", "copyMessageValue": "Valeur copiée",
@ -391,10 +391,10 @@
"subflowInstances": "Il existe __count__ instance de ce modèle de sous-flux", "subflowInstances": "Il existe __count__ instance de ce modèle de sous-flux",
"subflowInstances_plural": "Il existe __count__ instances de ce modèle de sous-flux", "subflowInstances_plural": "Il existe __count__ instances de ce modèle de sous-flux",
"editSubflowProperties": "modifier les propriétés", "editSubflowProperties": "modifier les propriétés",
"input": "entrées:", "input": "Entrées:",
"output": "sorties:", "output": "Sorties:",
"status": "statut du noeud", "status": "Statut du noeud",
"deleteSubflow": "supprimer le sous-flux", "deleteSubflow": "Supprimer le sous-flux",
"confirmDelete": "Voulez-vous vraiment supprimer ce sous-flux ?", "confirmDelete": "Voulez-vous vraiment supprimer ce sous-flux ?",
"info": "Description", "info": "Description",
"category": "Catégorie", "category": "Catégorie",
@ -416,6 +416,7 @@
}, },
"errors": { "errors": {
"noNodesSelected": "<strong>Impossible de créer un sous-flux</strong> : aucun noeud sélectionné", "noNodesSelected": "<strong>Impossible de créer un sous-flux</strong> : aucun noeud sélectionné",
"acrossMultipleGroups": "Impossible de créer un sous-flux sur plusieurs groupes",
"multipleInputsToSelection": "<strong>Impossible de créer un sous-flux</strong> : plusieurs entrées pour la sélection" "multipleInputsToSelection": "<strong>Impossible de créer un sous-flux</strong> : plusieurs entrées pour la sélection"
} }
}, },
@ -447,8 +448,8 @@
"default": "Par défaut", "default": "Par défaut",
"noDefaultLabel": "Aucune", "noDefaultLabel": "Aucune",
"defaultLabel": "Utiliser l'étiquette par défaut", "defaultLabel": "Utiliser l'étiquette par défaut",
"searchIcons": "Icônes de recherche", "searchIcons": "Rechercher une icône",
"useDefault": "Utilisation par défaut", "useDefault": "Icône par défaut",
"description": "Description", "description": "Description",
"show": "Afficher", "show": "Afficher",
"hide": "Masquer", "hide": "Masquer",
@ -498,13 +499,13 @@
"keyboard": { "keyboard": {
"title": "Raccourcis clavier", "title": "Raccourcis clavier",
"keyboard": "Clavier", "keyboard": "Clavier",
"filterActions": "Actions de filtrage", "filterActions": "Rechercher l'action",
"shortcut": "raccourci", "shortcut": "Raccourci",
"scope": "portée", "scope": "Portée",
"unassigned": "Non attribué", "unassigned": "Non attribué",
"global": "global", "global": "Global",
"workspace": "espace de travail", "workspace": "Espace de travail",
"editor": "boîte de dialogue d'édition", "editor": "Boîte de dialogue d'édition",
"selectAll": "Tout sélectionner", "selectAll": "Tout sélectionner",
"selectNone": "Ne rien sélectionner", "selectNone": "Ne rien sélectionner",
"selectAllConnected": "Sélectionner tous les éléments connectés", "selectAllConnected": "Sélectionner tous les éléments connectés",
@ -550,22 +551,22 @@
}, },
"palette": { "palette": {
"noInfo": "Pas d'information disponible", "noInfo": "Pas d'information disponible",
"filter": "Filtrer les noeuds", "filter": "Rechercher le noeud",
"search": "Rechercher les modules", "search": "Rechercher les modules",
"addCategory": "Ajouter un nouveau...", "addCategory": "Ajouter un nouveau...",
"label": { "label": {
"subflows": "sous-flux", "subflows": "Sous-flux",
"network": "réseau", "network": "Réseau",
"common": "commun", "common": "Commun",
"input": "entrée", "input": "Entrée",
"output": "sortie", "output": "Sortie",
"function": "fonction", "function": "Fonction",
"sequence": "séquence", "sequence": "Séquence",
"parser": "analyseur", "parser": "Analyseur",
"social": "social", "social": "Social",
"storage": "stockage", "storage": "Stockage",
"analysis": "analyse", "analysis": "Analyse",
"advanced": "avancé" "advanced": "Avancé"
}, },
"actions": { "actions": {
"collapse-all": "Réduire toutes les catégories", "collapse-all": "Réduire toutes les catégories",
@ -586,6 +587,7 @@
"editor": { "editor": {
"title": "Gérer la palette", "title": "Gérer la palette",
"palette": "Palette", "palette": "Palette",
"allCatalogs": "Tous les catalogues",
"times": { "times": {
"seconds": "il y a quelques secondes", "seconds": "il y a quelques secondes",
"minutes": "il y a quelques minutes", "minutes": "il y a quelques minutes",
@ -609,24 +611,25 @@
"nodeCount_plural": "__label__ noeuds", "nodeCount_plural": "__label__ noeuds",
"moduleCount": "__count__ module disponible", "moduleCount": "__count__ module disponible",
"moduleCount_plural": "__count__ modules disponibles", "moduleCount_plural": "__count__ modules disponibles",
"inuse": "en cours d'utilisation", "inuse": "En cours d'utilisation",
"enableall": "activer tout", "enableall": "Activer tout",
"disableall": "désactiver tout", "disableall": "Désactiver tout",
"enable": "activer", "enable": "Activer",
"disable": "désactiver", "disable": "Désactiver",
"remove": "supprimer", "remove": "Supprimer",
"update": "mettre à jour vers __version__", "update": "Mettre à jour vers __version__",
"updated": "mis à jour", "updated": "Mis à jour",
"install": "installer", "install": "Installer",
"installed": "installé", "installed": "Installé",
"conflict": "conflit", "conflict": "Conflit",
"conflictTip": "<p>Ce module ne peut pas être installé car il inclut un<br/>type de noeud qui a déjà été installé</p><p>Conflits avec <code>__module__</code></p>", "conflictTip": "<p>Ce module ne peut pas être installé car il inclut un<br/>type de noeud qui a déjà été installé</p><p>Conflits avec <code>__module__</code></p>",
"loading": "Chargement des catalogues...", "loading": "Chargement des catalogues...",
"tab-nodes": "Noeuds", "tab-nodes": "Noeuds",
"tab-install": "Installer", "tab-install": "Installer",
"sort": "trier:", "sort": "Trier:",
"sortAZ": "a-z", "sortRelevance": "Pertinence",
"sortRecent": "récent", "sortAZ": "A-Z",
"sortRecent": "Récent",
"more": "+ __count__ en plus", "more": "+ __count__ en plus",
"upload": "Charger le fichier tgz du module", "upload": "Charger le fichier tgz du module",
"refresh": "Actualiser la liste des modules", "refresh": "Actualiser la liste des modules",
@ -667,7 +670,7 @@
"info": { "info": {
"name": "Information", "name": "Information",
"tabName": "Nom", "tabName": "Nom",
"label": "info", "label": "Info",
"node": "Noeud", "node": "Noeud",
"type": "Type", "type": "Type",
"group": "Groupe", "group": "Groupe",
@ -681,10 +684,10 @@
"properties": "Propriétés", "properties": "Propriétés",
"info": "Information", "info": "Information",
"desc": "Description", "desc": "Description",
"blank": "vide", "blank": "Vide",
"null": "nul", "null": "Nul",
"showMore": "afficher en plus", "showMore": "Afficher en plus",
"showLess": "afficher en moins", "showLess": "Afficher en moins",
"flow": "Flux", "flow": "Flux",
"selection": "Sélection", "selection": "Sélection",
"nodes": "__count__ noeuds", "nodes": "__count__ noeuds",
@ -695,7 +698,7 @@
"arrayItems": "__count__ éléments", "arrayItems": "__count__ éléments",
"showTips": "Vous pouvez ouvrir les astuces à partir du panneau des paramètres", "showTips": "Vous pouvez ouvrir les astuces à partir du panneau des paramètres",
"outline": "Plan", "outline": "Plan",
"empty": "vide", "empty": "Vide",
"globalConfig": "Noeuds de configuration globale", "globalConfig": "Noeuds de configuration globale",
"triggerAction": "Déclencher une action", "triggerAction": "Déclencher une action",
"find": "Rechercher dans l'espace de travail", "find": "Rechercher dans l'espace de travail",
@ -706,7 +709,7 @@
}, },
"help": { "help": {
"name": "Aide", "name": "Aide",
"label": "aide", "label": "Aide",
"search": "Aide à la recherche", "search": "Aide à la recherche",
"nodeHelp": "Aide sur les noeuds", "nodeHelp": "Aide sur les noeuds",
"showHelp": "Afficher l'aide", "showHelp": "Afficher l'aide",
@ -717,23 +720,23 @@
}, },
"config": { "config": {
"name": "Noeuds de configuration", "name": "Noeuds de configuration",
"label": "configuration", "label": "Configuration",
"global": "Tous les flux", "global": "Tous les flux",
"none": "aucun", "none": "Aucun",
"subflows": "sous-flux", "subflows": "Sous-flux",
"flows": "flux", "flows": "Flux",
"filterAll": "tout", "filterAll": "Tout",
"showAllConfigNodes": "Afficher tous les noeuds de configuration", "showAllConfigNodes": "Afficher tous les noeuds de configuration",
"filterUnused": "inutilisé", "filterUnused": "Inutilisé",
"showAllUnusedConfigNodes": "Afficher tous les noeuds de configuration inutilisés", "showAllUnusedConfigNodes": "Afficher tous les noeuds de configuration inutilisés",
"filtered": "__count__ caché(s)" "filtered": "__count__ caché(s)"
}, },
"context": { "context": {
"name": "Données contextuelles", "name": "Données contextuelles",
"label": "contexte", "label": "Contexte",
"none": "aucune sélection", "none": "Aucune sélection",
"refresh": "actualiser pour charger", "refresh": "Actualiser pour charger",
"empty": "vide", "empty": "Vide",
"node": "Noeud", "node": "Noeud",
"flow": "Flux", "flow": "Flux",
"global": "Global", "global": "Global",
@ -744,10 +747,10 @@
}, },
"palette": { "palette": {
"name": "Gestion des palettes", "name": "Gestion des palettes",
"label": "palette" "label": "Palette"
}, },
"project": { "project": {
"label": "projet", "label": "Projet",
"name": "Projet", "name": "Projet",
"description": "Description", "description": "Description",
"dependencies": "Dépendances", "dependencies": "Dépendances",
@ -760,11 +763,11 @@
"showProjectSettings": "Afficher les paramètres du projet", "showProjectSettings": "Afficher les paramètres du projet",
"projectSettings": { "projectSettings": {
"title": "Paramètres du projet", "title": "Paramètres du projet",
"edit": "modifier", "edit": "Modifier",
"none": "Vide", "none": "Vide",
"install": "installer", "install": "Installer",
"removeFromProject": "supprimer du projet", "removeFromProject": "Supprimer du projet",
"addToProject": "ajouter au projet", "addToProject": "Ajouter au projet",
"files": "Fichiers", "files": "Fichiers",
"flow": "Flux", "flow": "Flux",
"credentials": "Identifiants", "credentials": "Identifiants",
@ -812,7 +815,7 @@
"workflowAutoTip": "Les modifications sont validées automatiquement à chaque déploiement", "workflowAutoTip": "Les modifications sont validées automatiquement à chaque déploiement",
"sshKeys": "Clés SSH", "sshKeys": "Clés SSH",
"sshKeysTip": "Vous permet de créer des connexions sécurisées aux référentiels Git distants.", "sshKeysTip": "Vous permet de créer des connexions sécurisées aux référentiels Git distants.",
"add": "ajouter une clé", "add": "Ajouter une clé",
"addSshKey": "Ajouter une clé SSH", "addSshKey": "Ajouter une clé SSH",
"addSshKeyTip": "Générer une nouvelle paire de clés publique/privée", "addSshKeyTip": "Générer une nouvelle paire de clés publique/privée",
"name": "Nom", "name": "Nom",
@ -848,7 +851,7 @@
"none": "Vide", "none": "Vide",
"conflictResolve": "Tous les conflits ont été résolus. Valider les modifications pour terminer la fusion.", "conflictResolve": "Tous les conflits ont été résolus. Valider les modifications pour terminer la fusion.",
"localFiles": "Fichiers locaux", "localFiles": "Fichiers locaux",
"all": "tout", "all": "Tout",
"unmergedChanges": "Modifications non fusionnées", "unmergedChanges": "Modifications non fusionnées",
"abortMerge": "Abandonner la fusion", "abortMerge": "Abandonner la fusion",
"commit": "Valider", "commit": "Valider",
@ -1097,9 +1100,9 @@
"desc8": "Le fichier contenant les identifiants ne sera pas crypté et son contenu sera facilement lisible", "desc8": "Le fichier contenant les identifiants ne sera pas crypté et son contenu sera facilement lisible",
"create-project-files": "Créer des fichiers de projet", "create-project-files": "Créer des fichiers de projet",
"create-project": "Créer un projet", "create-project": "Créer un projet",
"already-exists": "existe déjà", "already-exists": "Existe déjà",
"git-error": "Erreur Git", "git-error": "Erreur Git",
"git-auth-error": "erreur d'authentification Git" "git-auth-error": "Erreur d'authentification Git"
}, },
"create-success": { "create-success": {
"success": "Vous avez créé avec succès votre premier projet !", "success": "Vous avez créé avec succès votre premier projet !",
@ -1135,8 +1138,8 @@
"desc2": "Avant de pouvoir cloner un référentiel sur ssh, vous devez ajouter une clé SSH pour y accéder.", "desc2": "Avant de pouvoir cloner un référentiel sur ssh, vous devez ajouter une clé SSH pour y accéder.",
"add-ssh-key": "Ajouter une clé ssh", "add-ssh-key": "Ajouter une clé ssh",
"credentials-encryption-key": "Clé de chiffrement des identifiants", "credentials-encryption-key": "Clé de chiffrement des identifiants",
"already-exists-2": "existe déjà", "already-exists-2": "Existe déjà",
"git-error": "erreur git", "git-error": "Erreur git",
"con-failed": "La connexion a échoué", "con-failed": "La connexion a échoué",
"not-git": "Ce n'est pas un dépôt git", "not-git": "Ce n'est pas un dépôt git",
"no-resource": "Référentiel introuvable", "no-resource": "Référentiel introuvable",
@ -1148,8 +1151,8 @@
"confirm": "Voulez-vous vraiment supprimer ce projet ?" "confirm": "Voulez-vous vraiment supprimer ce projet ?"
}, },
"create-project-list": { "create-project-list": {
"search": "rechercher vos projets", "search": "Rechercher vos projets",
"current": "actuel" "current": "Actuel"
}, },
"require-clean": { "require-clean": {
"confirm": "<p>Vous avez des modifications non déployées qui seront perdues.</p><p>Voulez-vous continuer ?</p>" "confirm": "<p>Vous avez des modifications non déployées qui seront perdues.</p><p>Voulez-vous continuer ?</p>"
@ -1212,11 +1215,8 @@
"validator": { "validator": {
"errors": { "errors": {
"invalid-json": "Données JSON invalides : __error__", "invalid-json": "Données JSON invalides : __error__",
"invalid-json-prop": "__prop__: données JSON invalides : __error__",
"invalid-prop": "Expression de propriété non valide", "invalid-prop": "Expression de propriété non valide",
"invalid-prop-prop": "__prop__: expression de propriété invalide",
"invalid-num": "Numéro invalide", "invalid-num": "Numéro invalide",
"invalid-num-prop": "__prop__: numéro invalide",
"invalid-regexp": "Modèle d'entrée non valide", "invalid-regexp": "Modèle d'entrée non valide",
"invalid-regex-prop": "__prop__: modèle d'entrée non valide", "invalid-regex-prop": "__prop__: modèle d'entrée non valide",
"missing-required-prop": "__prop__: valeur de la propriété manquante", "missing-required-prop": "__prop__: valeur de la propriété manquante",

View File

@ -270,5 +270,9 @@
"$moment": { "$moment": {
"args": "[str]", "args": "[str]",
"desc": "Obtient un objet de date à l'aide de la bibliothèque Moment." "desc": "Obtient un objet de date à l'aide de la bibliothèque Moment."
},
"$clone": {
"args": "valeur",
"desc": "Cloner un objet en toute sécurité."
} }
} }

View File

@ -1215,11 +1215,8 @@
"validator": { "validator": {
"errors": { "errors": {
"invalid-json": "JSONデータが不正: __error__", "invalid-json": "JSONデータが不正: __error__",
"invalid-json-prop": "__prop__: JSONデータが不正: __error__",
"invalid-prop": "プロパティ式が不正", "invalid-prop": "プロパティ式が不正",
"invalid-prop-prop": "__prop__: プロパティ式が不正",
"invalid-num": "数値が不正", "invalid-num": "数値が不正",
"invalid-num-prop": "__prop__: 数値が不正",
"invalid-regexp": "入力パターンが不正", "invalid-regexp": "入力パターンが不正",
"invalid-regex-prop": "__prop__: 入力パターンが不正", "invalid-regex-prop": "__prop__: 入力パターンが不正",
"missing-required-prop": "__prop__: プロパティが未設定", "missing-required-prop": "__prop__: プロパティが未設定",

View File

@ -1186,11 +1186,8 @@
"validator": { "validator": {
"errors": { "errors": {
"invalid-json": "Dados JSON inválidos: __error__", "invalid-json": "Dados JSON inválidos: __error__",
"invalid-json-prop": "__prop__: dados JSON inválidos: __error__",
"invalid-prop": "Expressão de propriedade inválida", "invalid-prop": "Expressão de propriedade inválida",
"invalid-prop-prop": "__prop__: expressão de propriedade inválida",
"invalid-num": "Número inválido", "invalid-num": "Número inválido",
"invalid-num-prop": "__prop__: número inválido",
"invalid-regexp": "Padrão de entrada inválido", "invalid-regexp": "Padrão de entrada inválido",
"invalid-regex-prop": "__prop__: Padrão de entrada inválido", "invalid-regex-prop": "__prop__: Padrão de entrada inválido",
"missing-required-prop": "__prop__: valor de propriedade ausente", "missing-required-prop": "__prop__: valor de propriedade ausente",

View File

@ -1199,11 +1199,8 @@
"validator": { "validator": {
"errors": { "errors": {
"invalid-json": "无效的 JSON 数据: __error__", "invalid-json": "无效的 JSON 数据: __error__",
"invalid-json-prop": "__prop__: 无效的 JSON 数据: __error__",
"invalid-prop": "无效的属性表达式", "invalid-prop": "无效的属性表达式",
"invalid-prop-prop": "__prop__: 无效的属性表达式",
"invalid-num": "无效的数字", "invalid-num": "无效的数字",
"invalid-num-prop": "__prop__: 无效的数字",
"invalid-regexp": "输入格式无效", "invalid-regexp": "输入格式无效",
"invalid-regex-prop": "__prop__: 输入格式无效", "invalid-regex-prop": "__prop__: 输入格式无效",
"missing-required-prop": "__prop__: 缺少属性值", "missing-required-prop": "__prop__: 缺少属性值",

View File

@ -797,8 +797,8 @@ RED.nodes = (function() {
if (node && node._def.onremove) { if (node && node._def.onremove) {
// Deprecated: never documented but used by some early nodes // Deprecated: never documented but used by some early nodes
console.log("Deprecated API warning: node type ",node.type," has an onremove function - should be oneditremove - please report"); console.log("Deprecated API warning: node type ",node.type," has an onremove function - should be oneditdelete - please report");
node._def.onremove.call(n); node._def.onremove.call(node);
} }
return {links:removedLinks,nodes:removedNodes}; return {links:removedLinks,nodes:removedNodes};
} }
@ -2198,6 +2198,12 @@ RED.nodes = (function() {
} }
node._config.x = node.x; node._config.x = node.x;
node._config.y = node.y; node._config.y = node.y;
if (n.hasOwnProperty('w')) {
node.w = n.w
}
if (n.hasOwnProperty('h')) {
node.h = n.h
}
} else if (n.type.substring(0,7) === "subflow") { } else if (n.type.substring(0,7) === "subflow") {
var parentId = n.type.split(":")[1]; var parentId = n.type.split(":")[1];
var subflow = subflow_denylist[parentId]||subflow_map[parentId]||getSubflow(parentId); var subflow = subflow_denylist[parentId]||subflow_map[parentId]||getSubflow(parentId);

View File

@ -498,6 +498,15 @@ var RED = (function() {
] ]
} }
} }
} else if (notificationId === 'restart-required') {
options.buttons = [
{
text: RED._("common.label.close"),
click: function() {
persistentNotifications[notificationId].hideNotification();
}
}
]
} }
if (!persistentNotifications.hasOwnProperty(notificationId)) { if (!persistentNotifications.hasOwnProperty(notificationId)) {
persistentNotifications[notificationId] = RED.notify(text,options); persistentNotifications[notificationId] = RED.notify(text,options);
@ -525,6 +534,10 @@ var RED = (function() {
RED.view.redrawStatus(node); RED.view.redrawStatus(node);
} }
}); });
let pendingNodeRemovedNotifications = []
let pendingNodeRemovedTimeout
RED.comms.subscribe("notification/node/#",function(topic,msg) { RED.comms.subscribe("notification/node/#",function(topic,msg) {
var i,m; var i,m;
var typeList; var typeList;
@ -562,8 +575,15 @@ var RED = (function() {
m = msg[i]; m = msg[i];
info = RED.nodes.removeNodeSet(m.id); info = RED.nodes.removeNodeSet(m.id);
if (info.added) { if (info.added) {
typeList = "<ul><li>"+m.types.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>"; pendingNodeRemovedNotifications = pendingNodeRemovedNotifications.concat(m.types.map(RED.utils.sanitize))
RED.notify(RED._("palette.event.nodeRemoved", {count:m.types.length})+typeList,"success"); if (pendingNodeRemovedTimeout) {
clearTimeout(pendingNodeRemovedTimeout)
}
pendingNodeRemovedTimeout = setTimeout(function () {
typeList = "<ul><li>"+pendingNodeRemovedNotifications.join("</li><li>")+"</li></ul>";
RED.notify(RED._("palette.event.nodeRemoved", {count:pendingNodeRemovedNotifications.length})+typeList,"success");
pendingNodeRemovedNotifications = []
}, 200)
} }
} }
loadIconList(); loadIconList();

View File

@ -182,7 +182,9 @@
valueLabel: contextLabel valueLabel: contextLabel
}, },
str: {value:"str",label:"string",icon:"red/images/typedInput/az.svg"}, str: {value:"str",label:"string",icon:"red/images/typedInput/az.svg"},
num: {value:"num",label:"number",icon:"red/images/typedInput/09.svg",validate:/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/}, num: {value:"num",label:"number",icon:"red/images/typedInput/09.svg",validate: function(v) {
return (true === RED.utils.validateTypedProperty(v, "num"));
} },
bool: {value:"bool",label:"boolean",icon:"red/images/typedInput/bool.svg",options:["true","false"]}, bool: {value:"bool",label:"boolean",icon:"red/images/typedInput/bool.svg",options:["true","false"]},
json: { json: {
value:"json", value:"json",

View File

@ -168,8 +168,8 @@ RED.contextMenu = (function () {
menuItems.push( menuItems.push(
null, null,
{ onselect: 'core:undo', disabled: RED.history.list().length === 0 }, { onselect: 'core:undo', label: RED._("keyboard.undoChange"), disabled: RED.history.list().length === 0 },
{ onselect: 'core:redo', disabled: RED.history.listRedo().length === 0 }, { onselect: 'core:redo', label: RED._("keyboard.redoChange"), disabled: RED.history.listRedo().length === 0 },
null, null,
{ onselect: 'core:cut-selection-to-internal-clipboard', label: RED._("keyboard.cutNode"), disabled: !canEdit || !hasSelection }, { onselect: 'core:cut-selection-to-internal-clipboard', label: RED._("keyboard.cutNode"), disabled: !canEdit || !hasSelection },
{ onselect: 'core:copy-selection-to-internal-clipboard', label: RED._("keyboard.copyNode"), disabled: !hasSelection }, { onselect: 'core:copy-selection-to-internal-clipboard', label: RED._("keyboard.copyNode"), disabled: !hasSelection },
@ -177,7 +177,7 @@ RED.contextMenu = (function () {
{ onselect: 'core:delete-selection', disabled: !canEdit || !canDelete }, { onselect: 'core:delete-selection', disabled: !canEdit || !canDelete },
{ onselect: 'core:delete-selection-and-reconnect', label: RED._('keyboard.deleteReconnect'), disabled: !canEdit || !canDelete }, { onselect: 'core:delete-selection-and-reconnect', label: RED._('keyboard.deleteReconnect'), disabled: !canEdit || !canDelete },
{ onselect: 'core:show-export-dialog', label: RED._("menu.label.export") }, { onselect: 'core:show-export-dialog', label: RED._("menu.label.export") },
{ onselect: 'core:select-all-nodes' }, { onselect: 'core:select-all-nodes', label: RED._("keyboard.selectAll") },
) )
} }

View File

@ -989,9 +989,10 @@ RED.diff = (function() {
} }
if (localNode && remoteNode && typeof localNode[d] === "string") { if (localNode && remoteNode && typeof localNode[d] === "string") {
if (/\n/.test(localNode[d]) || /\n/.test(remoteNode[d])) { if (/\n/.test(localNode[d]) || /\n/.test(remoteNode[d])) {
$('<button class="red-ui-button red-ui-button-small red-ui-diff-text-diff-button"><i class="fa fa-file-o"> <i class="fa fa-caret-left"></i> <i class="fa fa-caret-right"></i> <i class="fa fa-file-o"></i></button>').on("click", function() { var textDiff = $('<button class="red-ui-button red-ui-button-small red-ui-diff-text-diff-button"><i class="fa fa-file-o"> <i class="fa fa-caret-left"></i> <i class="fa fa-caret-right"></i> <i class="fa fa-file-o"></i></button>').on("click", function() {
showTextDiff(localNode[d],remoteNode[d]); showTextDiff(localNode[d],remoteNode[d]);
}).appendTo(propertyNameCell); }).appendTo(propertyNameCell);
RED.popover.tooltip(textDiff, RED._("diff.compareChanges"));
} }
} }

View File

@ -115,8 +115,9 @@ RED.editor = (function() {
var valid = validateNodeProperty(node, definition, prop, properties[prop]); var valid = validateNodeProperty(node, definition, prop, properties[prop]);
if ((typeof valid) === "string") { if ((typeof valid) === "string") {
result.push(valid); result.push(valid);
} } else if (Array.isArray(valid)) {
else if(!valid) { result = result.concat(valid)
} else if(!valid) {
result.push(prop); result.push(prop);
} }
} }
@ -165,7 +166,7 @@ RED.editor = (function() {
// If the validator takes two arguments, it is a 3.x validator that // If the validator takes two arguments, it is a 3.x validator that
// can return a String to mean 'invalid' and provide a reason // can return a String to mean 'invalid' and provide a reason
if ((definition[property].validate.length === 2) && if ((definition[property].validate.length === 2) &&
((typeof valid) === "string")) { ((typeof valid) === "string") || Array.isArray(valid)) {
return valid; return valid;
} else { } else {
// Otherwise, a 2.x returns a truth-like/false-like value that // Otherwise, a 2.x returns a truth-like/false-like value that
@ -181,6 +182,17 @@ RED.editor = (function() {
error: err.message error: err.message
}); });
} }
} else if (valid) {
// If the validator is not provided in node property => Check if the input has a validator
if ("category" in node._def) {
const isConfig = node._def.category === "config";
const prefix = isConfig ? "node-config-input" : "node-input";
const input = $("#"+prefix+"-"+property);
const isTypedInput = input.length > 0 && input.next(".red-ui-typedInput-container").length > 0;
if (isTypedInput) {
valid = input.typedInput("validate");
}
}
} }
if (valid && definition[property].type && RED.nodes.getType(definition[property].type) && !("validate" in definition[property])) { if (valid && definition[property].type && RED.nodes.getType(definition[property].type) && !("validate" in definition[property])) {
if (!value || value == "_ADD_") { if (!value || value == "_ADD_") {

View File

@ -121,7 +121,7 @@
var i=0,l=bufferBinValue.length; var i=0,l=bufferBinValue.length;
var c = 0; var c = 0;
for(i=0;i<l;i++) { for(i=0;i<l;i++) {
var d = parseInt(bufferBinValue[i]); var d = parseInt(Number(bufferBinValue[i]));
if (!isString && (isNaN(d) || d < 0 || d > 255)) { if (!isString && (isNaN(d) || d < 0 || d > 255)) {
valid = false; valid = false;
break; break;

View File

@ -966,12 +966,10 @@ RED.editor.codeEditor.monaco = (function() {
//Unbind ctrl-Enter (default action is to insert a newline in editor) This permits the shortcut to close the tray. //Unbind ctrl-Enter (default action is to insert a newline in editor) This permits the shortcut to close the tray.
try { try {
ed._standaloneKeybindingService.addDynamicKeybinding( monaco.editor.addKeybindingRule({keybinding: 0, command: "-editor.action.insertLineAfter"});
'-editor.action.insertLineAfter', // command ID prefixed by '-' } catch (error) {
null, // keybinding console.warn(error)
() => {} // need to pass an empty handler }
);
} catch (error) { }
ed.nodered = { ed.nodered = {
refreshModuleLibs: refreshModuleLibs //expose this for function node externalModules refresh refreshModuleLibs: refreshModuleLibs //expose this for function node externalModules refresh

View File

@ -169,7 +169,7 @@
var currentScrollTop = $(".red-ui-editor-type-markdown-panel-preview").scrollTop(); var currentScrollTop = $(".red-ui-editor-type-markdown-panel-preview").scrollTop();
$(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue())); $(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue()));
$(".red-ui-editor-type-markdown-panel-preview").scrollTop(currentScrollTop); $(".red-ui-editor-type-markdown-panel-preview").scrollTop(currentScrollTop);
mermaid.init(); RED.editor.mermaid.render()
},200); },200);
}) })
if (options.header) { if (options.header) {
@ -178,7 +178,7 @@
if (value) { if (value) {
$(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue())); $(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue()));
mermaid.init(); RED.editor.mermaid.render()
} }
panels = RED.panels.create({ panels = RED.panels.create({
id:"red-ui-editor-type-markdown-panels", id:"red-ui-editor-type-markdown-panels",

View File

@ -0,0 +1,54 @@
RED.editor.mermaid = (function () {
let initializing = false
let loaded = false
let pendingEvals = []
let diagramIds = 0
function render(selector = '.mermaid') {
// $(selector).hide()
if (!loaded) {
pendingEvals.push(selector)
if (!initializing) {
initializing = true
$.getScript(
'vendor/mermaid/mermaid.min.js',
function (data, stat, jqxhr) {
mermaid.initialize({
startOnLoad: false,
theme: RED.settings.get('mermaid', {}).theme
})
loaded = true
while(pendingEvals.length > 0) {
const pending = pendingEvals.shift()
render(pending)
}
}
)
}
} else {
const nodes = document.querySelectorAll(selector)
nodes.forEach(async node => {
if (!node.getAttribute('mermaid-processed')) {
const mermaidContent = node.innerText
node.setAttribute('mermaid-processed', true)
try {
const { svg } = await mermaid.render('mermaid-render-'+Date.now()+'-'+(diagramIds++), mermaidContent);
node.innerHTML = svg
} catch (err) {
$('<div>').css({
fontSize: '0.8em',
border: '1px solid var(--red-ui-border-color-error)',
padding: '5px',
marginBottom: '10px',
}).text(err.toString()).prependTo(node)
}
}
})
}
}
return {
render: render,
};
})();

View File

@ -196,7 +196,7 @@
} }
$('<div class="form-row">'+ $('<div class="form-row">'+
'<label for="node-input-show-label-btn" data-i18n="editor.label"></label>'+ '<label for="node-input-show-label" data-i18n="editor.label"></label>'+
'<span style="margin-right: 2px;"/>'+ '<span style="margin-right: 2px;"/>'+
'<input type="checkbox" id="node-input-show-label"/>'+ '<input type="checkbox" id="node-input-show-label"/>'+
'</div>').appendTo(dialogForm); '</div>').appendTo(dialogForm);

View File

@ -1,46 +0,0 @@
// Mermaid diagram stub library for on-demand dynamic loading
// Will be overwritten after script loading by $.getScript
var mermaid = (function () {
var enabled /* = undefined */;
var initializing = false;
var initCalled = false;
function initialize(opt) {
if (enabled === undefined) {
if (RED.settings.markdownEditor &&
RED.settings.markdownEditor.mermaid) {
enabled = RED.settings.markdownEditor.mermaid.enabled;
}
else {
enabled = true;
}
}
if (enabled) {
initializing = true;
$.getScript("vendor/mermaid/mermaid.min.js",
function (data, stat, jqxhr) {
$(".mermaid").show();
// invoke loaded mermaid API
initializing = false;
mermaid.initialize(opt);
if (initCalled) {
mermaid.init();
initCalled = false;
}
});
}
}
function init() {
if (initializing) {
$(".mermaid").hide();
initCalled = true;
}
}
return {
initialize: initialize,
init: init,
};
})();

View File

@ -166,7 +166,7 @@ RED.projects.settings = (function() {
var description = addTargetToExternalLinks($('<span class="red-ui-text-bidi-aware" dir=\"'+RED.text.bidi.resolveBaseTextDir(desc)+'">'+desc+'</span>')).appendTo(container); var description = addTargetToExternalLinks($('<span class="red-ui-text-bidi-aware" dir=\"'+RED.text.bidi.resolveBaseTextDir(desc)+'">'+desc+'</span>')).appendTo(container);
description.find(".red-ui-text-bidi-aware").contents().filter(function() { return this.nodeType === 3 && this.textContent.trim() !== "" }).wrap( "<span></span>" ); description.find(".red-ui-text-bidi-aware").contents().filter(function() { return this.nodeType === 3 && this.textContent.trim() !== "" }).wrap( "<span></span>" );
setTimeout(function () { setTimeout(function () {
mermaid.init(); RED.editor.mermaid.render()
}, 200); }, 200);
} }

View File

@ -647,9 +647,9 @@ RED.sidebar.versionControl = (function() {
$.getJSON("projects/"+activeProject.name+"/commits/"+entry.sha,function(result) { $.getJSON("projects/"+activeProject.name+"/commits/"+entry.sha,function(result) {
result.project = activeProject; result.project = activeProject;
result.parents = entry.parents; result.parents = entry.parents;
result.oldRev = entry.sha+"~1"; result.oldRev = entry.parents[0].length !== 0 ? entry.sha+"~1" : entry.sha;
result.newRev = entry.sha; result.newRev = entry.sha;
result.oldRevTitle = RED._("sidebar.project.versionControl.commitCapital")+" "+entry.sha.substring(0,7)+"~1"; result.oldRevTitle = entry.parents[0].length !== 0 ? RED._("sidebar.project.versionControl.commitCapital")+" "+entry.sha.substring(0,7)+"~1" : " ";
result.newRevTitle = RED._("sidebar.project.versionControl.commitCapital")+" "+entry.sha.substring(0,7); result.newRevTitle = RED._("sidebar.project.versionControl.commitCapital")+" "+entry.sha.substring(0,7);
result.date = humanizeSinceDate(parseInt(entry.date)); result.date = humanizeSinceDate(parseInt(entry.date));
RED.diff.showCommitDiff(result); RED.diff.showCommitDiff(result);

View File

@ -383,6 +383,7 @@ RED.sidebar.help = (function() {
$(this).toggleClass('expanded',!isExpanded); $(this).toggleClass('expanded',!isExpanded);
}) })
helpSection.parent().scrollTop(0); helpSection.parent().scrollTop(0);
RED.editor.mermaid.render()
} }
function set(html,title) { function set(html,title) {

View File

@ -464,7 +464,7 @@ RED.sidebar.info = (function() {
} }
$(this).toggleClass('expanded',!isExpanded); $(this).toggleClass('expanded',!isExpanded);
}); });
mermaid.init(); RED.editor.mermaid.render()
} }
var tips = (function() { var tips = (function() {

View File

@ -323,7 +323,7 @@ RED.typeSearch = (function() {
} }
} }
function applyFilter(filter,type,def) { function applyFilter(filter,type,def) {
return !filter || return !def || !filter ||
( (
(!filter.spliceMultiple) && (!filter.spliceMultiple) &&
(!filter.type || type === filter.type) && (!filter.type || type === filter.type) &&

View File

@ -101,28 +101,8 @@ RED.utils = (function() {
renderer.code = function (code, lang) { renderer.code = function (code, lang) {
if(lang === "mermaid") { if(lang === "mermaid") {
// mermaid diagram rendering return `<pre class='mermaid'>${code}</pre>`;
if (mermaidIsEnabled === undefined) { } else {
if (RED.settings.markdownEditor &&
RED.settings.markdownEditor.mermaid) {
mermaidIsEnabled = RED.settings.markdownEditor.mermaid.enabled;
}
else {
mermaidIsEnabled = true;
}
}
if (mermaidIsEnabled) {
if (!mermaidIsInitialized) {
mermaidIsInitialized = true;
mermaid.initialize({startOnLoad:false});
}
return `<pre class='mermaid'>${code}</pre>`;
}
else {
return `<details><summary>${RED._("markdownEditor.mermaid.summary")}</summary><pre><code>${code}</code></pre></details>`;
}
}
else {
return "<pre><code>" +code +"</code></pre>"; return "<pre><code>" +code +"</code></pre>";
} }
}; };
@ -917,6 +897,51 @@ RED.utils = (function() {
} }
} }
/**
* Checks a typed property is valid according to the type.
* Returns true if valid.
* Return String error message if invalid
* @param {*} propertyType
* @param {*} propertyValue
* @returns true if valid, String if invalid
*/
function validateTypedProperty(propertyValue, propertyType, opt) {
let error
if (propertyType === 'json') {
try {
JSON.parse(propertyValue);
} catch(err) {
error = RED._("validator.errors.invalid-json", {
error: err.message
})
}
} else if (propertyType === 'msg' || propertyType === 'flow' || propertyType === 'global' ) {
if (!RED.utils.validatePropertyExpression(propertyValue)) {
error = RED._("validator.errors.invalid-prop")
}
} else if (propertyType === 'num') {
if (!/^NaN$|^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$|^[+-]?(0b|0B)[01]+$|^[+-]?(0o|0O)[0-7]+$|^[+-]?(0x|0X)[0-9a-fA-F]+$/.test(propertyValue)) {
error = RED._("validator.errors.invalid-num")
}
} else if (propertyType === 'jsonata') {
try {
jsonata(propertyValue)
} catch(err) {
error = RED._("validator.errors.invalid-expr", {
error: err.message
})
}
}
if (error) {
if (opt && opt.label) {
return opt.label+': '+error
}
return error
}
return true
}
function getMessageProperty(msg,expr) { function getMessageProperty(msg,expr) {
var result = null; var result = null;
var msgPropParts; var msgPropParts;
@ -1451,6 +1476,7 @@ RED.utils = (function() {
getDarkerColor: getDarkerColor, getDarkerColor: getDarkerColor,
parseModuleList: parseModuleList, parseModuleList: parseModuleList,
checkModuleAllowed: checkModuleAllowed, checkModuleAllowed: checkModuleAllowed,
getBrowserInfo: getBrowserInfo getBrowserInfo: getBrowserInfo,
validateTypedProperty: validateTypedProperty
} }
})(); })();

View File

@ -4187,7 +4187,7 @@ RED.view = (function() {
nodeEl.__statusGroup__.style.display = "none"; nodeEl.__statusGroup__.style.display = "none";
} else { } else {
nodeEl.__statusGroup__.style.display = "inline"; nodeEl.__statusGroup__.style.display = "inline";
let backgroundWidth = 12 let backgroundWidth = 15
var fill = status_colours[d.status.fill]; // Only allow our colours for now var fill = status_colours[d.status.fill]; // Only allow our colours for now
if (d.status.shape == null && fill == null) { if (d.status.shape == null && fill == null) {
backgroundWidth = 0 backgroundWidth = 0
@ -4207,7 +4207,11 @@ RED.view = (function() {
nodeEl.__statusLabel__.textContent = ""; nodeEl.__statusLabel__.textContent = "";
} }
const textSize = nodeEl.__statusLabel__.getBBox() const textSize = nodeEl.__statusLabel__.getBBox()
nodeEl.__statusBackground__.setAttribute('width', backgroundWidth + textSize.width + 6) backgroundWidth += textSize.width
if (backgroundWidth > 0 && textSize.width > 0) {
backgroundWidth += 6
}
nodeEl.__statusBackground__.setAttribute('width', backgroundWidth)
} }
delete d.dirtyStatus; delete d.dirtyStatus;
} }
@ -4619,8 +4623,8 @@ RED.view = (function() {
statusBackground.setAttribute("y",-1); statusBackground.setAttribute("y",-1);
statusBackground.setAttribute("width",200); statusBackground.setAttribute("width",200);
statusBackground.setAttribute("height",13); statusBackground.setAttribute("height",13);
statusBackground.setAttribute("rx",1); statusBackground.setAttribute("rx",2);
statusBackground.setAttribute("ry",1); statusBackground.setAttribute("ry",2);
statusEl.appendChild(statusBackground); statusEl.appendChild(statusBackground);
node[0][0].__statusBackground__ = statusBackground; node[0][0].__statusBackground__ = statusBackground;

View File

@ -17,6 +17,8 @@
RED.workspaces = (function() { RED.workspaces = (function() {
const documentTitle = document.title;
var activeWorkspace = 0; var activeWorkspace = 0;
var workspaceIndex = 0; var workspaceIndex = 0;
@ -339,12 +341,18 @@ RED.workspaces = (function() {
$("#red-ui-workspace-chart").show(); $("#red-ui-workspace-chart").show();
activeWorkspace = tab.id; activeWorkspace = tab.id;
window.location.hash = 'flow/'+tab.id; window.location.hash = 'flow/'+tab.id;
if (tab.label) {
document.title = `${documentTitle} : ${tab.label}`
} else {
document.title = documentTitle
}
$("#red-ui-workspace").toggleClass("red-ui-workspace-disabled", !!tab.disabled); $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled", !!tab.disabled);
$("#red-ui-workspace").toggleClass("red-ui-workspace-locked", !!tab.locked); $("#red-ui-workspace").toggleClass("red-ui-workspace-locked", !!tab.locked);
} else { } else {
$("#red-ui-workspace-chart").hide(); $("#red-ui-workspace-chart").hide();
activeWorkspace = 0; activeWorkspace = 0;
window.location.hash = ''; window.location.hash = '';
document.title = documentTitle
} }
event.workspace = activeWorkspace; event.workspace = activeWorkspace;
RED.events.emit("workspace:change",event); RED.events.emit("workspace:change",event);

View File

@ -40,46 +40,29 @@ RED.validators = {
return opt ? RED._("validator.errors.invalid-regexp") : false; return opt ? RED._("validator.errors.invalid-regexp") : false;
}; };
}, },
typedInput: function(ptypeName,isConfig,mopt) { typedInput: function(ptypeName, isConfig, mopt) {
let options = ptypeName
if (typeof ptypeName === 'string' ) {
options = {}
options.typeField = ptypeName
options.isConfig = isConfig
options.allowBlank = false
}
return function(v, opt) { return function(v, opt) {
var ptype = $("#node-"+(isConfig?"config-":"")+"input-"+ptypeName).val() || this[ptypeName]; let ptype = options.type
if (ptype === 'json') { if (!ptype && options.typeField) {
try { ptype = $("#node-"+(options.isConfig?"config-":"")+"input-"+options.typeField).val() || this[options.typeField];
JSON.parse(v);
return true;
} catch(err) {
if (opt && opt.label) {
return RED._("validator.errors.invalid-json-prop", {
error: err.message,
prop: opt.label,
});
}
return opt ? RED._("validator.errors.invalid-json", {
error: err.message
}) : false;
}
} else if (ptype === 'msg' || ptype === 'flow' || ptype === 'global' ) {
if (RED.utils.validatePropertyExpression(v)) {
return true;
}
if (opt && opt.label) {
return RED._("validator.errors.invalid-prop-prop", {
prop: opt.label
});
}
return opt ? RED._("validator.errors.invalid-prop") : false;
} else if (ptype === 'num') {
if (/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/.test(v)) {
return true;
}
if (opt && opt.label) {
return RED._("validator.errors.invalid-num-prop", {
prop: opt.label
});
}
return opt ? RED._("validator.errors.invalid-num") : false;
} }
return true; if (options.allowBlank && v === '') {
}; return true
}
const result = RED.utils.validateTypedProperty(v, ptype, opt)
if (result === true || opt) {
// Valid, or opt provided - return result as-is
return result
}
// No opt - need to return false for backwards compatibilty
return false
}
} }
}; };

View File

@ -114,6 +114,7 @@
pointer-events: stroke; pointer-events: stroke;
} }
.red-ui-flow-group-outline-select { .red-ui-flow-group-outline-select {
cursor: move;
fill: none; fill: none;
stroke: var(--red-ui-node-selected-color); stroke: var(--red-ui-node-selected-color);
pointer-events: none; pointer-events: none;

View File

@ -825,6 +825,7 @@ div.red-ui-projects-dialog-ssh-public-key {
margin-top: 0 !important; margin-top: 0 !important;
padding: 5px 10px; padding: 5px 10px;
margin-bottom: 10px; margin-bottom: 10px;
border-radius: 3px 3px 0px 0px;
} }
} }

View File

@ -320,7 +320,7 @@
} }
// but replace with repeat one if set to repeat // but replace with repeat one if set to repeat
if ((this.repeat && this.repeat != 0) || this.crontab) { if ((this.repeat && this.repeat != 0) || this.crontab) {
suffix = " ↻"; suffix = "\t↻";
} }
if (this.name) { if (this.name) {
return this.name+suffix; return this.name+suffix;

View File

@ -109,9 +109,8 @@ module.exports = function(RED) {
} }
const p = props.shift() const p = props.shift()
const property = p.p; const property = p.p;
const value = p.v ? p.v : ''; const value = p.v !== undefined ? p.v : '';
const valueType = p.vt ? p.vt : 'str'; const valueType = p.vt !== undefined ? p.vt : 'str';
if (property) { if (property) {
if (valueType === "jsonata") { if (valueType === "jsonata") {
if (p.v) { if (p.v) {

View File

@ -86,7 +86,7 @@
}, },
label: function() { label: function() {
var suffix = ""; var suffix = "";
if (this.console === true || this.console === "true") { suffix = " ⇲"; } if (this.console === true || this.console === "true") { suffix = "\t⇲"; }
if (this.targetType === "jsonata") { if (this.targetType === "jsonata") {
return (this.name || "JSONata") + suffix; return (this.name || "JSONata") + suffix;
} }
@ -195,6 +195,119 @@
node.dirty = true; node.dirty = true;
}); });
RED.view.redraw(); RED.view.redraw();
},
requestDebugNodeList: function(filteredNodes) {
var workspaceOrder = RED.nodes.getWorkspaceOrder();
var workspaceOrderMap = {};
workspaceOrder.forEach(function(ws,i) {
workspaceOrderMap[ws] = i;
});
var candidateNodes = [];
var candidateSFs = [];
var subflows = {};
RED.nodes.eachNode(function (n) {
var nt = n.type;
if (nt === "debug") {
if (n.z in workspaceOrderMap) {
candidateNodes.push(n);
}
else {
var sf = RED.nodes.subflow(n.z);
if (sf) {
subflows[sf.id] = {
debug: true,
subflows: {}
};
}
}
}
else if(nt.substring(0, 8) === "subflow:") {
if (n.z in workspaceOrderMap) {
candidateSFs.push(n);
}
else {
var psf = RED.nodes.subflow(n.z);
if (psf) {
var sid = nt.substring(8);
var item = subflows[psf.id];
if (!item) {
item = {
debug: undefined,
subflows: {}
};
subflows[psf.id] = item;
}
item.subflows[sid] = true;
}
}
}
});
candidateSFs.forEach(function (sf) {
var sid = sf.type.substring(8);
if (containsDebug(sid, subflows)) {
candidateNodes.push(sf);
}
});
candidateNodes.sort(function(A,B) {
var wsA = workspaceOrderMap[A.z];
var wsB = workspaceOrderMap[B.z];
if (wsA !== wsB) {
return wsA-wsB;
}
var labelA = RED.utils.getNodeLabel(A,A.id);
var labelB = RED.utils.getNodeLabel(B,B.id);
return labelA.localeCompare(labelB);
});
var currentWs = null;
var data = [];
var currentFlow;
var currentSelectedCount = 0;
candidateNodes.forEach(function(node) {
if (currentWs !== node.z) {
if (currentFlow && currentFlow.checkbox) {
currentFlow.selected = currentSelectedCount === currentFlow.children.length
}
currentSelectedCount = 0;
currentWs = node.z;
var parent = RED.nodes.workspace(currentWs) || RED.nodes.subflow(currentWs);
currentFlow = {
label: RED.utils.getNodeLabel(parent, currentWs),
}
if (!parent.disabled) {
currentFlow.children = [];
currentFlow.checkbox = true;
} else {
currentFlow.class = "disabled"
}
data.push(currentFlow);
}
if (currentFlow.children) {
if (!filteredNodes[node.id]) {
currentSelectedCount++;
}
currentFlow.children.push({
label: RED.utils.getNodeLabel(node,node.id),
node: {
id: node.id
},
checkbox: true,
selected: !filteredNodes[node.id]
});
}
});
if (currentFlow && currentFlow.checkbox) {
currentFlow.selected = currentSelectedCount === currentFlow.children.length
}
if (subWindow) {
try {
subWindow.postMessage({event:"refreshDebugNodeList", nodes:data},"*");
} catch(err) {
console.log(err);
}
}
RED.debug.refreshDebugNodeList(data)
} }
}; };
@ -396,6 +509,26 @@
} }
} }
function containsDebug(sid, map) {
var item = map[sid];
if (item) {
if (item.debug === undefined) {
var sfs = Object.keys(item.subflows);
var contain = false;
for (var i = 0; i < sfs.length; i++) {
var sf = sfs[i];
if (containsDebug(sf, map)) {
contain = true;
break;
}
}
item.debug = contain;
}
return item.debug;
}
return false;
}
$("#red-ui-sidebar-debug-open").on("click", function(e) { $("#red-ui-sidebar-debug-open").on("click", function(e) {
e.preventDefault(); e.preventDefault();
subWindow = window.open(document.location.toString().replace(/[?#].*$/,"")+"debug/view/view.html"+document.location.search,"nodeREDDebugView","menubar=no,location=no,toolbar=no,chrome,height=500,width=600"); subWindow = window.open(document.location.toString().replace(/[?#].*$/,"")+"debug/view/view.html"+document.location.search,"nodeREDDebugView","menubar=no,location=no,toolbar=no,chrome,height=500,width=600");
@ -427,6 +560,8 @@
options.messageSourceClick(msg.id,msg._alias,msg.path); options.messageSourceClick(msg.id,msg._alias,msg.path);
} else if (msg.event === "clear") { } else if (msg.event === "clear") {
options.clear(); options.clear();
} else if (msg.event === "requestDebugNodeList") {
options.requestDebugNodeList(msg.filteredNodes)
} }
}; };
window.addEventListener('message',this.handleWindowMessage); window.addEventListener('message',this.handleWindowMessage);

View File

@ -275,7 +275,7 @@
value: [], value: [],
type: "link in[]", type: "link in[]",
validate: function (v, opt) { validate: function (v, opt) {
if ((this.linkType === "static" && v.length > 0) if (((this.linkType || "static") === "static" && v.length > 0)
|| this.linkType === "dynamic") { || this.linkType === "dynamic") {
return true; return true;
} }

View File

@ -167,19 +167,13 @@ RED.debug = (function() {
var menu = RED.popover.menu({ var menu = RED.popover.menu({
options: options, options: options,
onselect: function(item) { onselect: function(item) {
if (item.value !== filterType) { setFilterType(item.value)
filterType = item.value;
$('#red-ui-sidebar-debug-filter span').text(RED._('node-red:debug.sidebar.'+filterType));
refreshMessageList();
RED.settings.set("debug.filter",filterType)
}
if (filterType === 'filterSelected') { if (filterType === 'filterSelected') {
refreshDebugNodeList(); config.requestDebugNodeList(filteredNodes);
filterDialog.slideDown(200); filterDialog.slideDown(200);
filterDialogShown = true; filterDialogShown = true;
debugNodeTreeList.focus(); debugNodeTreeList.focus();
} }
} }
}); });
menu.show({ menu.show({
@ -254,131 +248,7 @@ RED.debug = (function() {
} }
function refreshDebugNodeList(data) {
function containsDebug(sid, map) {
var item = map[sid];
if (item) {
if (item.debug === undefined) {
var sfs = Object.keys(item.subflows);
var contain = false;
for (var i = 0; i < sfs.length; i++) {
var sf = sfs[i];
if (containsDebug(sf, map)) {
contain = true;
break;
}
}
item.debug = contain;
}
return item.debug;
}
return false;
}
function refreshDebugNodeList() {
var workspaceOrder = RED.nodes.getWorkspaceOrder();
var workspaceOrderMap = {};
workspaceOrder.forEach(function(ws,i) {
workspaceOrderMap[ws] = i;
});
var candidateNodes = [];
var candidateSFs = [];
var subflows = {};
RED.nodes.eachNode(function (n) {
var nt = n.type;
if (nt === "debug") {
if (n.z in workspaceOrderMap) {
candidateNodes.push(n);
}
else {
var sf = RED.nodes.subflow(n.z);
if (sf) {
subflows[sf.id] = {
debug: true,
subflows: {}
};
}
}
}
else if(nt.substring(0, 8) === "subflow:") {
if (n.z in workspaceOrderMap) {
candidateSFs.push(n);
}
else {
var psf = RED.nodes.subflow(n.z);
if (psf) {
var sid = nt.substring(8);
var item = subflows[psf.id];
if (!item) {
item = {
debug: undefined,
subflows: {}
};
subflows[psf.id] = item;
}
item.subflows[sid] = true;
}
}
}
});
candidateSFs.forEach(function (sf) {
var sid = sf.type.substring(8);
if (containsDebug(sid, subflows)) {
candidateNodes.push(sf);
}
});
candidateNodes.sort(function(A,B) {
var wsA = workspaceOrderMap[A.z];
var wsB = workspaceOrderMap[B.z];
if (wsA !== wsB) {
return wsA-wsB;
}
var labelA = RED.utils.getNodeLabel(A,A.id);
var labelB = RED.utils.getNodeLabel(B,B.id);
return labelA.localeCompare(labelB);
});
var currentWs = null;
var data = [];
var currentFlow;
var currentSelectedCount = 0;
candidateNodes.forEach(function(node) {
if (currentWs !== node.z) {
if (currentFlow && currentFlow.checkbox) {
currentFlow.selected = currentSelectedCount === currentFlow.children.length
}
currentSelectedCount = 0;
currentWs = node.z;
var parent = RED.nodes.workspace(currentWs) || RED.nodes.subflow(currentWs);
currentFlow = {
label: RED.utils.getNodeLabel(parent, currentWs),
}
if (!parent.disabled) {
currentFlow.children = [];
currentFlow.checkbox = true;
} else {
currentFlow.class = "disabled"
}
data.push(currentFlow);
}
if (currentFlow.children) {
if (!filteredNodes[node.id]) {
currentSelectedCount++;
}
currentFlow.children.push({
label: RED.utils.getNodeLabel(node,node.id),
node: node,
checkbox: true,
selected: !filteredNodes[node.id]
});
}
});
if (currentFlow && currentFlow.checkbox) {
currentFlow.selected = currentSelectedCount === currentFlow.children.length
}
debugNodeTreeList.treeList("data", data); debugNodeTreeList.treeList("data", data);
} }
@ -401,7 +271,7 @@ RED.debug = (function() {
},200); },200);
} }
function _refreshMessageList(_activeWorkspace) { function _refreshMessageList(_activeWorkspace) {
if (_activeWorkspace) { if (typeof _activeWorkspace === 'string') {
activeWorkspace = _activeWorkspace.replace(/\./g,"_"); activeWorkspace = _activeWorkspace.replace(/\./g,"_");
} }
if (filterType === "filterAll") { if (filterType === "filterAll") {
@ -479,12 +349,12 @@ RED.debug = (function() {
filteredNodes[n.id] = true; filteredNodes[n.id] = true;
}); });
delete filteredNodes[sourceId]; delete filteredNodes[sourceId];
$("#red-ui-sidebar-debug-filterSelected").trigger("click");
RED.settings.set('debug.filteredNodes',Object.keys(filteredNodes)) RED.settings.set('debug.filteredNodes',Object.keys(filteredNodes))
setFilterType('filterSelected')
refreshMessageList(); refreshMessageList();
}}, }},
{id:"red-ui-debug-msg-menu-item-clear-filter",label:RED._("node-red:debug.messageMenu.clearFilter"),onselect:function(){ {id:"red-ui-debug-msg-menu-item-clear-filter",label:RED._("node-red:debug.messageMenu.clearFilter"),onselect:function(){
$("#red-ui-sidebar-debug-filterAll").trigger("click"); clearFilterSettings()
refreshMessageList(); refreshMessageList();
}} }}
); );
@ -713,9 +583,17 @@ RED.debug = (function() {
if (!!clearFilter) { if (!!clearFilter) {
clearFilterSettings(); clearFilterSettings();
} }
refreshDebugNodeList(); config.requestDebugNodeList(filteredNodes);
} }
function setFilterType(type) {
if (type !== filterType) {
filterType = type;
$('#red-ui-sidebar-debug-filter span').text(RED._('node-red:debug.sidebar.'+filterType));
refreshMessageList();
RED.settings.set("debug.filter",filterType)
}
}
function clearFilterSettings() { function clearFilterSettings() {
filteredNodes = {}; filteredNodes = {};
filterType = 'filterAll'; filterType = 'filterAll';
@ -728,6 +606,7 @@ RED.debug = (function() {
init: init, init: init,
refreshMessageList:refreshMessageList, refreshMessageList:refreshMessageList,
handleDebugMessage: handleDebugMessage, handleDebugMessage: handleDebugMessage,
clearMessageList: clearMessageList clearMessageList: clearMessageList,
refreshDebugNodeList: refreshDebugNodeList
} }
})(); })();

View File

@ -12,6 +12,9 @@ $(function() {
}, },
clear: function() { clear: function() {
window.opener.postMessage({event:"clear"},'*'); window.opener.postMessage({event:"clear"},'*');
},
requestDebugNodeList: function(filteredNodes) {
window.opener.postMessage({event: 'requestDebugNodeList', filteredNodes},'*')
} }
} }
@ -26,6 +29,8 @@ $(function() {
RED.debug.refreshMessageList(evt.data.activeWorkspace); RED.debug.refreshMessageList(evt.data.activeWorkspace);
} else if (evt.data.event === "projectChange") { } else if (evt.data.event === "projectChange") {
RED.debug.clearMessageList(true); RED.debug.clearMessageList(true);
} else if (evt.data.event === "refreshDebugNodeList") {
RED.debug.refreshDebugNodeList(evt.data.nodes)
} }
},false); },false);
} catch(err) { } catch(err) {

View File

@ -103,7 +103,6 @@
} else if (type === "istype") { } else if (type === "istype") {
r.v = rule.find(".node-input-rule-type-value").typedInput('type'); r.v = rule.find(".node-input-rule-type-value").typedInput('type');
r.vt = rule.find(".node-input-rule-type-value").typedInput('type'); r.vt = rule.find(".node-input-rule-type-value").typedInput('type');
r.vt = (r.vt === "number") ? "num" : "str";
} else if (type === "jsonata_exp") { } else if (type === "jsonata_exp") {
r.v = rule.find(".node-input-rule-exp-value").typedInput('value'); r.v = rule.find(".node-input-rule-exp-value").typedInput('value');
r.vt = rule.find(".node-input-rule-exp-value").typedInput('type'); r.vt = rule.find(".node-input-rule-exp-value").typedInput('type');
@ -168,7 +167,33 @@
label:RED._("node-red:common.label.payload"), label:RED._("node-red:common.label.payload"),
validate: RED.validators.typedInput("propertyType", false)}, validate: RED.validators.typedInput("propertyType", false)},
propertyType: { value:"msg" }, propertyType: { value:"msg" },
rules: {value:[{t:"eq", v:"", vt:"str"}]}, rules: {
value:[{t:"eq", v:"", vt:"str"}],
validate: function (rules, opt) {
let msg;
const errors = []
if (!rules || rules.length === 0) { return true }
for (var i=0;i<rules.length;i++) {
const opt = { label: RED._('node-red:switch.label.rule')+' '+(i+1) }
const r = rules[i];
if (r.hasOwnProperty('v')) {
if ((msg = RED.utils.validateTypedProperty(r.v,r.vt,opt)) !== true) {
errors.push(msg)
}
}
if (r.hasOwnProperty('v2')) {
if ((msg = RED.utils.validateTypedProperty(r.v2,r.v2t,opt)) !== true) {
errors.push(msg)
}
}
}
if (errors.length) {
console.log(errors)
return errors
}
return true;
}
},
checkall: {value:"true", required:true}, checkall: {value:"true", required:true},
repair: {value:false}, repair: {value:false},
outputs: {value:1} outputs: {value:1}
@ -218,7 +243,11 @@
if (i > 0) { if (i > 0) {
var lastRule = $("#node-input-rule-container").editableList('getItemAt',i-1); var lastRule = $("#node-input-rule-container").editableList('getItemAt',i-1);
var exportedRule = exportRule(lastRule.element); var exportedRule = exportRule(lastRule.element);
opt.r.vt = exportedRule.vt; if (exportedRule.t === "istype") {
opt.r.vt = (exportedRule.vt === "number") ? "num" : "str";
} else {
opt.r.vt = exportedRule.vt;
}
opt.r.v = ""; opt.r.v = "";
// We could copy the value over as well and preselect it (see the 'activeElement' code below) // We could copy the value over as well and preselect it (see the 'activeElement' code below)
// But not sure that feels right. Is copying over the last value 'expected' behaviour? // But not sure that feels right. Is copying over the last value 'expected' behaviour?

View File

@ -19,71 +19,42 @@
<script type="text/javascript"> <script type="text/javascript">
(function() { (function() {
function isInvalidProperty(v,vt) {
if (/msg|flow|global/.test(vt)) {
if (!RED.utils.validatePropertyExpression(v)) {
return RED._("node-red:change.errors.invalid-prop", {
property: v
});
}
} else if (vt === "jsonata") {
try{ jsonata(v); } catch(e) {
return RED._("node-red:change.errors.invalid-expr", {
error: e.message
});
}
} else if (vt === "json") {
try{ JSON.parse(v); } catch(e) {
return RED._("node-red:change.errors.invalid-json-data", {
error: e.message
});
}
}
return false;
}
RED.nodes.registerType('change', { RED.nodes.registerType('change', {
color: "#E2D96E", color: "#E2D96E",
category: 'function', category: 'function',
defaults: { defaults: {
name: {value:""}, name: {value:""},
rules:{value:[{t:"set",p:"payload",pt:"msg",to:"",tot:"str"}],validate: function(rules, opt) { rules:{
var msg; value:[{t:"set",p:"payload",pt:"msg",to:"",tot:"str"}],
if (!rules || rules.length === 0) { return true } validate: function(rules, opt) {
for (var i=0;i<rules.length;i++) { let msg;
var r = rules[i]; const errors = []
if (r.t === 'set') { if (!rules || rules.length === 0) { return true }
if (msg = isInvalidProperty(r.p,r.pt)) { for (var i=0;i<rules.length;i++) {
return msg; const opt = { label: RED._('node-red:change.label.rule')+' '+(i+1) }
const r = rules[i];
if (r.t === 'set' || r.t === 'change' || r.t === 'delete' || r.t === 'move') {
if ((msg = RED.utils.validateTypedProperty(r.p,r.pt,opt)) !== true) {
errors.push(msg)
}
} }
if (msg = isInvalidProperty(r.to,r.tot)) { if (r.t === 'set' || r.t === 'change' || r.t === 'move') {
return msg; if ((msg = RED.utils.validateTypedProperty(r.to,r.tot,opt)) !== true) {
errors.push(msg)
}
} }
} else if (r.t === 'change') { if (r.t === 'change') {
if (msg = isInvalidProperty(r.p,r.pt)) { if ((msg = RED.utils.validateTypedProperty(r.from,r.fromt,opt)) !== true) {
return msg; errors.push(msg)
} }
if(msg = isInvalidProperty(r.from,r.fromt)) {
return msg;
}
if(msg = isInvalidProperty(r.to,r.tot)) {
return msg;
}
} else if (r.t === 'delete') {
if (msg = isInvalidProperty(r.p,r.pt)) {
return msg;
}
} else if (r.t === 'move') {
if (msg = isInvalidProperty(r.p,r.pt)) {
return msg;
}
if (msg = isInvalidProperty(r.to,r.tot)) {
return msg;
} }
} }
if (errors.length) {
return errors
}
return true;
} }
return true; },
}},
// legacy // legacy
action: {value:""}, action: {value:""},
property: {value:""}, property: {value:""},

View File

@ -57,7 +57,12 @@
action: {value:"scale"}, action: {value:"scale"},
round: {value:false}, round: {value:false},
property: {value:"payload",required:true, property: {value:"payload",required:true,
label:RED._("node-red:common.label.property")}, label:RED._("node-red:common.label.property"),
validate: RED.validators.typedInput({ type: 'msg' })
},
// RED.validators.typedInput("propertyType", false)},
name: {value:""} name: {value:""}
}, },
inputs: 1, inputs: 1,

View File

@ -153,7 +153,7 @@
} }
var editorRow = $("#dialog-form>div.node-text-editor-row"); var editorRow = $("#dialog-form>div.node-text-editor-row");
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom"))); height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$(".node-text-editor").css("height",height+"px"); $("#dialog-form .node-text-editor").css("height",height+"px");
this.editor.resize(); this.editor.resize();
} }
}); });

View File

@ -284,7 +284,7 @@ module.exports = function(RED) {
done(); done();
} }
} }
else { else if (!msg.hasOwnProperty("reset")) {
if (maxKeptMsgsCount(node) > 0) { if (maxKeptMsgsCount(node) > 0) {
if (node.intervalID === -1) { if (node.intervalID === -1) {
node.send(msg); node.send(msg);

View File

@ -56,7 +56,7 @@
color:"darksalmon", color:"darksalmon",
defaults: { defaults: {
command: {value:""}, command: {value:""},
addpay: {value:""}, addpay: {value:"", validate: RED.validators.typedInput({ type: 'msg', allowBlank: true })},
append: {value:""}, append: {value:""},
useSpawn: {value:"false"}, useSpawn: {value:"false"},
timer: {value:""}, timer: {value:""},

View File

@ -56,9 +56,11 @@
inout: {value:"out"}, inout: {value:"out"},
septopics: {value:true}, septopics: {value:true},
property: {value:"payload", required:true, property: {value:"payload", required:true,
label:RED._("node-red:rbe.label.property")}, label:RED._("node-red:rbe.label.property"),
validate: RED.validators.typedInput({ type: 'msg' })},
topi: {value:"topic", required:true, topi: {value:"topic", required:true,
label:RED._("node-red:rbe.label.topic")} label:RED._("node-red:rbe.label.topic"),
validate: RED.validators.typedInput({ type: 'msg' })}
}, },
inputs:1, inputs:1,
outputs:1, outputs:1,

View File

@ -104,6 +104,7 @@ module.exports = function(RED) {
* @returns `true` if it is a valid topic * @returns `true` if it is a valid topic
*/ */
function isValidPublishTopic(topic) { function isValidPublishTopic(topic) {
if (topic.length === 0) return false;
return !/[\+#\b\f\n\r\t\v\0]/.test(topic); return !/[\+#\b\f\n\r\t\v\0]/.test(topic);
} }
@ -219,8 +220,8 @@ module.exports = function(RED) {
*/ */
function subscriptionHandler(node, datatype ,topic, payload, packet) { function subscriptionHandler(node, datatype ,topic, payload, packet) {
const msg = {topic:topic, payload:null, qos:packet.qos, retain:packet.retain}; const msg = {topic:topic, payload:null, qos:packet.qos, retain:packet.retain};
const v5 = (node && node.brokerConn) const v5 = (node && node.brokerConn)
? node.brokerConn.v5() ? node.brokerConn.v5()
: Object.prototype.hasOwnProperty.call(packet, "properties"); : Object.prototype.hasOwnProperty.call(packet, "properties");
if(v5 && packet.properties) { if(v5 && packet.properties) {
setStrProp(packet.properties, msg, "responseTopic"); setStrProp(packet.properties, msg, "responseTopic");
@ -451,7 +452,7 @@ module.exports = function(RED) {
/** /**
* Perform the disconnect action * Perform the disconnect action
* @param {MQTTInNode|MQTTOutNode} node * @param {MQTTInNode|MQTTOutNode} node
* @param {Function} done * @param {Function} done
*/ */
function handleDisconnectAction(node, done) { function handleDisconnectAction(node, done) {
@ -611,7 +612,7 @@ module.exports = function(RED) {
node.brokerurl = node.url; node.brokerurl = node.url;
} else { } else {
// if the broker is ws:// or wss:// or tcp:// // if the broker is ws:// or wss:// or tcp://
if (node.broker.indexOf("://") > -1) { if ((typeof node.broker === 'string') && node.broker.indexOf("://") > -1) {
node.brokerurl = node.broker; node.brokerurl = node.broker;
// Only for ws or wss, check if proxy env var for additional configuration // Only for ws or wss, check if proxy env var for additional configuration
if (node.brokerurl.indexOf("wss://") > -1 || node.brokerurl.indexOf("ws://") > -1) { if (node.brokerurl.indexOf("wss://") > -1 || node.brokerurl.indexOf("ws://") > -1) {
@ -865,7 +866,7 @@ module.exports = function(RED) {
* Call end and wait for the client to end (or timeout) * Call end and wait for the client to end (or timeout)
* @param {mqtt.MqttClient} client The broker client * @param {mqtt.MqttClient} client The broker client
* @param {number} ms The time to wait for the client to end * @param {number} ms The time to wait for the client to end
* @returns * @returns
*/ */
let waitEnd = (client, ms) => { let waitEnd = (client, ms) => {
return new Promise( (resolve, reject) => { return new Promise( (resolve, reject) => {
@ -905,7 +906,7 @@ module.exports = function(RED) {
node.subid = 1; node.subid = 1;
//typedef for subscription object: //typedef for subscription object:
/** /**
* @typedef {Object} Subscription * @typedef {Object} Subscription
* @property {String} topic - topic to subscribe to * @property {String} topic - topic to subscribe to
* @property {Object} [options] - options object * @property {Object} [options] - options object
@ -933,7 +934,7 @@ module.exports = function(RED) {
const ref = _ref || 0; const ref = _ref || 0;
let options let options
let qos = 1 // default to QoS 1 (AWS and several other brokers don't support QoS 2) let qos = 1 // default to QoS 1 (AWS and several other brokers don't support QoS 2)
// if options is an object, then clone it // if options is an object, then clone it
if (typeof _options == "object") { if (typeof _options == "object") {
options = RED.util.cloneMessage(_options || {}) options = RED.util.cloneMessage(_options || {})
@ -947,7 +948,7 @@ module.exports = function(RED) {
if (typeof qos === "number" && qos >= 0 && qos <= 2) { if (typeof qos === "number" && qos >= 0 && qos <= 2) {
options.qos = qos; options.qos = qos;
} }
subscription.topic = _topic; subscription.topic = _topic;
subscription.qos = qos; subscription.qos = qos;
subscription.options = RED.util.cloneMessage(options); subscription.options = RED.util.cloneMessage(options);
@ -957,16 +958,16 @@ module.exports = function(RED) {
} }
/** /**
* If topic is a subscription object, then use that, otherwise look up the topic in * If topic is a subscription object, then use that, otherwise look up the topic in
* the subscriptions object. If the topic is not found, then create a new subscription * the subscriptions object. If the topic is not found, then create a new subscription
* object and add it to the subscriptions object. * object and add it to the subscriptions object.
* @param {Subscription|String} topic * @param {Subscription|String} topic
* @param {*} options * @param {*} options
* @param {*} callback * @param {*} callback
* @param {*} ref * @param {*} ref
*/ */
node.subscribe = function (topic, options, callback, ref) { node.subscribe = function (topic, options, callback, ref) {
/** @type {Subscription} */ /** @type {Subscription} */
let subscription let subscription
let doCompare = false let doCompare = false
let changesFound = false let changesFound = false
@ -1004,7 +1005,7 @@ module.exports = function(RED) {
_brokerConn.unsubscribe(sub.topic, sub.ref, true) _brokerConn.unsubscribe(sub.topic, sub.ref, true)
} }
}) })
// if subscription is found (or sent in as a parameter), then check for changes. // if subscription is found (or sent in as a parameter), then check for changes.
// if there are any changes requested, tidy up the old subscription // if there are any changes requested, tidy up the old subscription
if (subscription) { if (subscription) {
@ -1091,7 +1092,7 @@ module.exports = function(RED) {
delete sub[ref] delete sub[ref]
} }
} }
// if instructed to remove the actual MQTT client subscription // if instructed to remove the actual MQTT client subscription
if (unsub) { if (unsub) {
// if there are no more subscriptions for the topic, then remove the topic // if there are no more subscriptions for the topic, then remove the topic
if (Object.keys(sub).length === 0) { if (Object.keys(sub).length === 0) {

View File

@ -452,10 +452,6 @@ in your Node-RED user directory (${RED.settings.userDir}).
formData.append(opt, val); formData.append(opt, val);
} else if (typeof val === 'object' && val.hasOwnProperty('value')) { } else if (typeof val === 'object' && val.hasOwnProperty('value')) {
formData.append(opt,val.value,val.options || {}); formData.append(opt,val.value,val.options || {});
} else if (Array.isArray(val)) {
for (var i=0; i<val.length; i++) {
formData.append(opt, val[i])
}
} else { } else {
formData.append(opt,JSON.stringify(val)); formData.append(opt,JSON.stringify(val));
} }

View File

@ -63,7 +63,7 @@ module.exports = function(RED) {
if (!(notemplate && (msg.hasOwnProperty("parts") && msg.parts.hasOwnProperty("index") && msg.parts.index > 0))) { if (!(notemplate && (msg.hasOwnProperty("parts") && msg.parts.hasOwnProperty("index") && msg.parts.index > 0))) {
template = clean(node.template); template = clean(node.template);
} }
var ou = ""; const ou = [];
if (!Array.isArray(msg.payload)) { msg.payload = [ msg.payload ]; } if (!Array.isArray(msg.payload)) { msg.payload = [ msg.payload ]; }
if (node.hdrout !== "none" && node.hdrSent === false) { if (node.hdrout !== "none" && node.hdrSent === false) {
if ((template.length === 1) && (template[0] === '')) { if ((template.length === 1) && (template[0] === '')) {
@ -74,7 +74,7 @@ module.exports = function(RED) {
template = Object.keys(msg.payload[0]); template = Object.keys(msg.payload[0]);
} }
} }
ou += template.map(v => v.indexOf(node.sep)!==-1 ? '"'+v+'"' : v).join(node.sep) + node.ret; ou.push(template.map(v => v.indexOf(node.sep)!==-1 ? '"'+v+'"' : v).join(node.sep));
if (node.hdrout === "once") { node.hdrSent = true; } if (node.hdrout === "once") { node.hdrSent = true; }
} }
for (var s = 0; s < msg.payload.length; s++) { for (var s = 0; s < msg.payload.length; s++) {
@ -93,7 +93,7 @@ module.exports = function(RED) {
msg.payload[s][t] = node.quo + msg.payload[s][t].toString() + node.quo; msg.payload[s][t] = node.quo + msg.payload[s][t].toString() + node.quo;
} }
} }
ou += msg.payload[s].join(node.sep) + node.ret; ou.push(msg.payload[s].join(node.sep));
} }
else { else {
if ((template.length === 1) && (template[0] === '') && (msg.hasOwnProperty("columns"))) { if ((template.length === 1) && (template[0] === '') && (msg.hasOwnProperty("columns"))) {
@ -105,6 +105,7 @@ module.exports = function(RED) {
node.warn(RED._("csv.errors.obj_csv")); node.warn(RED._("csv.errors.obj_csv"));
tmpwarn = false; tmpwarn = false;
} }
const row = [];
for (var p in msg.payload[0]) { for (var p in msg.payload[0]) {
/* istanbul ignore else */ /* istanbul ignore else */
if (msg.payload[s].hasOwnProperty(p)) { if (msg.payload[s].hasOwnProperty(p)) {
@ -118,21 +119,22 @@ module.exports = function(RED) {
} }
if (q.indexOf(node.quo) !== -1) { // add double quotes if any quotes if (q.indexOf(node.quo) !== -1) { // add double quotes if any quotes
q = q.replace(/"/g, '""'); q = q.replace(/"/g, '""');
ou += node.quo + q + node.quo + node.sep; row.push(node.quo + q + node.quo);
} }
else if (q.indexOf(node.sep) !== -1 || p.indexOf("\n") !== -1) { // add quotes if any "commas" or "\n" else if (q.indexOf(node.sep) !== -1 || p.indexOf("\n") !== -1) { // add quotes if any "commas" or "\n"
ou += node.quo + q + node.quo + node.sep; row.push(node.quo + q + node.quo);
} }
else { ou += q + node.sep; } // otherwise just add else { row.push(q); } // otherwise just add
} }
} }
} }
ou = ou.slice(0,-1) + node.ret; ou.push(row.join(node.sep)); // add separator
} }
else { else {
const row = [];
for (var t=0; t < template.length; t++) { for (var t=0; t < template.length; t++) {
if (template[t] === '') { if (template[t] === '') {
ou += node.sep; row.push('');
} }
else { else {
var tt = template[t]; var tt = template[t];
@ -146,19 +148,20 @@ module.exports = function(RED) {
p = RED.util.ensureString(p); p = RED.util.ensureString(p);
if (p.indexOf(node.quo) !== -1) { // add double quotes if any quotes if (p.indexOf(node.quo) !== -1) { // add double quotes if any quotes
p = p.replace(/"/g, '""'); p = p.replace(/"/g, '""');
ou += node.quo + p + node.quo + node.sep; row.push(node.quo + p + node.quo);
} }
else if (p.indexOf(node.sep) !== -1 || p.indexOf("\n") !== -1) { // add quotes if any "commas" or "\n" else if (p.indexOf(node.sep) !== -1 || p.indexOf("\n") !== -1) { // add quotes if any "commas" or "\n"
ou += node.quo + p + node.quo + node.sep; row.push(node.quo + p + node.quo);
} }
else { ou += p + node.sep; } // otherwise just add else { row.push(p); } // otherwise just add
} }
} }
ou = ou.slice(0,-1) + node.ret; // remove final "comma" and add "newline" ou.push(row.join(node.sep)); // add separator
} }
} }
} }
msg.payload = ou; // join lines, don't forget to add the last new line
msg.payload = ou.join(node.ret) + node.ret;
msg.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).join(','); msg.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).join(',');
if (msg.payload !== '') { send(msg); } if (msg.payload !== '') { send(msg); }
done(); done();

View File

@ -41,8 +41,8 @@
color:"#DEBD5C", color:"#DEBD5C",
defaults: { defaults: {
name: {value:""}, name: {value:""},
property: {value:"payload"}, property: {value:"payload", validate: RED.validators.typedInput({ type: 'msg' }) },
outproperty: {value:"payload"}, outproperty: {value:"payload", validate: RED.validators.typedInput({ type: 'msg' }) },
tag: {value:""}, tag: {value:""},
ret: {value:"html"}, ret: {value:"html"},
as: {value:"single"} as: {value:"single"}

View File

@ -32,6 +32,7 @@
defaults: { defaults: {
name: {value:""}, name: {value:""},
property: {value:"payload",required:true, property: {value:"payload",required:true,
validate: RED.validators.typedInput({ type: 'msg' }),
label:RED._("node-red:json.label.property")}, label:RED._("node-red:json.label.property")},
action: {value:""}, action: {value:""},
pretty: {value:false} pretty: {value:false}

View File

@ -27,7 +27,8 @@
defaults: { defaults: {
name: {value:""}, name: {value:""},
property: {value:"payload",required:true, property: {value:"payload",required:true,
label:RED._("node-red:common.label.property")}, label:RED._("node-red:common.label.property"),
validate: RED.validators.typedInput({ type: 'msg' })},
attr: {value:""}, attr: {value:""},
chr: {value:""} chr: {value:""}
}, },

View File

@ -16,6 +16,7 @@
color:"#DEBD5C", color:"#DEBD5C",
defaults: { defaults: {
property: {value:"payload",required:true, property: {value:"payload",required:true,
validate: RED.validators.typedInput({ type: 'msg' }),
label:RED._("node-red:common.label.property")}, label:RED._("node-red:common.label.property")},
name: {value:""} name: {value:""}
}, },

View File

@ -57,7 +57,7 @@
arraySplt: {value:1}, arraySplt: {value:1},
arraySpltType: {value:"len"}, arraySpltType: {value:"len"},
stream: {value:false}, stream: {value:false},
addname: {value:""} addname: {value:"", validate: RED.validators.typedInput({ type: 'msg', allowBlank: true })}
}, },
inputs:1, inputs:1,
outputs:1, outputs:1,
@ -208,7 +208,22 @@
validate:RED.validators.typedInput("propertyType", false) validate:RED.validators.typedInput("propertyType", false)
}, },
propertyType: { value:"msg"}, propertyType: { value:"msg"},
key: {value:"topic"}, key: {value:"topic", validate: (function () {
const typeValidator = RED.validators.typedInput({ type: 'msg' })
return function(v, opt) {
const joinMode = $("#node-input-mode").val() || this.mode
if (joinMode !== 'custom') {
return true
}
const buildType = $("#node-input-build").val() || this.build
if (buildType !== 'object') {
return true
} else {
return typeValidator(v, opt)
}
}
})()
},
joiner: { value:"\\n"}, joiner: { value:"\\n"},
joinerType: { value:"str"}, joinerType: { value:"str"},
accumulate: { value:"false" }, accumulate: { value:"false" },

View File

@ -198,7 +198,7 @@
category: 'storage', category: 'storage',
defaults: { defaults: {
name: {value:""}, name: {value:""},
filename: {value:""}, filename: {value:"", validate: RED.validators.typedInput({ typeField: 'filenameType' })},
filenameType: {value:"str"}, filenameType: {value:"str"},
appendNewline: {value:true}, appendNewline: {value:true},
createDir: {value:false}, createDir: {value:false},
@ -297,7 +297,7 @@
category: 'storage', category: 'storage',
defaults: { defaults: {
name: {value:""}, name: {value:""},
filename: {value:""}, filename: {value:"", validate: RED.validators.typedInput({ typeField: 'filenameType' }) },
filenameType: {value:"str"}, filenameType: {value:"str"},
format: {value:"utf8"}, format: {value:"utf8"},
chunk: {value:false}, chunk: {value:false},

Binary file not shown.

Before

Width:  |  Height:  |  Size: 603 B

View File

@ -0,0 +1 @@
<svg width="40" height="60" viewBox="0, 0, 40, 60" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke="#fff"><path d="m9.7884 22.379c-5.2427-0.41732-9.6475 5.7885-7.4975 10.585 2.0949 5.2041 9.9782 6.6154 13.727 2.4477 3.633-3.5613 5.0332-9.0411 9.4821-11.853 4.5205-3.0872 11.797-0.172 12.68 5.3144 0.86 5.2537-4.8017 10.364-9.9231 8.8205-3.7873-0.85449-6.5051-4.0905-8.0487-7.4975-1.9019-3.2526-4.3882-6.7257-8.2693-7.6077-0.6891-0.15656-1.4003-0.21831-2.1059-0.21721z" stroke-width="3.3"/><path d="m6.7012 29.821h6.6154" stroke-width="1.4"/><path d="m26.988 29.821h5.5128m-2.8115-2.7564v5.5128" stroke-width="1.8"/></g></svg>

After

Width:  |  Height:  |  Size: 635 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1 @@
<svg width="40" height="60" viewBox="0, 0, 40, 60" xmlns="http://www.w3.org/2000/svg"><path d="m8.3474 17.75 22.298 22.444-10.747 13.013v-46.497l10.747 12.428-22.298 21.859" fill="none" stroke="#fff" stroke-width="4"/></svg>

After

Width:  |  Height:  |  Size: 225 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1 @@
<svg width="40" height="60" viewBox="0, 0, 40, 60" xmlns="http://www.w3.org/2000/svg"><path d="m2.7078 12.986c0 7.7994-0.36386 21.569 0 32.545s35.118 9.8751 34.848 0c-0.26959-9.8751 0-24.82 0-32.545 0-7.7243-34.848-7.7995-34.848 0z" fill="none" stroke="#fff"/><g fill="#fff"><path d="m3.8741 13.406v8.955c0.021834 3.5781 19.543 5.0789 25.575 3.2543 0 0 0.02229-2.6683 0.02998-2.6673l5.5325 0.7238c0.64508 0.0844 1.1345-0.74597 1.134-1.3284v-8.573l-0.99896 0.93349-15.217-2.2765c4.5883 2.1798 9.808 4.1312 9.808 4.1312-9.3667 3.1562-25.846-0.31965-25.864-3.1525z"/><path d="m3.886 26.607v8.1052c3.2188 6.1087 29.901 5.8574 32.272 0v-8.1052c-3.3598 4.6685-29.204 5.1534-32.272 0z"/><path d="m4.0032 39.082v7.1522c2.556 7.4622 28.918 7.6072 32.272 0v-7.1522c-3.2345 4.9471-29.087 5.359-32.272 0z"/></g></svg>

After

Width:  |  Height:  |  Size: 806 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 414 B

View File

@ -0,0 +1 @@
<svg width="40" height="60" viewBox="0, 0, 40, 60" xmlns="http://www.w3.org/2000/svg"><path d="m23.515 13.831c-4.7594-5.8789-2.6084-5.7751-7.3474 0-8.0368 10.477-8.3322 24.431 2.5476 32.935 0.13181 2.0418 0.46056 4.9803 0.46056 4.9803h1.315s0.32875-2.9219 0.46017-4.9803c2.8458-2.2339 16.799-14.619 2.5641-32.935z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 671 B

View File

@ -0,0 +1 @@
<svg width="40" height="60" viewBox="0, 0, 40, 60" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke="#fff" stroke-width="3"><path d="m6 30c6 5 24 4 29-0.07"/><path d="m21 33 0.1-19c0.02-4 4-3 4-6s-4-2-4-5"/><path d="m6 22c0-11 29-10 29 0v21c0 18-29 19-29 0s4e-7 -11 0-21z"/></g></svg>

After

Width:  |  Height:  |  Size: 293 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 252 B

View File

@ -0,0 +1 @@
<svg width="40" height="60" viewBox="0, 0, 40, 60" xmlns="http://www.w3.org/2000/svg"><path d="m29 12s0.1 30 0.05 31-3 5-7 5-19 0.04-19 0.04c6-4 9-5 17-5 0 0 4-0.1 4-2 0-2 8e-3 -29 8e-3 -29z" fill="#fff"/><path d="m12 47s-0.1-30-0.05-31 3-5 7-5 19-0.04 19-0.04c-6 4-9 5-17 5 0 0-4 0.1-4 2 0 2-8e-3 29-8e-3 29z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 736 B

View File

@ -0,0 +1 @@
<svg width="40" height="60" viewBox="0, 0, 40, 60" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke="#fff" stroke-width="3"><path class="cls-4" d="m17.639 30.221c-1.7087-0.88225-12.465-5.6284-14.414-6.636-1.9492-1.0075-1.9868-1.7073-0.075164-2.5188 1.9117-0.81145 12.643-5.3861 14.91-6.2738 2.2675-0.8877 3.0517-0.91493 4.9785-0.14704 1.9267 0.76789 12.026 5.1329 13.923 5.8898 1.8966 0.75699 1.9843 1.386 0.02631 2.4861-1.958 1.1001-12.1 5.6611-14.285 6.8729s-3.355 1.2091-5.0636 0.32685z"/><path class="cls-4" d="m32.23 25.251c2.8239 1.2039 4.155 1.764 4.7307 1.9938 1.8966 0.75699 1.9843 1.386 0.0263 2.4861s-12.1 5.6611-14.285 6.8729c-2.1848 1.2117-3.3548 1.209-5.0634 0.32676-1.7087-0.88225-12.465-5.6284-14.414-6.636-1.9492-1.0075-1.9868-1.7073-0.075164-2.5188 10.883-4.6196-9.1087 3.8612 4.9598-2.1076"/><path class="cls-4" d="m32.23 31.961c2.8239 1.2039 4.155 1.764 4.7307 1.9938 1.8966 0.75699 1.9843 1.386 0.0263 2.4861s-12.1 5.6611-14.285 6.8729c-2.1848 1.2117-3.3548 1.209-5.0634 0.32676-1.7087-0.88225-12.465-5.6284-14.414-6.636-1.9492-1.0075-1.9868-1.7073-0.075164-2.5188 10.883-4.6196-9.1087 3.8612 4.9598-2.1076"/></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -36,5 +36,5 @@
<p><b>Remarque</b> : Les options <i>"Intervalle entre les heures"</i> et <i>"à une heure précise"</i> utilisent le système cron standard. <p><b>Remarque</b> : Les options <i>"Intervalle entre les heures"</i> et <i>"à une heure précise"</i> utilisent le système cron standard.
Cela signifie que pour la première option, vous pouvez envoyer un message à intervalle régulier entre les heures voulues. Cela signifie que pour la première option, vous pouvez envoyer un message à intervalle régulier entre les heures voulues.
Si vous voulez envoyer un message toutes les minutes à partir de maintenant, utiliser l'option <i>"intervalle"</i>.</p> Si vous voulez envoyer un message toutes les minutes à partir de maintenant, utiliser l'option <i>"intervalle"</i>.</p>
<p><b>Remarque</b> : Pour inclure une nouvelle ligne dans une chaîne, vous devez utiliser un noeud de fonction pour créer la charge utile.</p> <p><b>Remarque</b> : Pour inclure une nouvelle ligne dans une chaîne, vous devez utiliser soit un noeud de fonction soit le noeud template pour créer la charge utile.</p>
</script> </script>

View File

@ -11,11 +11,11 @@
"expand": "Développer" "expand": "Développer"
}, },
"status": { "status": {
"connected": "connecté", "connected": "Connecté",
"not-connected": "pas connecté", "not-connected": "Pas connecté",
"disconnected": "déconnecté", "disconnected": "Déconnecté",
"connecting": "connexion", "connecting": "Connexion",
"error": "erreur", "error": "Erreur",
"ok": "OK" "ok": "OK"
}, },
"notification": { "notification": {
@ -32,7 +32,7 @@
}, },
"inject": { "inject": {
"inject": "Injecter", "inject": "Injecter",
"injectNow": "injecter maintenant", "injectNow": "Injecter maintenant",
"repeat": "répéter = __repeat__", "repeat": "répéter = __repeat__",
"crontab": "crontab = __crontab__", "crontab": "crontab = __crontab__",
"stopped": "arrêté", "stopped": "arrêté",
@ -98,7 +98,7 @@
"catchUncaught": "catch : non capturé", "catchUncaught": "catch : non capturé",
"label": { "label": {
"source": "Détecter les erreurs de", "source": "Détecter les erreurs de",
"selectAll": "tout sélectionner", "selectAll": "Tout sélectionner",
"uncaught": "Ignorer les erreurs gérées par les autres noeuds Catch" "uncaught": "Ignorer les erreurs gérées par les autres noeuds Catch"
}, },
"scope": { "scope": {
@ -112,7 +112,7 @@
"statusNodes": "statut : __number__", "statusNodes": "statut : __number__",
"label": { "label": {
"source": "Signaler l'état de", "source": "Signaler l'état de",
"sortByType": "trier par type" "sortByType": "Trier par type"
}, },
"scope": { "scope": {
"all": "tous les noeuds", "all": "tous les noeuds",
@ -148,23 +148,23 @@
"deactivated": "Désactivé avec succès : __label__" "deactivated": "Désactivé avec succès : __label__"
}, },
"sidebar": { "sidebar": {
"label": "débogage", "label": "Débogage",
"name": "Messages de débogage", "name": "Messages de débogage",
"filterAll": "tous les noeuds", "filterAll": "Tous les noeuds",
"filterSelected": "noeuds sélectionnés", "filterSelected": "Noeuds sélectionnés",
"filterCurrent": "flux actuel", "filterCurrent": "Flux actuel",
"debugNodes": "noeuds de débogage", "debugNodes": "noeuds de débogage",
"clearLog": "Effacer les messages", "clearLog": "Tous les messages",
"clearFilteredLog": "Effacer les messages filtrés", "clearFilteredLog": "Les messages filtrés",
"filterLog": "Filtrer les messages", "filterLog": "Filtrer les messages",
"openWindow": "Ouvrir dans une nouvelle fenêtre", "openWindow": "Ouvrir dans une nouvelle fenêtre",
"copyPath": "Copier le chemin", "copyPath": "Copier le chemin",
"copyPayload": "Copier la valeur", "copyPayload": "Copier la valeur",
"pinPath": "Épingler le chemin", "pinPath": "Épingler le chemin",
"selectAll": "tout sélectionner", "selectAll": "Tout sélectionner",
"selectNone": "ne rien sélectionner", "selectNone": "Ne rien sélectionner",
"all": "tout", "all": "Tout",
"filtered": "filtré" "filtered": "Filtrés"
}, },
"messageMenu": { "messageMenu": {
"collapseAll": "Réduire tous les chemins", "collapseAll": "Réduire tous les chemins",
@ -177,11 +177,11 @@
"linkIn": "Lien entrant", "linkIn": "Lien entrant",
"linkOut": "Lien sortant", "linkOut": "Lien sortant",
"linkCall": "Appel de lien", "linkCall": "Appel de lien",
"linkOutReturn": "retour de lien", "linkOutReturn": "Retour de lien",
"outMode": "Mode", "outMode": "Mode",
"sendToAll": "Envoyer à tous les noeuds de liaison connectés", "sendToAll": "Envoyer à tous les noeuds de liaison connectés",
"returnToCaller": "Retour au noeud de liaison appelant", "returnToCaller": "Retour au noeud de liaison appelant",
"timeout": "temps mort", "timeout": "Temps mort",
"linkCallType": "Type de liaison", "linkCallType": "Type de liaison",
"staticLinkCall": "Lien fixe", "staticLinkCall": "Lien fixe",
"dynamicLinkCall": "Lien dynamique (msg.target)", "dynamicLinkCall": "Lien dynamique (msg.target)",
@ -225,7 +225,7 @@
"command": "Commande", "command": "Commande",
"append": "Joindre", "append": "Joindre",
"timeout": "Temps mort", "timeout": "Temps mort",
"timeoutplace": "facultatif", "timeoutplace": "Facultatif",
"return": "Sortie", "return": "Sortie",
"seconds": "secondes", "seconds": "secondes",
"stdout": "stdout", "stdout": "stdout",
@ -234,7 +234,7 @@
"winHide": "Masquer la console" "winHide": "Masquer la console"
}, },
"placeholder": { "placeholder": {
"extraparams": "paramètres d'entrée supplémentaires" "extraparams": "Paramètres d'entrée supplémentaires"
}, },
"opt": { "opt": {
"exec": "lorsque la commande est terminée - mode exec", "exec": "lorsque la commande est terminée - mode exec",
@ -319,7 +319,7 @@
"queuemsg": "Mettre en file d'attente les messages intermédiaires", "queuemsg": "Mettre en file d'attente les messages intermédiaires",
"dropmsg": "Supprimer les messages intermédiaires", "dropmsg": "Supprimer les messages intermédiaires",
"sendmsg": "Envoyer les messages intermédiaires sur la 2ème sortie", "sendmsg": "Envoyer les messages intermédiaires sur la 2ème sortie",
"allowrate": "autoriser msg.rate (en ms) à remplacer le débit", "allowrate": "Autoriser msg.rate (en ms) à remplacer le débit",
"label": { "label": {
"delay": "retard", "delay": "retard",
"variable": "variable", "variable": "variable",
@ -349,7 +349,7 @@
} }
}, },
"errors": { "errors": {
"too-many": "trop de messages en attente dans le noeud 'Delay'", "too-many": "Trop de messages en attente dans le noeud 'Delay'",
"invalid-timeout": "Valeur de délai invalide", "invalid-timeout": "Valeur de délai invalide",
"invalid-rate": "Valeur de taux invalide", "invalid-rate": "Valeur de taux invalide",
"invalid-rate-unit": "Valeur de débit invalide", "invalid-rate-unit": "Valeur de débit invalide",
@ -359,8 +359,8 @@
}, },
"trigger": { "trigger": {
"send": "Envoyer", "send": "Envoyer",
"then": "puis", "then": "Puis",
"then-send": "puis envoyer", "then-send": "Puis envoyer",
"output": { "output": {
"string": "la chaîne", "string": "la chaîne",
"number": "le nombre", "number": "le nombre",
@ -381,9 +381,9 @@
"m": "Minutes", "m": "Minutes",
"h": "Heures" "h": "Heures"
}, },
"extend": " prolonger le délai si un nouveau message arrive", "extend": " Prolonger le délai si un nouveau message arrive",
"override": "remplacer le délai avec msg.delay", "override": "Remplacer le délai avec msg.delay",
"second": " envoyer un deuxième message à une sortie séparée", "second": " Envoyer un deuxième message à une sortie séparée",
"label": { "label": {
"trigger": "déclencher", "trigger": "déclencher",
"trigger-block": "déclencher et bloquer", "trigger-block": "déclencher et bloquer",
@ -408,7 +408,7 @@
"mqtt": { "mqtt": {
"label": { "label": {
"broker": "Serveur", "broker": "Serveur",
"example": "par exemple. localhost", "example": "expl. localhost",
"output": "Sortie", "output": "Sortie",
"qos": "QoS", "qos": "QoS",
"retain": "Conserver", "retain": "Conserver",
@ -438,7 +438,7 @@
"sessionExpiry": "Expiration de la session (secondes)", "sessionExpiry": "Expiration de la session (secondes)",
"topicAlias": "Alias", "topicAlias": "Alias",
"payloadFormatIndicator": "Formater", "payloadFormatIndicator": "Formater",
"payloadFormatIndicatorFalse": "octets non spécifiés (par défaut)", "payloadFormatIndicatorFalse": "Octets non spécifiés (par défaut)",
"payloadFormatIndicatorTrue": "Charge utile encodée en UTF-8", "payloadFormatIndicatorTrue": "Charge utile encodée en UTF-8",
"protocolVersion": "Protocole", "protocolVersion": "Protocole",
"protocolVersion3": "MQTT V3.1 (hérité)", "protocolVersion3": "MQTT V3.1 (hérité)",
@ -493,8 +493,8 @@
"false": "faux", "false": "faux",
"tip": "Conseil : laisser le sujet, le qos ou le contenu vide si vous souhaitez les définir via les propriétés du msg.", "tip": "Conseil : laisser le sujet, le qos ou le contenu vide si vous souhaitez les définir via les propriétés du msg.",
"errors": { "errors": {
"not-defined": "sujet non défini", "not-defined": "Sujet non défini",
"missing-config": "configuration du courtier manquante", "missing-config": "Configuration du courtier manquante",
"invalid-topic": "Sujet invalide spécifié", "invalid-topic": "Sujet invalide spécifié",
"nonclean-missingclientid": "Aucun ID client défini, utilisation d'une session propre", "nonclean-missingclientid": "Aucun ID client défini, utilisation d'une session propre",
"invalid-json-string": "Chaîne JSON invalide", "invalid-json-string": "Chaîne JSON invalide",
@ -514,7 +514,7 @@
"upload": "Accepter les téléchargements de fichiers ?", "upload": "Accepter les téléchargements de fichiers ?",
"status": "Code d'état", "status": "Code d'état",
"headers": "En-têtes", "headers": "En-têtes",
"other": "autre", "other": "Autre",
"paytoqs": { "paytoqs": {
"ignore": "Ignorer", "ignore": "Ignorer",
"query": "Joindre aux paramètres de chaîne de requête", "query": "Joindre aux paramètres de chaîne de requête",
@ -625,7 +625,7 @@
"chars": "caractères", "chars": "caractères",
"close": "Fermer", "close": "Fermer",
"optional": "(facultatif)", "optional": "(facultatif)",
"reattach": "rattacher le délimiteur" "reattach": "Rattacher le délimiteur"
}, },
"type": { "type": {
"listen": "Écoute sur", "listen": "Écoute sur",
@ -633,8 +633,8 @@
"reply": "Répondre sur TCP" "reply": "Répondre sur TCP"
}, },
"output": { "output": {
"stream": "flux de", "stream": "Flux de",
"single": "unique", "single": "Unique",
"buffer": "Tampon", "buffer": "Tampon",
"string": "Chaîne", "string": "Chaîne",
"base64": "Chaîne en Base64" "base64": "Chaîne en Base64"
@ -657,15 +657,15 @@
"connections_plural": "__count__ connexions" "connections_plural": "__count__ connexions"
}, },
"errors": { "errors": {
"connection-lost": "connexion perdue avec __host__:__port__", "connection-lost": "Connexion perdue avec __host__:__port__",
"timeout": "délai d'expiration du port __port__ du socket fermé", "timeout": "Délai d'expiration du port __port__ du socket fermé",
"cannot-listen": "impossible d'écouter sur le port __port__, erreur : __error__", "cannot-listen": "Impossible d'écouter sur le port __port__, erreur : __error__",
"error": "erreur : __error__", "error": "Erreur : __error__",
"socket-error": "erreur de courtier depuis __host__:__port__", "socket-error": "Erreur de courtier depuis __host__:__port__",
"no-host": "Hôte et/ou port non défini", "no-host": "Hôte et/ou port non défini",
"connect-timeout": "délai de connexion", "connect-timeout": "Délai de connexion",
"connect-fail": "la connexion a échoué", "connect-fail": "La connexion a échoué",
"bad-string": "échec de la conversion en chaîne", "bad-string": "Échec de la conversion en chaîne",
"invalid-host": "Hôte invalide", "invalid-host": "Hôte invalide",
"invalid-port": "Port invalide" "invalid-port": "Port invalide"
} }
@ -722,7 +722,7 @@
}, },
"errors": { "errors": {
"access-error": "Erreur d'accès UDP, vous aurez peut-être besoin d'un accès root pour les ports inférieurs à 1024", "access-error": "Erreur d'accès UDP, vous aurez peut-être besoin d'un accès root pour les ports inférieurs à 1024",
"error": "erreur : __erreur__", "error": "Erreur : __erreur__",
"bad-mcaddress": "Mauvaise adresse de multidiffusion", "bad-mcaddress": "Mauvaise adresse de multidiffusion",
"interface": "Doit être l'adresse IP de l'interface requise", "interface": "Doit être l'adresse IP de l'interface requise",
"ip-notset": "udp : adresse IP non définie", "ip-notset": "udp : adresse IP non définie",
@ -730,7 +730,7 @@
"port-invalid": "udp : numéro de port non valide", "port-invalid": "udp : numéro de port non valide",
"alreadyused": "udp : port __port__ déjà utilisé", "alreadyused": "udp : port __port__ déjà utilisé",
"ifnotfound": "udp : interface __iface__ introuvable", "ifnotfound": "udp : interface __iface__ introuvable",
"invalid-group": "groupe de multidiffusion invalide" "invalid-group": "Groupe de multidiffusion invalide"
} }
}, },
"switch": { "switch": {
@ -738,15 +738,15 @@
"label": { "label": {
"property": "Propriété", "property": "Propriété",
"rule": "règle", "rule": "règle",
"repair": "recréer des séquences du messages", "repair": "Recréer des séquences du messages",
"value-rules": "règles de valeur", "value-rules": "Règles de valeur",
"sequence-rules": "règles de séquence" "sequence-rules": "Règles de séquence"
}, },
"previous": "valeur précédente", "previous": "valeur précédente",
"and": "et", "and": "et",
"checkall": "vérifier toutes les règles", "checkall": "Vérifier toutes les règles",
"stopfirst": "arrêter après la première concordance", "stopfirst": "Arrêter après la première concordance",
"ignorecase": "ignorer la casse", "ignorecase": "Ignorer la casse",
"rules": { "rules": {
"btwn": "est entre", "btwn": "est entre",
"cont": "contient", "cont": "contient",
@ -767,7 +767,7 @@
}, },
"errors": { "errors": {
"invalid-expr": "Expression JSONata non valide : __error__", "invalid-expr": "Expression JSONata non valide : __error__",
"too-many": "trop de messages en attente dans le noeud de commutation" "too-many": "Trop de messages en attente dans le noeud de commutation"
} }
}, },
"change": { "change": {
@ -840,13 +840,13 @@
"entrée": "Entrée", "entrée": "Entrée",
"skip-s": "Passer en premier", "skip-s": "Passer en premier",
"skip-e": "lignes", "skip-e": "lignes",
"firstrow": "la première ligne contient les noms des colonnes", "firstrow": "La première ligne contient les noms des colonnes",
"output": "Sortie", "output": "Sortie",
"includerow": "inclure la ligne du nom de la colonne", "includerow": "Inclure la ligne du nom de la colonne",
"newline": "Nouvelle ligne", "newline": "Nouvelle ligne",
"usestrings": "analyser les valeurs numériques", "usestrings": "Analyser les valeurs numériques",
"include_empty_strings": "inclure les chaînes vides", "include_empty_strings": "Inclure les chaînes vides",
"include_null_values": "inclure les valeurs nulles" "include_null_values": "Inclure les valeurs nulles"
}, },
"placeholder": { "placeholder": {
"columns": "noms de colonnes séparés par des virgules" "columns": "noms de colonnes séparés par des virgules"
@ -936,8 +936,8 @@
}, },
"file": { "file": {
"label": { "label": {
"write": "écrire le fichier", "write": "Écrire le fichier",
"read": "lire le fichier", "read": "Lire le fichier",
"filename": "Nom du fichier", "filename": "Nom du fichier",
"path": "chemin", "path": "chemin",
"action": "Action", "action": "Action",
@ -972,8 +972,8 @@
"appendedfile": "ajouté au fichier : __file__" "appendedfile": "ajouté au fichier : __file__"
}, },
"encoding": { "encoding": {
"none": "par défaut", "none": "Par défaut",
"setbymsg": "défini par msg.encoding", "setbymsg": "Défini par msg.encoding",
"native": "Natif", "native": "Natif",
"unicode": "Unicode", "unicode": "Unicode",
"japanese": "Japonais", "japanese": "Japonais",
@ -990,10 +990,10 @@
"errors": { "errors": {
"nofilename": "Aucun nom de fichier spécifié", "nofilename": "Aucun nom de fichier spécifié",
"invaliddelete": "Attention : suppression non valide. Veuiller utiliser une option de suppression spécifique dans la boîte de dialogue de configuration.", "invaliddelete": "Attention : suppression non valide. Veuiller utiliser une option de suppression spécifique dans la boîte de dialogue de configuration.",
"deletefail": "échec de la suppression du fichier : __error__", "deletefail": "Échec de la suppression du fichier : __error__",
"writefail": "échec de l'écriture dans le fichier : __error__", "writefail": "Échec de l'écriture dans le fichier : __error__",
"appendfail": "échec de l'ajout au fichier : __error__", "appendfail": "Échec de l'ajout au fichier : __error__",
"createfail": "échec de la création du fichier : __error__" "createfail": "Échec de la création du fichier : __error__"
}, },
"tip": "Astuce : Le nom du fichier doit être un chemin absolu, sinon il sera relatif au répertoire de travail du processus Node-RED." "tip": "Astuce : Le nom du fichier doit être un chemin absolu, sinon il sera relatif au répertoire de travail du processus Node-RED."
}, },
@ -1018,9 +1018,9 @@
"reduce": "réduire la séquence", "reduce": "réduire la séquence",
"custom": "manuel" "custom": "manuel"
}, },
"combine": "Combine each", "combine": "Combiner chaque",
"completeMessage": "message complet", "completeMessage": "message complet",
"create": "créer", "create": "Créer",
"type": { "type": {
"string": "une Chaîne", "string": "une Chaîne",
"array": "un Tableau", "array": "un Tableau",
@ -1028,13 +1028,13 @@
"object": "un Objet clé/valeur", "object": "un Objet clé/valeur",
"merged": "un Objet fusionné" "merged": "un Objet fusionné"
}, },
"using": "en utilisant la valeur de", "using": "En utilisant la valeur du",
"key": "comme la clé", "key": "comme clé",
"joinedUsing": "joint en utilisant", "joinedUsing": "joint en utilisant",
"send": "Envoyer le message :", "send": "Envoyer le message :",
"afterCount": "Après un certain nombre de parties du message", "afterCount": "Après un nombre de parties du message",
"count": "compter", "count": "nombre",
"subsequent": "et tous les messages suivants.", "subsequent": "Et tous les messages suivants.",
"afterTimeout": "Après un délai d'attente après le premier message", "afterTimeout": "Après un délai d'attente après le premier message",
"seconds": "secondes", "seconds": "secondes",
"complete": "Après un message avec la propriété <code>msg.complete</code> définie", "complete": "Après un message avec la propriété <code>msg.complete</code> définie",
@ -1068,10 +1068,10 @@
"order": "Sens", "order": "Sens",
"ascending": "croissant", "ascending": "croissant",
"descending": "descendant", "descending": "descendant",
"as-number": "comme nombre", "as-number": "Comme nombre",
"invalid-exp": "Expression JSONata invalide dans le noeud sort: __message__", "invalid-exp": "Expression JSONata invalide dans le noeud sort: __message__",
"too-many": "Trop de messages en attente dans le noeud sort", "too-many": "Trop de messages en attente dans le noeud sort",
"clear": "effacer le message en attente dans le noeud sort" "clear": "Effacer le message en attente dans le noeud sort"
}, },
"batch": { "batch": {
"batch": "Regrouper", "batch": "Regrouper",
@ -1084,21 +1084,21 @@
"count": { "count": {
"label": "Nombre de messages", "label": "Nombre de messages",
"overlap": "Chevauchement", "overlap": "Chevauchement",
"count": "compter", "count": "nombre",
"invalid": "Comptage et chevauchement invalides" "invalid": "Comptage et chevauchement invalides"
}, },
"interval": { "interval": {
"label": "Intervalle", "label": "Intervalle",
"seconds": "secondes", "seconds": "secondes",
"empty": "envoyer un message vide lorsqu'aucun message n'arrive" "empty": "Envoyer un message vide lorsqu'aucun message n'arrive"
}, },
"concat": { "concat": {
"topics-label": "Sujets", "topics-label": "Sujets",
"topic": "sujet" "topic": "sujet"
}, },
"too-many": "trop de messages en attente dans le noeud batch", "too-many": "Trop de messages en attente dans le noeud batch",
"unexpected": "mode inattendu", "unexpected": "Mode inattendu",
"no-parts": "aucune propriété de pièces dans le message", "no-parts": "Aucune propriété de pièces dans le message",
"error": { "error": {
"invalid-count": "Compte invalide", "invalid-count": "Compte invalide",
"invalid-overlap": "Recouvrement invalide", "invalid-overlap": "Recouvrement invalide",
@ -1132,7 +1132,7 @@
"out": "par rapport à la dernière valeur de sortie valide" "out": "par rapport à la dernière valeur de sortie valide"
}, },
"warn": { "warn": {
"nonumber": "aucun numéro trouvé dans la charge utile" "nonumber": "Aucun numéro trouvé dans la charge utile"
} }
}, },
"global-config": { "global-config": {

View File

@ -98,7 +98,7 @@ function requireModule(module) {
const parsedModule = parseModuleName(module); const parsedModule = parseModuleName(module);
if (BUILTIN_MODULES.indexOf(parsedModule.module) !== -1) { if (BUILTIN_MODULES.indexOf(parsedModule.module) !== -1) {
return require(parsedModule.module); return require(parsedModule.module + parsedModule.subpath);
} }
if (!knownExternalModules[parsedModule.module]) { if (!knownExternalModules[parsedModule.module]) {
const e = new Error("Module not allowed"); const e = new Error("Module not allowed");
@ -131,7 +131,7 @@ function importModule(module) {
const parsedModule = parseModuleName(module); const parsedModule = parseModuleName(module);
if (BUILTIN_MODULES.indexOf(parsedModule.module) !== -1) { if (BUILTIN_MODULES.indexOf(parsedModule.module) !== -1) {
return import(parsedModule.module); return import(parsedModule.module + parsedModule.subpath);
} }
if (!knownExternalModules[parsedModule.module]) { if (!knownExternalModules[parsedModule.module]) {
const e = new Error("Module not allowed"); const e = new Error("Module not allowed");
@ -152,12 +152,13 @@ function importModule(module) {
} }
function parseModuleName(module) { function parseModuleName(module) {
var match = /((?:@[^/]+\/)?[^/@]+)(?:@([\s\S]+))?/.exec(module); var match = /((?:@[^/]+\/)?[^/@]+)(\/[^/@]+)?(?:@([\s\S]+))?/.exec(module);
if (match) { if (match) {
return { return {
spec: module, spec: module,
module: match[1], module: match[1],
version: match[2], subpath: match[2] || '',
version: match[3],
builtin: BUILTIN_MODULES.indexOf(match[1]) !== -1, builtin: BUILTIN_MODULES.indexOf(match[1]) !== -1,
known: !!knownExternalModules[match[1]] known: !!knownExternalModules[match[1]]
} }
@ -283,6 +284,7 @@ async function installModule(moduleDetails) {
const runtimeInstalledModules = settings.get("modules") || {}; const runtimeInstalledModules = settings.get("modules") || {};
runtimeInstalledModules[moduleDetails.module] = moduleDetails; runtimeInstalledModules[moduleDetails.module] = moduleDetails;
settings.set("modules",runtimeInstalledModules) settings.set("modules",runtimeInstalledModules)
log.audit({event: "modules.install",module:moduleDetails.module, version:moduleDetails.version});
}).catch(result => { }).catch(result => {
var output = result.stderr || result.toString(); var output = result.stderr || result.toString();
var e; var e;

View File

@ -143,6 +143,12 @@ function loadModuleFiles(modules) {
return loadNodeSetList(pluginList); return loadNodeSetList(pluginList);
}).then(function() { }).then(function() {
return loadNodeSetList(nodeList); return loadNodeSetList(nodeList);
}).then(function () {
if (settings.available()) {
return registry.saveNodeList();
} else {
return
}
}) })
} }
@ -436,7 +442,7 @@ async function loadPlugin(plugin) {
return plugin; return plugin;
} }
} }
let invocation = 0
function loadNodeSetList(nodes) { function loadNodeSetList(nodes) {
var promises = []; var promises = [];
nodes.forEach(function(node) { nodes.forEach(function(node) {
@ -451,13 +457,7 @@ function loadNodeSetList(nodes) {
} }
}); });
return Promise.all(promises).then(function() { return Promise.all(promises)
if (settings.available()) {
return registry.saveNodeList();
} else {
return;
}
});
} }
function addModule(module) { function addModule(module) {

View File

@ -99,6 +99,9 @@ var api = module.exports = {
safeSettings.markdownEditor = runtime.settings.editorTheme.markdownEditor || {}; safeSettings.markdownEditor = runtime.settings.editorTheme.markdownEditor || {};
safeSettings.markdownEditor.mermaid = safeSettings.markdownEditor.mermaid || { enabled: true }; safeSettings.markdownEditor.mermaid = safeSettings.markdownEditor.mermaid || { enabled: true };
} }
if (runtime.settings.editorTheme.mermaid) {
safeSettings.mermaid = runtime.settings.editorTheme.mermaid
}
} }
safeSettings.libraries = runtime.library.getLibraries(); safeSettings.libraries = runtime.library.getLibraries();
if (util.isArray(runtime.settings.paletteCategories)) { if (util.isArray(runtime.settings.paletteCategories)) {

View File

@ -161,7 +161,8 @@ class Flow {
for (let i = 0; i < configNodes.length; i++) { for (let i = 0; i < configNodes.length; i++) {
const node = this.flow.configs[configNodes[i]] const node = this.flow.configs[configNodes[i]]
if (node.type === 'global-config' && node.env) { if (node.type === 'global-config' && node.env) {
const nodeEnv = await flowUtil.evaluateEnvProperties(this, node.env, credentials.get(node.id)) const globalCreds = credentials.get(node.id)?.map || {}
const nodeEnv = await flowUtil.evaluateEnvProperties(this, node.env, globalCreds)
this._env = { ...this._env, ...nodeEnv } this._env = { ...this._env, ...nodeEnv }
} }
} }

View File

@ -73,9 +73,20 @@ class Subflow extends Flow {
id: subflowInstance.id, id: subflowInstance.id,
configs: {}, configs: {},
nodes: {}, nodes: {},
groups: {},
subflows: {} subflows: {}
} }
if (subflowDef.groups) {
// Clone all of the subflow group definitions and give them new IDs
for (i in subflowDef.groups) {
if (subflowDef.groups.hasOwnProperty(i)) {
node = createNodeInSubflow(subflowInstance.id,subflowDef.groups[i]);
node_map[node._alias] = node;
subflowInternalFlowConfig.groups[node.id] = node;
}
}
}
if (subflowDef.configs) { if (subflowDef.configs) {
// Clone all of the subflow config node definitions and give them new IDs // Clone all of the subflow config node definitions and give them new IDs
for (i in subflowDef.configs) { for (i in subflowDef.configs) {
@ -237,7 +248,7 @@ class Subflow extends Flow {
for (j=0;j<wires.length;j++) { for (j=0;j<wires.length;j++) {
if (wires[j].id != self.subflowDef.id) { if (wires[j].id != self.subflowDef.id) {
node = self.node_map[wires[j].id]; node = self.node_map[wires[j].id];
if (node._originalWires) { if (node && node._originalWires) {
node.wires = clone(node._originalWires); node.wires = clone(node._originalWires);
} }
} }
@ -254,8 +265,10 @@ class Subflow extends Flow {
subflowInstanceModified = true; subflowInstanceModified = true;
} else { } else {
node = self.node_map[wires[j].id]; node = self.node_map[wires[j].id];
node.wires[wires[j].port] = node.wires[wires[j].port].concat(newWires[i]); if (node) {
modifiedNodes[node.id] = node; node.wires[wires[j].port] = node.wires[wires[j].port].concat(newWires[i]);
modifiedNodes[node.id] = node;
}
} }
} }
} }
@ -283,10 +296,14 @@ class Subflow extends Flow {
this.node._updateWires(subflowInstanceConfig.wires); this.node._updateWires(subflowInstanceConfig.wires);
} else { } else {
var node = self.node_map[wires[j].id]; var node = self.node_map[wires[j].id];
if (!node._originalWires) { if (node) {
node._originalWires = clone(node.wires); if (!node._originalWires) {
node._originalWires = clone(node.wires);
}
node.wires[wires[j].port] = (node.wires[wires[j].port]||[]).concat(this.subflowInstance.wires[i]);
} else {
this.error("Unknown node referenced inside subflow: " + wires[j].id)
} }
node.wires[wires[j].port] = (node.wires[wires[j].port]||[]).concat(this.subflowInstance.wires[i]);
} }
} }
} }
@ -302,11 +319,15 @@ class Subflow extends Flow {
this.node._updateWires(subflowInstanceConfig.wires); this.node._updateWires(subflowInstanceConfig.wires);
} else { } else {
var node = self.node_map[wires[j].id]; var node = self.node_map[wires[j].id];
if (!node._originalWires) { if (node) {
node._originalWires = clone(node.wires); if (!node._originalWires) {
node._originalWires = clone(node.wires);
}
node.wires[wires[j].port] = (node.wires[wires[j].port]||[]);
node.wires[wires[j].port].push(subflowStatusId);
} else {
this.error("Unknown node referenced inside subflow: " + wires[j].id)
} }
node.wires[wires[j].port] = (node.wires[wires[j].port]||[]);
node.wires[wires[j].port].push(subflowStatusId);
} }
} }
} }

View File

@ -57,18 +57,20 @@ var EnvVarPropertyRE = /^\${(\S+)}$/;
function mapEnvVarProperties(obj,prop,flow,config) { function mapEnvVarProperties(obj,prop,flow,config) {
var v = obj[prop]; const v = obj[prop];
if (Buffer.isBuffer(v)) { if (Buffer.isBuffer(v)) {
return; return;
} else if (Array.isArray(v)) { } else if (Array.isArray(v)) {
for (var i=0;i<v.length;i++) { for (let i=0;i<v.length;i++) {
mapEnvVarProperties(v,i,flow,config); mapEnvVarProperties(v,i,flow,config);
} }
} else if (typeof obj[prop] === 'string') { } else if (typeof obj[prop] === 'string') {
if (obj[prop][0] === "$" && (EnvVarPropertyRE_old.test(v) || EnvVarPropertyRE.test(v)) ) { if (obj[prop][0] === "$" && (EnvVarPropertyRE_old.test(v) || EnvVarPropertyRE.test(v)) ) {
var envVar = v.substring(2,v.length-1); const envVar = v.substring(2,v.length-1);
var r = redUtil.getSetting(config, envVar, flow); const r = redUtil.getSetting(config, envVar, flow);
obj[prop] = r ? r : obj[prop]; if (r !== undefined && r !== '') {
obj[prop] = r
}
} }
} else { } else {
for (var p in v) { for (var p in v) {
@ -80,6 +82,7 @@ function mapEnvVarProperties(obj,prop,flow,config) {
} }
async function evaluateEnvProperties(flow, env, credentials) { async function evaluateEnvProperties(flow, env, credentials) {
credentials = credentials || {}
const pendingEvaluations = [] const pendingEvaluations = []
const evaluatedEnv = {} const evaluatedEnv = {}
const envTypes = [] const envTypes = []
@ -112,6 +115,7 @@ async function evaluateEnvProperties(flow, env, credentials) {
if (pendingEvaluations.length > 0) { if (pendingEvaluations.length > 0) {
await Promise.all(pendingEvaluations) await Promise.all(pendingEvaluations)
} }
// Now loop over the env types and evaluate them properly
for (let i = 0; i < envTypes.length; i++) { for (let i = 0; i < envTypes.length; i++) {
let { name, value, type } = envTypes[i] let { name, value, type } = envTypes[i]
// If an env-var wants to lookup itself, delegate straight to the parent // If an env-var wants to lookup itself, delegate straight to the parent
@ -122,7 +126,17 @@ async function evaluateEnvProperties(flow, env, credentials) {
if (evaluatedEnv.hasOwnProperty(value)) { if (evaluatedEnv.hasOwnProperty(value)) {
value = evaluatedEnv[value] value = evaluatedEnv[value]
} else { } else {
value = redUtil.evaluateNodeProperty(value, type, {_flow: flow}, null, null); value = redUtil.evaluateNodeProperty(value, type, {_flow: {
// Provide a hook so when it tries to look up a flow setting,
// we can insert the just-evaluated value which hasn't yet
// been set on the flow object - otherwise delegate up to the flow
getSetting: function(name) {
if (evaluatedEnv.hasOwnProperty(name)){
return evaluatedEnv[name]
}
return flow.getSetting(name)
}
}}, null, null);
} }
evaluatedEnv[name] = value evaluatedEnv[name] = value
} }

View File

@ -42,6 +42,7 @@ function Node(n) {
this._closeCallbacks = []; this._closeCallbacks = [];
this._inputCallback = null; this._inputCallback = null;
this._inputCallbacks = null; this._inputCallbacks = null;
this._expectedDoneCount = 0;
if (n.name) { if (n.name) {
this.name = n.name; this.name = n.name;
@ -159,6 +160,9 @@ Node.prototype.on = function(event, callback) {
if (event == "close") { if (event == "close") {
this._closeCallbacks.push(callback); this._closeCallbacks.push(callback);
} else if (event === "input") { } else if (event === "input") {
if (callback.length === 3) {
this._expectedDoneCount++
}
if (this._inputCallback) { if (this._inputCallback) {
this._inputCallbacks = [this._inputCallback, callback]; this._inputCallbacks = [this._inputCallback, callback];
this._inputCallback = null; this._inputCallback = null;
@ -218,19 +222,17 @@ Node.prototype._emitInput = function(arg) {
} else if (node._inputCallbacks) { } else if (node._inputCallbacks) {
// Multiple callbacks registered. Call each one, tracking eventual completion // Multiple callbacks registered. Call each one, tracking eventual completion
var c = node._inputCallbacks.length; var c = node._inputCallbacks.length;
let doneCount = 0
for (var i=0;i<c;i++) { for (var i=0;i<c;i++) {
var cb = node._inputCallbacks[i]; var cb = node._inputCallbacks[i];
if (cb.length === 2) {
c++;
}
try { try {
cb.call( cb.call(
node, node,
arg, arg,
function() { node.send.apply(node,arguments) }, function() { node.send.apply(node,arguments) },
function(err) { function(err) {
c--; doneCount++;
if (c === 0) { if (doneCount === node._expectedDoneCount) {
node._complete(arg,err); node._complete(arg,err);
} }
} }
@ -257,6 +259,9 @@ Node.prototype._removeListener = Node.prototype.removeListener;
Node.prototype.removeListener = function(name, listener) { Node.prototype.removeListener = function(name, listener) {
var index; var index;
if (name === "input") { if (name === "input") {
if (listener.length === 3) {
this._expectedDoneCount--
}
if (this._inputCallback && this._inputCallback === listener) { if (this._inputCallback && this._inputCallback === listener) {
// Removing the only callback // Removing the only callback
this._inputCallback = null; this._inputCallback = null;

View File

@ -48,7 +48,7 @@
"port-in-use": "Erreur : port utilisé", "port-in-use": "Erreur : port utilisé",
"uncaught-exception": "Exception non reconnue :", "uncaught-exception": "Exception non reconnue :",
"admin-ui-disabled": "Interface d'administration désactivée", "admin-ui-disabled": "Interface d'administration désactivée",
"now-running": "Le serveur tourne maintenant sur __listenpath__", "now-running": "Le serveur est disponible à l'adresse __listenpath__",
"failed-to-start": "Échec lors du démarrage du serveur :", "failed-to-start": "Échec lors du démarrage du serveur :",
"headless-mode": "Fonctionne en mode sans interface graphique (headless)", "headless-mode": "Fonctionne en mode sans interface graphique (headless)",
"httpadminauth-deprecated": "L'utilisation de httpAdminAuth est DÉCONSEILLÉE. Utiliser adminAuth à la place", "httpadminauth-deprecated": "L'utilisation de httpAdminAuth est DÉCONSEILLÉE. Utiliser adminAuth à la place",
@ -100,7 +100,7 @@
"error": "Erreur lors du chargement des identifiants : __message__", "error": "Erreur lors du chargement des identifiants : __message__",
"error-saving": "Erreur lors de l'enregistrement des identifiants : __message__", "error-saving": "Erreur lors de l'enregistrement des identifiants : __message__",
"not-registered": "Le type d'identifiant '__type__' n'a pas été enregistré", "not-registered": "Le type d'identifiant '__type__' n'a pas été enregistré",
"system-key-warning": "\n\n---------------------------------------------------------------------\nVotre fichier contenant les identifiants de flux est chiffré à l'aide d'une clé générée par le système.\n\nSi la clé générée par le système est perdue pour une raison quelconque, votre fichier contenant\nles identifiants ne sera pas récupérable, vous devrez le supprimer et ressaisir vos identifiants.\n\nVous pouvez définir votre propre clé en utilisant l'option 'credentialSecret' dans\nvotre fichier de paramètres. Node-RED rechiffrera alors votre fichier contenant les identifiants\nà l'aide de la clé que vous avez choisie la prochaine fois que vous déploierez une modification.\n---------------------------------------------------------------------\n", "system-key-warning": "\n\n--------------------------------------------------------------------------------------------------------\nVotre fichier contenant les identifiants de flux est chiffré à l'aide d'une clé générée par le système.\n\nSi la clé générée par le système est perdue pour une raison quelconque, votre fichier contenant\nles identifiants ne sera pas récupérable, vous devrez le supprimer et ressaisir vos identifiants.\n\nVous pouvez définir votre propre clé en utilisant l'option 'credentialSecret' dans\nvotre fichier de paramètres. Node-RED rechiffrera alors votre fichier contenant les identifiants\nà l'aide de la clé que vous avez choisie la prochaine fois que vous déploierez une modification.\n--------------------------------------------------------------------------------------------------------\n",
"unencrypted": "Utilisation d'identifiants non chiffrés", "unencrypted": "Utilisation d'identifiants non chiffrés",
"encryptedNotFound": "Identifiants chiffrés introuvables" "encryptedNotFound": "Identifiants chiffrés introuvables"
}, },

View File

@ -39,7 +39,7 @@
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"express": "4.18.2", "express": "4.18.2",
"fs-extra": "11.1.1", "fs-extra": "11.1.1",
"node-red-admin": "^3.1.0", "node-red-admin": "^3.1.1",
"nopt": "5.0.0", "nopt": "5.0.0",
"semver": "7.5.4" "semver": "7.5.4"
}, },

View File

@ -2538,38 +2538,4 @@ describe('HTTP Request Node', function() {
}); });
} }
}); });
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' ]
}
});
})
});
})
}); });

View File

@ -26,6 +26,7 @@ var flowUtils = NR_TEST_UTILS.require("@node-red/runtime/lib/flows/util");
var Flow = NR_TEST_UTILS.require("@node-red/runtime/lib/flows/Flow"); var Flow = NR_TEST_UTILS.require("@node-red/runtime/lib/flows/Flow");
var flows = NR_TEST_UTILS.require("@node-red/runtime/lib/flows"); var flows = NR_TEST_UTILS.require("@node-red/runtime/lib/flows");
var Node = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/Node"); var Node = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/Node");
var credentials = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/credentials");
var hooks = NR_TEST_UTILS.require("@node-red/util/lib/hooks"); var hooks = NR_TEST_UTILS.require("@node-red/util/lib/hooks");
var typeRegistry = NR_TEST_UTILS.require("@node-red/registry"); var typeRegistry = NR_TEST_UTILS.require("@node-red/registry");
@ -61,6 +62,7 @@ describe('Flow', function() {
this.scope = n.scope; this.scope = n.scope;
var node = this; var node = this;
this.foo = n.foo; this.foo = n.foo;
this.bar = n.bar;
this.handled = 0; this.handled = 0;
this.stopped = false; this.stopped = false;
currentNodes[node.id] = node; currentNodes[node.id] = node;
@ -1235,11 +1237,12 @@ describe('Flow', function() {
}) })
describe("#env", function () { describe("#env", function () {
afterEach(() => {
delete process.env.V0;
delete process.env.V1;
credentials.get.restore?.()
})
it("can instantiate a node with environment variable property values of group and tab", async function () { it("can instantiate a node with environment variable property values of group and tab", async function () {
after(function() {
delete process.env.V0;
delete process.env.V1;
})
process.env.V0 = "gv0"; process.env.V0 = "gv0";
process.env.V1 = "gv1"; process.env.V1 = "gv1";
process.env.V3 = "gv3"; process.env.V3 = "gv3";
@ -1283,10 +1286,6 @@ describe('Flow', function() {
}); });
it("can access environment variable property using $parent", async function () { it("can access environment variable property using $parent", async function () {
after(function() {
delete process.env.V0;
delete process.env.V1;
})
process.env.V0 = "gv0"; process.env.V0 = "gv0";
process.env.V1 = "gv1"; process.env.V1 = "gv1";
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
@ -1321,9 +1320,6 @@ describe('Flow', function() {
}); });
it("can define environment variable using JSONata", async function () { it("can define environment variable using JSONata", async function () {
after(function() {
delete process.env.V0;
})
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
{id:"t1",type:"tab",env:[ {id:"t1",type:"tab",env:[
{"name": "V0", value: "1+2", type: "jsonata"} {"name": "V0", value: "1+2", type: "jsonata"}
@ -1346,9 +1342,6 @@ describe('Flow', function() {
}); });
it("can access global environment variables defined as JSONata values", async function () { it("can access global environment variables defined as JSONata values", async function () {
after(function() {
delete process.env.V0;
})
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
{id:"t1",type:"tab",env:[ {id:"t1",type:"tab",env:[
{"name": "V0", value: "1+2", type: "jsonata"} {"name": "V0", value: "1+2", type: "jsonata"}
@ -1370,15 +1363,21 @@ describe('Flow', function() {
await flow.stop() await flow.stop()
}); });
it("global flow can access global-config defined environment variables", async function () { it("global flow can access global-config defined environment variables", async function () {
after(function() { sinon.stub(credentials,"get").callsFake(function(id) {
delete process.env.V0; if (id === 'gc') {
return { map: { GC_CRED: 'gc_cred' }}
}
return null
}) })
const config = flowUtils.parseConfig([ const config = flowUtils.parseConfig([
{id:"gc", type:"global-config", env:[ {id:"gc", type:"global-config", env:[
{"name": "GC0", value: "3+4", type: "jsonata"} {"name": "GC0", value: "3+4", type: "jsonata"},
{"name": "GC_CRED", type: "cred"},
]}, ]},
{id:"t1",type:"tab" }, {id:"t1",type:"tab" },
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"${GC0}",wires:[]}, {id:"1",x:10,y:10,z:"t1",type:"test",foo:"${GC0}", bar:"${GC_CRED}", wires:[]},
]); ]);
// Two-arg call - makes this the global flow that handles global-config nodes // Two-arg call - makes this the global flow that handles global-config nodes
const globalFlow = Flow.create({getSetting:v=>process.env[v]},config); const globalFlow = Flow.create({getSetting:v=>process.env[v]},config);
@ -1390,6 +1389,7 @@ describe('Flow', function() {
var activeNodes = flow.getActiveNodes(); var activeNodes = flow.getActiveNodes();
activeNodes["1"].foo.should.equal(7); activeNodes["1"].foo.should.equal(7);
activeNodes["1"].bar.should.equal('gc_cred');
await flow.stop() await flow.stop()
await globalFlow.stop() await globalFlow.stop()

View File

@ -183,6 +183,35 @@ describe('Node', function() {
n.receive(message); n.receive(message);
}); });
it('calls parent flow handleComplete when multiple callbacks provided', function(done) {
var n = new RedNode({id:'123',type:'abc', _flow: {
handleComplete: function(node,msg) {
try {
doneCount.should.equal(2)
msg.should.deepEqual(message);
done();
} catch(err) {
done(err);
}
}
}});
var message = {payload:"hello world"};
let doneCount = 0
n.on('input',function(msg, nodeSend, nodeDone) {
doneCount++
nodeDone();
});
// Include a callback without explicit done signature
n.on('input',function(msg) { });
n.on('input',function(msg, nodeSend, nodeDone) {
doneCount++
nodeDone();
});
n.receive(message);
});
it('triggers onComplete hook when done callback provided', function(done) { it('triggers onComplete hook when done callback provided', function(done) {
var handleCompleteCalled = false; var handleCompleteCalled = false;
var hookCalled = false; var hookCalled = false;