Merge branch 'dev' into fix_html_tags

This commit is contained in:
Nick O'Leary 2024-09-11 15:27:45 +01:00 committed by GitHub
commit db51307e7f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 557 additions and 204 deletions

View File

@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20, 22]
node-version: [18, 20, 22.4.x]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}

View File

@ -1,3 +1,38 @@
#### 4.0.2: Maintenance Release
Editor
- Use a more subtle border on the header (#4818) @bonanitech
- Improve the editor's French translations (#4824) @GogoVega
- Clean up orphaned editors (#4821) @Steve-Mcl
- Fix node validation if the property is not required (#4812) @GogoVega
- Ensure mermaid.min.js is cached properly between loads of the editor (#4817) @knolleary
Runtime
- Allow auth cookie name to be customised (#4815) @knolleary
- Guard against undefined sessions in multiplayer (#4816) @knolleary
#### 4.0.1: Maintenance Release
Editor
- Ensure subflow instance credential property values are extracted (#4802) @knolleary
- Use `_ADD_` value for both `add new...` and `none` options (#4800) @GogoVega
- Fix the config node select value assignment (#4788) @GogoVega
- Add tooltip for number of subflow instance on info tab (#4786) @kazuhitoyokoi
- Add Japanese translations for v4.0.0 (#4785) @kazuhitoyokoi
Runtime
- Ensure group nodes are properly exported in /flow api (#4803) @knolleary
Nodes
- Joins: make using msg.parts optional in join node (#4796) @dceejay
- HTTP Request: UI proxy should setup agents for both http_proxy and https_proxy (#4794) @Steve-Mcl
- HTTP Request: Remove default user agent (#4791) @Steve-Mcl
#### 4.0.0: Milestone Release
This marks the next major release of Node-RED. The following changes represent

View File

@ -1,6 +1,6 @@
{
"name": "node-red",
"version": "4.0.0",
"version": "4.1.0-beta.0",
"description": "Low-code programming for event-driven applications",
"homepage": "https://nodered.org",
"license": "Apache-2.0",

View File

@ -182,6 +182,10 @@ function genericStrategy(adminApp,strategy) {
maxAge: null,
...settings.httpAdminCookieOptions
}
if (sessionOptions.cookie.name){
sessionOptions.name = sessionOptions.cookie.name
delete sessionOptions.cookie.name
}
}
adminApp.use(session(sessionOptions));
//TODO: all passport references ought to be in ./auth
@ -217,10 +221,10 @@ function genericStrategy(adminApp,strategy) {
adminApp.get('/auth/strategy',
passport.authenticate(strategy.name, {
session:false,
failureMessage: true,
failureRedirect: settings.httpAdminRoot + '?session_message=Login Failed'
failWithError: true,
failureMessage: true
}),
completeGenerateStrategyAuth,
completeGenericStrategyAuth,
handleStrategyError
);
@ -232,14 +236,14 @@ function genericStrategy(adminApp,strategy) {
passport.authenticate(strategy.name, {
session:false,
failureMessage: true,
failureRedirect: settings.httpAdminRoot + '?session_message=Login Failed'
failWithError: true
}),
completeGenerateStrategyAuth,
completeGenericStrategyAuth,
handleStrategyError
);
}
function completeGenerateStrategyAuth(req,res) {
function completeGenericStrategyAuth(req,res) {
var tokens = req.user.tokens;
delete req.user.tokens;
// Successful authentication, redirect home.
@ -249,6 +253,8 @@ function handleStrategyError(err, req, res, next) {
if (res.headersSent) {
return next(err)
}
// Remove the header that passport auto-adds as we don't need it
res.removeHeader('WWW-Authenticate')
log.audit({event: "auth.login.fail.oauth",error:err.toString()});
res.redirect(settings.httpAdminRoot + '?session_message='+err.toString());
}

View File

@ -1,6 +1,6 @@
{
"name": "@node-red/editor-api",
"version": "4.0.0",
"version": "4.1.0-beta.0",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@ -16,8 +16,8 @@
}
],
"dependencies": {
"@node-red/util": "4.0.0",
"@node-red/editor-client": "4.0.0",
"@node-red/util": "4.1.0-beta.0",
"@node-red/editor-client": "4.1.0-beta.0",
"bcryptjs": "2.4.3",
"body-parser": "1.20.2",
"clone": "2.1.2",

View File

@ -27,7 +27,8 @@
"lock": "Verrouiller",
"unlock": "Déverrouiller",
"locked": "Verrouillé",
"unlocked": "Déverrouillé"
"unlocked": "Déverrouillé",
"format": "Format"
},
"type": {
"string": "chaîne de caractères",
@ -54,10 +55,10 @@
"workspace": {
"defaultName": "Flux __number__",
"editFlow": "Modifier le flux : __name__",
"confirmDelete": "Confirmation de la suppression",
"delete": "Etes-vous sûr de vouloir supprimer '__label__'?",
"dropFlowHere": "Déposer le flux ici",
"dropImageHere": "Déposer l'image ici",
"confirmDelete": "Confirmer la suppression",
"delete": "Êtes-vous sûr de vouloir supprimer '__label__' ?",
"dropFlowHere": "Lâchez le flux ici",
"dropImageHere": "Lâchez l'image ici",
"addFlow": "Ajouter un flux",
"addFlowToRight": "Ajouter un flux à droite",
"closeFlow": "Fermer le flux",
@ -74,7 +75,7 @@
"enabled": "Activé",
"disabled": "Désactivé",
"info": "Description",
"selectNodes": "Cliquer sur les noeuds pour sélectionner",
"selectNodes": "Cliquer pour sélectionner",
"enableFlow": "Activer le flux",
"disableFlow": "Désactiver le flux",
"lockFlow": "Verrouiller le flux",
@ -98,7 +99,7 @@
"rtl": "De droite à gauche",
"auto": "Contextuel",
"language": "Langue",
"browserDefault": "Navigateur par défaut"
"browserDefault": "Par défaut du Navigateur"
},
"sidebar": {
"show": "Afficher la barre latérale"
@ -134,7 +135,7 @@
"disableSelectedNodes": "Désactiver les noeuds sélectionnés",
"showSelectedNodeLabels": "Afficher les étiquettes des noeuds sélectionnés",
"hideSelectedNodeLabels": "Masquer les étiquettes des noeuds sélectionnés",
"showWelcomeTours": "Afficher les visites guidées pour les nouvelles versions",
"showWelcomeTours": "Afficher les visites guidées des nouvelles versions",
"help": "Site web de Node-RED",
"projects": "Projets",
"projects-new": "Nouveau projet",
@ -143,7 +144,7 @@
"showNodeLabelDefault": "Afficher l'étiquette des noeuds nouvellement ajoutés",
"codeEditor": "Éditeur de code",
"groups": "Groupes",
"groupSelection": "Grouper cette sélection",
"groupSelection": "Grouper la sélection",
"ungroupSelection": "Dégrouper la sélection",
"groupMergeSelection": "Fusionner la sélection",
"groupRemoveSelection": "Supprimer du groupe",
@ -155,7 +156,7 @@
"alignMiddle": "Aligner au milieu",
"alignBottom": "Aligner en bas",
"distributeHorizontally": "Répartir horizontalement",
"distributeVertically": "Distribuer verticalement",
"distributeVertically": "Répartir verticalement",
"moveToBack": "Déplacer vers l'arrière",
"moveToFront": "Déplacer vers l'avant",
"moveBackwards": "Reculer",
@ -163,21 +164,21 @@
}
},
"actions": {
"toggle-navigator": "Basculer de navigateur",
"zoom-out": "Dézoomer",
"zoom-reset": "Réinitialiser le zoom",
"toggle-navigator": "Basculer l'affichage du navigateur",
"zoom-out": "Réduire",
"zoom-reset": "Réinitialiser",
"zoom-in": "Agrandir",
"search-flows": "Rechercher le flux",
"search-prev": "Précédent",
"search-next": "Suivant",
"search-counter": "\"__term__\" __result__ de __count__"
"search-counter": "\"__term__\" __result__ sur __count__"
},
"user": {
"loggedInAs": "Connecté en tant que __name__",
"username": "Nom d'utilisateur",
"password": "Mot de passe",
"login": "Connexion",
"loginFailed": "Échec de la connexion",
"login": "Se connecter",
"loginFailed": "Échec de connexion",
"notAuthorized": "Pas autorisé",
"errors": {
"settings": "Vous devez être connecté pour accéder aux paramètres",
@ -193,16 +194,16 @@
"warning": "<strong>Attention</strong> : __message__",
"warnings": {
"undeployedChanges": "Le noeud a des modifications non déployées",
"nodeActionDisabled": "Actions de noeud désactivées",
"nodeActionDisabledSubflow": "Actions de noeud désactivées dans le sous-flux",
"nodeActionDisabled": "Les actions du noeud sont désactivées",
"nodeActionDisabledSubflow": "Les actions de noeud sont désactivées à l'intérieur du sous-flux",
"missing-types": "<p>Flux arrêtés en raison de types de noeuds manquants.</p>",
"missing-modules": "<p>Flux arrêtés en raison de modules manquants.</p>",
"safe-mode": "<p>Flux arrêtés en mode sans échec.</p><p>Vous pouvez modifier vos flux et déployer les changements pour redémarrer.</p>",
"safe-mode": "<p>Flux arrêtés en mode sans échec.</p><p>Vous pouvez modifier vos flux et déployer ensuite les changements afin de démarrer vos flux.</p>",
"restartRequired": "Node-RED doit être redémarré pour mettre à jour les modules",
"credentials_load_failed": "<p>Les flux se sont arrêtés car les informations d'identification n'ont pas pu être déchiffrées.</p><p>Le fichier d'informations d'identification du flux est chiffré, mais la clé de chiffrement du projet est manquante ou invalide.</p>",
"credentials_load_failed_reset": "<p>Les informations d'identification n'ont pas pu être déchiffrées</p><p>Le fichier d'informations d'identification du flux est chiffré, mais la clé de chiffrement du projet est manquante ou invalide.</p><p>Le fichier d'informations d'identification du flux sera réinitialisé lors du prochain déploiement. Toutes les informations d'identification de flux existantes seront perdues.</p>",
"credentials_load_failed": "<p>Les flux se sont arrêtés car les informations d'identification n'ont pas pu être déchiffrées.</p><p>Le fichier d'informations d'identification du flux est chiffré mais la clé de chiffrement du projet est manquante ou invalide.</p>",
"credentials_load_failed_reset": "<p>Les informations d'identification n'ont pas pu être déchiffrées</p><p>Le fichier d'informations d'identification du flux est chiffré mais la clé de chiffrement du projet est manquante ou invalide.</p><p>Le fichier d'informations d'identification du flux sera réinitialisé lors du prochain déploiement. Toutes les informations d'identification des flux existants seront perdues.</p>",
"missing_flow_file": "<p>Fichier contenant les flux introuvable.</p><p>Le projet n'est pas configuré avec un fichier de flux.</p>",
"missing_package_file": "<p>Fichier de paquetage du projet introuvable.</p><p>Il manque au projet un fichier package.json.</p>",
"missing_package_file": "<p>Fichier de paquetage du projet introuvable.</p><p>Il manque au projet le fichier <code>package.json</code>.</p>",
"project_empty": "<p>Le projet est vide.</p><p>Voulez-vous créer un ensemble de fichiers de projet par défaut ?<br/>Sinon, vous devrez ajouter manuellement des fichiers au projet (en dehors de l'éditeur).</p>",
"project_not_found": "<p>Le projet '__project__' est introuvable.</p>",
"git_merge_conflict": "<p>La fusion automatique des modifications a échoué.</p><p>Corriger les conflits non fusionnés, puis valider le résultat.</p>"
@ -219,7 +220,7 @@
},
"project": {
"change-branch": "Changer pour une branche locale '__project__'",
"merge-abort": "Git fusion abandonnée",
"merge-abort": "Fusion Git abandonnée",
"loaded": "Projet '__project__' chargé",
"updated": "Projet '__project__' mis à jour",
"pull": "Projet '__project__' rechargé",
@ -352,7 +353,7 @@
"backgroundUpdate": "Les flux sur le serveur ont été mis à jour.",
"conflictChecking": "Vérifier si les modifications peuvent être fusionnées automatiquement",
"conflictAutoMerge": "Les modifications n'incluent aucun conflit et peuvent être fusionnées automatiquement.",
"conflictManualMerge": "Les changements incluent des conflits qui doivent être résolus avant de pouvoir être déployés.",
"conflictManualMerge": "Les modifications incluent des conflits qui doivent être résolus avant de pouvoir être déployées.",
"plusNMore": "+ __count__ en plus"
}
},
@ -372,16 +373,17 @@
"deleted": "supprimé",
"flowDeleted": "flux supprimé",
"flowAdded": "flux ajouté",
"moved": "déplacé",
"movedTo": "déplacé vers __id__",
"movedFrom": "déplacé depuis __id__"
},
"nodeCount": "__count__ noeud",
"nodeCount_plural": "__count__ noeuds",
"local": "Changements locaux",
"remote": "Modifications à distance",
"remote": "Changements distants",
"reviewChanges": "Examiner les modifications",
"noBinaryFileShowed": "Impossible d'afficher le contenu du fichier binaire",
"viewCommitDiff": "Afficher les modifications de validation",
"viewCommitDiff": "Afficher les modifications de la validation",
"compareChanges": "Comparer les modifications",
"saveConflict": "Enregistrer la résolution des conflits",
"conflictHeader": "<span>__resolved__</span> sur <span>__unresolved__</span> conflit(s) résolu(s)",
@ -395,9 +397,9 @@
"edit": "Modifier le modèle du 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",
"editSubflowProperties": "modifier les propriétés",
"input": "Entrées:",
"output": "Sorties:",
"editSubflowProperties": "Modifier les propriétés",
"input": "Entrées :",
"output": "Sorties :",
"status": "Statut du noeud",
"deleteSubflow": "Supprimer le sous-flux",
"confirmDelete": "Voulez-vous vraiment supprimer ce sous-flux ?",
@ -411,7 +413,7 @@
"version": "Version",
"versionPlaceholder": "x.y.z",
"keys": "Mots clés",
"keysPlaceholder": "Mots clés séparés par des virgules",
"keysPlaceholder": "Mots clés séparés par une virgule",
"author": "Auteur",
"authorPlaceholder": "Votre nom <email@exemple.com>",
"desc": "Description",
@ -468,7 +470,7 @@
"select": "sélection",
"checkbox": "case à cocher",
"spinner": "valeurs à défiler",
"none": "aucune",
"none": "aucun",
"hidden": "masquer la propriété"
},
"types": {
@ -496,7 +498,7 @@
"max": "Maximum"
},
"errors": {
"scopeChange": "La modification de la portée la rendra indisponible pour les noeuds d'autres flux qui l'utilisent",
"scopeChange": "La modification de la portée rendra indisponible ce noeud de configuration aux noeuds d'autres flux qui l'utilisent",
"invalidProperties": "Propriétés invalides :",
"credentialLoadFailed": "Échec du chargement des identifiants du noeud"
}
@ -510,7 +512,7 @@
"unassigned": "Non attribué",
"global": "Global",
"workspace": "Espace de travail",
"editor": "Boîte de dialogue d'édition",
"editor": "Boîte d'édition",
"selectAll": "Tout sélectionner",
"selectNone": "Ne rien sélectionner",
"selectAllConnected": "Sélectionner tous les éléments connectés",
@ -541,7 +543,7 @@
"openLibrary": "Ouvrir la bibliothèque...",
"saveToLibrary": "Enregistrer dans la bibliothèque...",
"typeLibrary": "__type__ bibliothèque",
"unnamedType": "Innomé __type__",
"unnamedType": "Sans nom __type__",
"exportedToLibrary": "Noeuds exportés vers la bibliothèque",
"dialogSaveOverwrite": "Une __libraryType__ appelée __libraryName__ existe déjà. Écraser ?",
"invalidFilename": "Nom de fichier non valide",
@ -558,7 +560,7 @@
"noInfo": "Pas d'information disponible",
"filter": "Rechercher le noeud",
"search": "Rechercher les modules",
"addCategory": "Ajouter un nouveau...",
"addCategory": "Ajouter une nouvelle...",
"label": {
"subflows": "Sous-flux",
"network": "Réseau",
@ -638,7 +640,7 @@
"sortAZ": "A-Z",
"sortRecent": "Récent",
"more": "+ __count__ en plus",
"upload": "Charger le fichier tgz du module",
"upload": "Charger le fichier .tgz du module",
"refresh": "Actualiser la liste des modules",
"errors": {
"catalogLoadFailed": "<p>Échec du chargement du catalogue de noeuds.</p><p>Vérifier la console du navigateur pour plus d'informations</p>",
@ -651,7 +653,7 @@
},
"confirm": {
"install": {
"body": "<p>Installation de '__module__'</p><p>Avant l'installation, veuiller lire la documentation du noeud. Certains noeuds ont des dépendances qui ne peuvent pas être résolues automatiquement et peuvent nécessiter un redémarrage de Node-RED.</p>",
"body": "<p>Installation de '__module__'</p><p>Avant l'installation, veuillez lire la documentation du noeud. Certains noeuds ont des dépendances qui ne peuvent pas être résolues automatiquement et peuvent nécessiter un redémarrage de Node-RED.</p>",
"title": "Installer les noeuds"
},
"remove": {
@ -666,7 +668,7 @@
"title": "Mettre à jour les noeuds"
},
"cannotUpdate": {
"body": "Une mise à jour pour ce noeud est disponible, mais il n'est pas installé dans un emplacement que le gestionnaire de palette peut mettre à jour.<br/><br/>Veuiller vous référer à la documentation pour savoir comment mettre à jour ce noeud."
"body": "Une mise à jour pour ce noeud est disponible, mais il n'est pas installé dans un emplacement que le gestionnaire de palette peut mettre à jour.<br/><br/>Veuillez vous référer à la documentation pour savoir comment mettre à jour ce noeud."
},
"button": {
"review": "Ouvrir la documentation",
@ -708,8 +710,8 @@
"nodeHelp": "Aide sur les noeuds",
"none": "Aucun",
"arrayItems": "__count__ éléments",
"showTips": "Vous pouvez ouvrir les astuces à partir du panneau des paramètres",
"outline": "Plan",
"showTips": "Vous pouvez afficher les astuces à partir du panneau des paramètres",
"outline": "Contour",
"empty": "Vide",
"globalConfig": "Noeuds de configuration globale",
"triggerAction": "Déclencher une action",
@ -722,7 +724,7 @@
"help": {
"name": "Aide",
"label": "Aide",
"search": "Aide à la recherche",
"search": "Rechercher l'aide",
"nodeHelp": "Aide sur les noeuds",
"showHelp": "Afficher l'aide",
"showInOutline": "Afficher dans les grandes lignes",
@ -801,7 +803,7 @@
"branches": "Branches",
"noBranches": "Pas de branche",
"deleteConfirm": "Êtes-vous sûr de vouloir supprimer la branche locale '__name__' ? Ça ne peut pas être annulé.",
"unmergedConfirm": "La branche locale '__name__' contient des modifications non fusionnées qui seront perdues. Etes-vous sûr de vouloir la supprimer?",
"unmergedConfirm": "La branche locale '__name__' contient des modifications non fusionnées qui seront perdues. Êtes-vous sûr de vouloir la supprimer?",
"deleteUnmergedBranch": "Supprimer la branche non fusionnée",
"gitRemotes": "Git distant",
"addRemote": "Ajout distant",
@ -845,17 +847,17 @@
"deleteConfirm": "Êtes-vous sûr de vouloir supprimer la clé SSH __name__ ? Ça ne peut pas être annulé."
},
"versionControl": {
"unstagedChanges": "Abandon des changements",
"stagedChanges": "Changement mis en place",
"unstageChange": "Ne pas mettre en place le changement",
"stageChange": "Mettre en place le changement",
"unstageAllChange": "Ne pas mettre en place tous les changements",
"stageAllChange": "Mettre en place tous les changements",
"unstagedChanges": "Changements non indexés",
"stagedChanges": "Changements indexés",
"unstageChange": "Annuler l'indexation des changements",
"stageChange": "Indexer les changements",
"unstageAllChange": "Annuler l'indexation de tous les changements",
"stageAllChange": "Indexer tous les changements",
"commitChanges": "Valider les changements",
"resolveConflicts": "Résoudre les conflits",
"head": "En-tête",
"staged": "Mis en place",
"unstaged": "Non mis en place",
"staged": "Indexé",
"unstaged": "Non indexé",
"local": "Local",
"remote": "Distant",
"revert": "Voulez-vous vraiment annuler les modifications apportées à '__file__' ? Ça ne peut pas être annulé.",
@ -889,11 +891,11 @@
"pushFailed": "L'envoi a échoué car la branche a des validations plus récentes. Tirer et fusionner d'abord, puis envoyer à nouveau.",
"push": "Envoyer",
"pull": "Tirer",
"unablePull": "<p>Impossible d'extraire les modifications à distance ; vos modifications locales non mises en place seraient écrasées.</p><p>Valider vos modifications et réessayer.</p>",
"showUnstagedChanges": "Afficher les modifications non mise en place",
"unablePull": "<p>Impossible d'extraire les modifications à distance; vos modifications locales non mises en place seraient écrasées.</p><p>Valider vos modifications et réessayer.</p>",
"showUnstagedChanges": "Afficher les modifications non indexées",
"connectionFailed": "Impossible de se connecter au référentiel distant: ",
"pullUnrelatedHistory": "<p>Le réferentiel distant a un historique de validations sans rapport.</p><p>Êtes-vous sûr de vouloir extraire les modifications dans votre référentiel local ?</p>",
"pullChanges": "Tirer les changements",
"pullChanges": "Tirer les changements distants",
"history": "Historique",
"projectHistory": "Historique du projet",
"daysAgo": "il y a __count__ jour",
@ -974,7 +976,7 @@
"result": "Résultat",
"format": "Format",
"compatMode": "Mode de compatibilité activé",
"compatModeDesc": "<h3>Mode de compatibilité JSONata</h3><p> L'expression actuelle semble toujours faire référence à <code>msg</code> et sera donc évaluée en mode de compatibilité. Veuiller mettre à jour l'expression pour ne pas utiliser <code>msg</code> car ce mode sera supprimé à l'avenir.</p><p> Lorsque la prise en charge de JSONata a été ajoutée pour la première fois à Node-RED, il fallait que l'expression référencie l'objet <code>msg</code>. Par exemple, <code>msg.payload</code> serait utilisé pour accéder à la charge utile.</p><p> Cela n'est plus nécessaire car l'expression sera évaluée directement par rapport au message. Pour accéder à la charge utile, l'expression doit être simplement <code>charge utile</code>.</p>",
"compatModeDesc": "<h3>Mode de compatibilité JSONata</h3><p> L'expression actuelle semble toujours faire référence à <code>msg</code> et sera donc évaluée en mode de compatibilité. Veuillez mettre à jour l'expression pour ne pas utiliser <code>msg</code> car ce mode sera supprimé à l'avenir.</p><p> Lorsque la prise en charge de JSONata a été ajoutée pour la première fois à Node-RED, il fallait que l'expression référencie l'objet <code>msg</code>. Par exemple, <code>msg.payload</code> serait utilisé pour accéder à la charge utile.</p><p> Cela n'est plus nécessaire car l'expression sera évaluée directement par rapport au message. Pour accéder à la charge utile, l'expression doit être simplement <code>charge utile</code>.</p>",
"noMatch": "Aucun résultat correspondant",
"errors": {
"invalid-expr": "Expression JSONata non valide :\n __message__",
@ -997,7 +999,7 @@
},
"jsonEditor": {
"title": "Éditeur JSON",
"format": "Format JSON",
"format": "Formatter JSON",
"rawMode": "Modifier JSON",
"uiMode": "Afficher l'éditeur",
"rawMode-readonly": "JSON",
@ -1016,7 +1018,7 @@
"markdownEditor": {
"title": "Éditeur Markdown",
"expand": "Développer",
"format": "Formaté avec Markdown",
"format": "Formatter avec Markdown",
"heading1": "Rubrique 1",
"heading2": "Rubrique 2",
"heading3": "Rubrique 3",
@ -1090,7 +1092,7 @@
"credential-key": "Clé de chiffrement des identifiants",
"cant-get-ssh-key": "Erreur! Impossible d'obtenir le chemin de la clé SSH sélectionnée.",
"already-exists2": "Existe déjà",
"git-error": "Erreur git",
"git-error": "Erreur Git",
"connection-failed": "La connexion a échoué",
"not-git-repo": "Ce n'est pas un dépôt Git",
"repo-not-found": "Référentiel introuvable"
@ -1104,7 +1106,7 @@
"credentials-file": "Fichier d'identifiants"
},
"encryption-config": {
"setup": "Configuration du chiffrage de votre fichier d'informations d'identification",
"setup": "Configuration du chiffrement de votre fichier d'informations d'identification",
"desc0": "Votre fichier d'informations d'identification de flux peut être chiffré pour sécuriser son contenu.",
"desc1": "Si vous souhaitez stocker ces identifiants dans un référentiel Git public, vous devez les chiffrer en fournissant une phrase clé secrète.",
"desc2": "Votre fichier d'identifiants de flux n'est actuellement pas chiffré.",
@ -1161,9 +1163,9 @@
"add-ssh-key": "Ajouter une clé ssh",
"credentials-encryption-key": "Clé de chiffrement des identifiants",
"already-exists-2": "Existe déjà",
"git-error": "Erreur git",
"git-error": "Erreur Git",
"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",
"cant-get-ssh-key-path": "Erreur! Impossible d'obtenir le chemin de la clé SSH sélectionnée.",
"unexpected_error": "Erreur inattendue",
@ -1201,7 +1203,7 @@
},
"errors": {
"no-username-email": "Votre client Git n'est pas configuré avec un nom d'utilisateur/e-mail.",
"unexpected": "Une erreur inattendue est apparue",
"unexpected": "Une erreur inattendue est survenue",
"code": "Code"
}
},
@ -1270,7 +1272,7 @@
"list-modified-nodes": "Afficher les flux modifiés",
"list-hidden-flows": "Afficher les flux cachés",
"list-flows": "Lister les flux",
"list-subflows": "Liste les sous-flux",
"list-subflows": "Lister les sous-flux",
"go-to-previous-location": "Aller à l'emplacement précédent",
"go-to-next-location": "Aller à l'emplacement suivant",
"copy-selection-to-internal-clipboard": "Copier la sélection dans le presse-papiers",
@ -1330,8 +1332,8 @@
"align-selection-to-bottom": "Aligner la sélection vers le bas",
"align-selection-to-middle": "Aligner la sélection au centre verticalement",
"align-selection-to-center": "Aligner la sélection au centre horizontalement",
"distribute-selection-horizontally": "Distribuer la sélection horizontalement",
"distribute-selection-vertical": "Distribuer la sélection verticalement",
"distribute-selection-horizontally": "Répartir la sélection horizontalement",
"distribute-selection-vertical": "Répartir la sélection verticalement",
"wire-series-of-nodes": "Connecter les noeuds en série",
"wire-node-to-multiple": "Connecter les noeuds à plusieurs",
"wire-multiple-to-node": "Connecter plusieurs au noeud",

View File

@ -27,7 +27,8 @@
"lock": "固定",
"unlock": "固定を解除",
"locked": "固定済み",
"unlocked": "固定なし"
"unlocked": "固定なし",
"format": "形式"
},
"type": {
"string": "文字列",
@ -281,8 +282,8 @@
"selected": "選択したフロー",
"current": "現在のタブ",
"all": "全てのタブ",
"compact": "インデントのないJSONフォーマット",
"formatted": "インデント付きのJSONフォーマット",
"compact": "インデントなし",
"formatted": "インデント付き",
"copy": "書き出し",
"export": "ライブラリに書き出し",
"exportAs": "書き出し先",
@ -923,6 +924,8 @@
}
},
"typedInput": {
"selected": "__count__個を選択",
"selected_plural": "__count__個を選択",
"type": {
"str": "文字列",
"num": "数値",

View File

@ -1,6 +1,6 @@
{
"name": "@node-red/editor-client",
"version": "4.0.0",
"version": "4.1.0-beta.0",
"license": "Apache-2.0",
"repository": {
"type": "git",

View File

@ -32,24 +32,28 @@ RED.contextMenu = (function () {
const canRemoveFromGroup = hasSelection && !!selection.nodes[0].g
let hasGroup, isAllGroups = true, hasDisabledNode, hasEnabledNode, hasLabeledNode, hasUnlabeledNode;
if (hasSelection) {
selection.nodes.forEach(n => {
const nodes = selection.nodes.slice();
while (nodes.length) {
const n = nodes.shift();
if (n.type === 'group') {
hasGroup = true;
nodes.push(...n.nodes);
} else {
isAllGroups = false;
}
if (n.d) {
hasDisabledNode = true;
} else {
hasEnabledNode = true;
}
}
if (n.l === undefined || n.l) {
hasLabeledNode = true;
} else {
hasUnlabeledNode = true;
}
});
}
}
const offset = $("#red-ui-workspace-chart").offset()
let addX = options.x - offset.left + $("#red-ui-workspace-chart").scrollLeft()

View File

@ -157,6 +157,12 @@ RED.editor = (function() {
}
}
if (valid && "validate" in definition[property]) {
if (definition[property].hasOwnProperty("required") &&
definition[property].required === false) {
if (value === "") {
return true;
}
}
try {
var opt = {};
if (label) {
@ -183,6 +189,11 @@ RED.editor = (function() {
});
}
} else if (valid) {
if (definition[property].hasOwnProperty("required") && definition[property].required === false) {
if (value === "") {
return true;
}
}
// 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";
@ -413,11 +424,8 @@ RED.editor = (function() {
if (selectedOpt?.data('env')) {
disableButton(addButton, true);
disableButton(editButton, true);
// disable the edit button if no options available
} else if (optionsLength === 1 && selectedOpt.val() === "_ADD_") {
disableButton(addButton, false);
disableButton(editButton, true);
} else if (selectedOpt.val() === "") {
// disable the edit button if no options available or 'none' selected
} else if (optionsLength === 1 || selectedOpt.val() === "_ADD_") {
disableButton(addButton, false);
disableButton(editButton, true);
} else {
@ -426,14 +434,9 @@ RED.editor = (function() {
}
});
var label = "";
var configNode = RED.nodes.node(nodeValue);
if (configNode) {
label = RED.utils.getNodeLabel(configNode, configNode.id);
}
input.val(label);
// If the value is "", 'add new...' option if no config node available or 'none' option
// Otherwise, it's a config node
select.val(nodeValue || '_ADD_');
}
/**
@ -934,9 +937,11 @@ RED.editor = (function() {
}
if (!configNodes.length) {
// Add 'add new...' option
select.append('<option value="_ADD_" selected>' + RED._("editor.addNewType", { type: label }) + '</option>');
} else {
select.append('<option value="">' + RED._("editor.inputs.none") + '</option>');
// Add 'none' option
select.append('<option value="_ADD_">' + RED._("editor.inputs.none") + '</option>');
}
window.setTimeout(function() { select.trigger("change");},50);

View File

@ -165,7 +165,13 @@ RED.editor.codeEditor.monaco = (function() {
//Handles orphaned models
//ensure loaded models that are not explicitly destroyed by a call to .destroy() are disposed
RED.events.on("editor:close",function() {
let models = window.monaco ? monaco.editor.getModels() : null;
if (!window.monaco) { return; }
const editors = window.monaco.editor.getEditors()
const orphanEditors = editors.filter(editor => editor && !document.body.contains(editor.getDomNode()))
orphanEditors.forEach(editor => {
editor.dispose();
});
let models = monaco.editor.getModels()
if(models && models.length) {
console.warn("Cleaning up monaco models left behind. Any node that calls createEditor() should call .destroy().")
for (let index = 0; index < models.length; index++) {
@ -1124,6 +1130,7 @@ RED.editor.codeEditor.monaco = (function() {
$(el).remove();
$(toolbarRow).remove();
ed.dispose();
}
ed.resize = function resize() {

View File

@ -11,9 +11,22 @@ RED.editor.mermaid = (function () {
if (!initializing) {
initializing = true
$.getScript(
'vendor/mermaid/mermaid.min.js',
function (data, stat, jqxhr) {
// Find the cache-buster:
let cacheBuster
$('script').each(function (i, el) {
if (!cacheBuster) {
const src = el.getAttribute('src')
const m = /\?v=(.+)$/.exec(src)
if (m) {
cacheBuster = m[1]
}
}
})
$.ajax({
url: `vendor/mermaid/mermaid.min.js?v=${cacheBuster}`,
dataType: "script",
cache: true,
success: function (data, stat, jqxhr) {
mermaid.initialize({
startOnLoad: false,
theme: RED.settings.get('mermaid', {}).theme
@ -24,7 +37,7 @@ RED.editor.mermaid = (function () {
render(pending)
}
}
)
});
}
} else {
const nodes = document.querySelectorAll(selector)

View File

@ -1100,7 +1100,7 @@ RED.subflow = (function() {
input.val(val.value);
break;
case "cred":
input = $('<input type="password">').css('width','70%').appendTo(row);
input = $('<input type="password">').css('width','70%').attr('id', elId).appendTo(row);
if (node.credentials) {
if (node.credentials[tenv.name]) {
input.val(node.credentials[tenv.name]);
@ -1346,7 +1346,7 @@ RED.subflow = (function() {
}
break;
case "cred":
item.value = input.val();
item.value = input.typedInput('value');
item.type = 'cred';
break;
case "spinner":

View File

@ -103,7 +103,7 @@ RED.sidebar.info.outliner = (function() {
evt.stopPropagation();
RED.search.show("type:subflow:"+n.id);
})
// RED.popover.tooltip(userCountBadge,function() { return RED._('editor.nodesUse',{count:n.users.length})});
RED.popover.tooltip(subflowInstanceBadge,function() { return RED._('subflow.subflowInstances',{count:n.instances.length})});
}
if (n._def.category === "config" && n.type !== "group") {
var userCountBadge = $('<button type="button" class="red-ui-info-outline-item-control-users red-ui-button red-ui-button-small"><i class="fa fa-toggle-right"></i></button>').text(n.users.length).appendTo(controls).on("click",function(evt) {

View File

@ -259,7 +259,7 @@ $deploy-button-background-disabled-hover: #555;
$header-background: #000;
$header-button-background-active: #121212;
$header-accent: #d41313;
$header-accent: #C02020;
$header-menu-color: #eee;
$header-menu-color-disabled: #666;
$header-menu-heading-color: #fff;

View File

@ -108,12 +108,13 @@ in your Node-RED user directory (${RED.settings.userDir}).
if (n.proxy && proxyConfig) {
proxyOptions.env = {
no_proxy: (proxyConfig.noproxy || []).join(','),
http_proxy: (proxyConfig.url)
http_proxy: (proxyConfig.url),
https_proxy: (proxyConfig.url)
}
}
return getProxyForUrl(url, proxyOptions)
}
let prox = getProxy(nodeUrl || '')
let prox = nodeUrl ? getProxy(nodeUrl) : null
let timingLog = false;
if (RED.settings.hasOwnProperty("httpRequestTimingLog")) {
@ -534,9 +535,7 @@ in your Node-RED user directory (${RED.settings.userDir}).
opts.headers[clSet] = opts.headers['content-length'];
delete opts.headers['content-length'];
}
if (!opts.headers.hasOwnProperty('user-agent')) {
opts.headers['user-agent'] = 'Mozilla/5.0 (Node-RED)';
}
if (proxyUrl) {
const match = proxyUrl.match(/^(https?:\/\/)?(.+)?:([0-9]+)?/i);
if (match) {
@ -566,7 +565,7 @@ in your Node-RED user directory (${RED.settings.userDir}).
//need both incase of http -> https redirect
opts.agent = {
http: new HttpProxyAgent(proxyOptions),
https: new HttpProxyAgent(proxyOptions)
https: new HttpsProxyAgent(proxyOptions)
};
} else {

View File

@ -17,7 +17,11 @@
<script type="text/html" data-template-name="split">
<!-- <div class="form-row"><span data-i18n="[html]split.intro"></span></div> -->
<div class="form-row">
<label for="node-input-property"><i class="fa fa-forward"></i> <span data-i18n="split.split"></span></label>
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="node-red:common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]node-red:common.label.name">
</div>
<div class="form-row">
<label for="node-input-property"><i class="fa fa-forward"></i> <span data-i18n="split.splitThe"></span></label>
<input type="text" id="node-input-property" style="width:70%;">
</div>
<div class="form-row"><span data-i18n="[html]split.strBuff"></span></div>
@ -43,10 +47,6 @@
<label for="node-input-addname-cb" style="width:auto;" data-i18n="split.addname"></label>
<input type="text" id="node-input-addname" style="width:70%">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="node-red:common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]node-red:common.label.name">
</div>
</script>
<script type="text/javascript">
@ -122,6 +122,10 @@
<script type="text/html" data-template-name="join">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-row">
<label data-i18n="join.mode.mode"></label>
<select id="node-input-mode" style="width:200px;">
@ -157,6 +161,12 @@
<input type="text" id="node-input-joiner" style="width:70%">
<input type="hidden" id="node-input-joinerType">
</div>
<div class="form-row">
<input type="checkbox" id="node-input-useparts" style="margin-left:8px; margin-right:8px; vertical-align:baseline; width:auto;">
<label for="node-input-useparts" style="width:auto;" data-i18n="join.useparts"></label>
</div>
<div class="form-row node-row-trigger" id="trigger-row">
<label style="width:auto;" data-i18n="join.send"></label>
<ul>
@ -195,10 +205,6 @@
<label for="node-input-reduceRight" data-i18n="join.reduce.right" style="width:70%; margin-left:10px;"></label>
</div>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-tips form-tips-auto hide" data-i18n="[html]join.tip"></div>
</script>
@ -234,6 +240,7 @@
},
joiner: { value:"\\n"},
joinerType: { value:"str"},
useparts: { value:false },
accumulate: { value:"false" },
timeout: {value:""},
count: {value:""},
@ -259,6 +266,12 @@
},
oneditprepare: function() {
var node = this;
$("#node-input-useparts").on("change", function(e) {
if (node.useparts === undefined) {
node.useparts = true;
$("#node-input-useparts").attr('checked', true);
}
});
$("#node-input-mode").on("change", function(e) {
var val = $(this).val();

View File

@ -444,6 +444,8 @@ module.exports = function(RED) {
this.count = Number(n.count || 0);
this.joiner = n.joiner||"";
this.joinerType = n.joinerType||"str";
if (n.useparts === undefined) { this.useparts = true; }
else { this.useparts = n.useparts || false; }
this.reduce = (this.mode === "reduce");
if (this.reduce) {
@ -611,7 +613,7 @@ module.exports = function(RED) {
return;
}
if (node.mode === 'custom' && msg.hasOwnProperty('parts')) {
if (node.mode === 'custom' && msg.hasOwnProperty('parts') && node.useparts === false ) {
if (msg.parts.hasOwnProperty('parts')) {
msg.parts = { parts: msg.parts.parts };
}

View File

@ -36,6 +36,10 @@
<label style="margin-left: 10px; width: 175px;" for="node-input-overlap" data-i18n="batch.count.overlap"></label>
<input type="text" id="node-input-overlap" style="width: 50px;">
</div>
<div class="form-row">
<input type="checkbox" id="node-input-honourParts" style="margin-left: 10px; margin-right:10px; vertical-align:top; width:auto;">
<label for="node-input-honourParts" style="width:auto;" data-i18n="batch.honourParts"></label>
</div>
</div>
<div class="node-row-msg-interval">
@ -45,7 +49,7 @@
<span data-i18n="batch.interval.seconds"></span>
</div>
<div class="form-row">
<input type="checkbox" id="node-input-allowEmptySequence" style="margin-left:20px; margin-right: 10px; vertical-align:top; width:auto;">
<input type="checkbox" id="node-input-allowEmptySequence" style="margin-left:20px; margin-right:10px; vertical-align:top; width:auto;">
<label for="node-input-allowEmptySequence" style="width:auto;" data-i18n="batch.interval.empty"></label>
</div>
</div>
@ -101,6 +105,7 @@
}
},
allowEmptySequence: {value:false},
honourParts: {value:false},
topics: {value:[{topic:""}]}
},
inputs:1,

View File

@ -181,6 +181,8 @@ module.exports = function(RED) {
RED.nodes.createNode(this,n);
var node = this;
var mode = n.mode || "count";
var eof = false;
node.honourParts = n.honourParts || false;
node.pending_count = 0;
if (mode === "count") {
@ -201,9 +203,12 @@ module.exports = function(RED) {
return;
}
var queue = node.pending;
if (node.honourParts && msg.hasOwnProperty("parts")) {
if (msg.parts.index + 1 === msg.parts.count) { eof = true; }
}
queue.push({msg, send, done});
node.pending_count++;
if (queue.length === count) {
if (queue.length === count || eof === true) {
send_msgs(node, queue, is_overlap);
for (let i = 0; i < queue.length-overlap; i++) {
queue[i].done();
@ -211,6 +216,7 @@ module.exports = function(RED) {
node.pending =
(overlap === 0) ? [] : queue.slice(-overlap);
node.pending_count = 0;
eof = false;
}
var max_msgs = max_kept_msgs_count(node);
if ((max_msgs > 0) && (node.pending_count > max_msgs)) {

View File

@ -20,12 +20,26 @@
<dt class="optional">delay <span class="property-type">number</span></dt>
<dd>Legt die Verzögerung in Millisekunden fest, die auf die Nachricht angewendet werden soll.
Zur Nutzung dieser Option muss <i>Verzög. mit msg.delay überschreibbar</i> aktiviert sein.</dd>
<dt class="optional">rate <span class="property-type">number</span></dt>
<dd>Setzt die Verzögerung in Millisekunden zwischen den Nachrichten. Diese Node überschreibt die
bestehende Verzögerung die in der Node konfiguration, wenn die empfangende Nachricht <code>msg.rate</code>
in Millisekunden enthält. Dies trifft nur zu, wenn in der Node konfiguriert ist, das empfangene
Nachrichten den konfigurierten Wert überschreiben können.</dd>
<dt class="optional">reset</dt>
<dd>Wenn bei der empfangenen Nachricht diese Eigenschaft auf einen beliebigen Wert gesetzt ist,
werden alle im Node gepufferten Nachrichten gelöscht.</dd>
<dt class="optional">flush</dt>
<dd>Wenn bei der empfangenen Nachricht diese Eigenschaft auf einen beliebigen Wert gesetzt ist,
werden alle im Node gepufferten Nachrichten sofort gesendet.</dd>
<dt class="optional">flush</dt>
<dd>Wenn bei der empfangenen Nachricht diese Eigenschaft auf einen numerischen Wert gesetzt ist,
wird diese Anzahl an Nachrichten sofort gesendet. Wenn ein anderer Typ gesetzt ist (z.B. Boolean),
werden alle in der Node gepufferten Nachrichten gesendet.</dd>
<dt class="optional">toFront</dt>
<dd>Wenn diese Eigenschaft im Ratenbegrenzungsmodus für die empfangene Nachricht auf den booleschen Wert
<code>true</code> gesetzt ist, Anschließend wird die Nachricht an den Anfang der Warteschlange verschoben
und als nächstes freigegeben. Dies kann in Kombination mit <code>msg.flush=1</code> verwendet werden, um sofort erneut zu senden.
</dd>
</dl>
<h3>Details</h3>
<p>Wenn Verzögerung als Nachrichtenaktion eingestellt ist, kann die Verzögerungszeit ein fixer Wert,

View File

@ -912,6 +912,7 @@
"objectSend": "Sende eine Nachricht für jedes Schlüssel/Wert-Paar",
"strBuff": "<b>string</b> / <b>buffer</b>",
"array": "<b>array</b>",
"splitThe": "Split",
"splitUsing": "Aufteilung",
"splitLength": "feste Längen von",
"stream": "Als Nachrichtenstrom behandeln (Streaming-Modus)",

View File

@ -1011,12 +1011,13 @@
"tip": "Tip: The filename should be an absolute path, otherwise it will be relative to the working directory of the Node-RED process."
},
"split": {
"split": "Split",
"split": "split",
"intro": "Split <code>msg.payload</code> based on type:",
"object": "<b>Object</b>",
"objectSend": "Send a message for each key/value pair",
"strBuff": "<b>String</b> / <b>Buffer</b>",
"array": "<b>Array</b>",
"splitThe": "Split the",
"splitUsing": "Split using",
"splitLength": "Fixed length of",
"stream": "Handle as a stream of messages",
@ -1046,6 +1047,7 @@
"joinedUsing": "joined using",
"send": "Send the message:",
"afterCount": "After a number of message parts",
"useparts": "Use existing msg.parts property",
"count": "count",
"subsequent": "and every subsequent message.",
"afterTimeout": "After a timeout following the first message",
@ -1112,6 +1114,7 @@
"too-many": "too many pending messages in batch node",
"unexpected": "unexpected mode",
"no-parts": "no parts property in message",
"honourParts": "Allow msg.parts to also complete batch operation.",
"error": {
"invalid-count": "Invalid count",
"invalid-overlap": "Invalid overlap",

View File

@ -1017,6 +1017,7 @@
"objectSend": "各key/valueペアのメッセージを送信",
"strBuff": "<b>文字列</b> / <b>バッファ</b>",
"array": "<b>配列</b>",
"splitThe": "に基づく",
"splitUsing": "分割",
"splitLength": "固定長",
"stream": "メッセージのストリームとして処理",

View File

@ -994,6 +994,7 @@
"objectSend": "Envia uma mensagem para cada par chave/valor",
"strBuff": "<b>Cadeia de caracteres</b> / <b>Armazenamento Temporário</b>",
"array": "<b>Matriz</b>",
"splitThe": "Dividir",
"splitUsing": "Dividir usando",
"splitLength": "Comprimento fixo de",
"stream": "Tratar como uma transmissão de mensagens",

View File

@ -874,6 +874,7 @@
"objectSend":"Отправлять сообщение для каждой пары ключ/значение",
"strBuff":"<b>Строка</b> / <b>Буфер</b>",
"array":"<b>Массив</b>",
"splitThe": "Pазделить",
"splitUsing":"С помощью",
"splitLength":"Фикс. длина",
"stream":"Обрабатывать как поток сообщений",

View File

@ -997,6 +997,7 @@
"objectSend": "每个键值对作为单个消息发送",
"strBuff": "<b>字符串</b> / <b>Buffer</b>",
"array": "<b>数组</b>",
"splitThe": "Split",
"splitUsing": "拆分使用",
"splitLength": "固定长度",
"stream": "作为消息流处理",

View File

@ -866,6 +866,7 @@
"objectSend": "每個鍵值對作為單個消息發送",
"strBuff": "<b>字串</b> / <b>Buffer</b>",
"array": "<b>陣列</b>",
"splitThe": "Split",
"splitUsing": "拆分使用",
"splitLength": "固定長度",
"stream": "作為消息流處理",

View File

@ -1,6 +1,6 @@
{
"name": "@node-red/nodes",
"version": "4.0.0",
"version": "4.1.0-beta.0",
"license": "Apache-2.0",
"repository": {
"type": "git",

View File

@ -1,6 +1,6 @@
{
"name": "@node-red/registry",
"version": "4.0.0",
"version": "4.1.0-beta.0",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@ -16,7 +16,7 @@
}
],
"dependencies": {
"@node-red/util": "4.0.0",
"@node-red/util": "4.1.0-beta.0",
"clone": "2.1.2",
"fs-extra": "11.2.0",
"semver": "7.5.4",

View File

@ -645,16 +645,27 @@ function getFlow(id) {
if (id !== 'global') {
result.nodes = [];
}
if (flow.groups) {
var nodeIds = Object.keys(flow.groups);
if (nodeIds.length > 0) {
nodeIds.forEach(function(nodeId) {
var node = jsonClone(flow.groups[nodeId]);
delete node.credentials;
result.nodes.push(node)
})
}
}
if (flow.nodes) {
var nodeIds = Object.keys(flow.nodes);
if (nodeIds.length > 0) {
result.nodes = nodeIds.map(function(nodeId) {
nodeIds.forEach(function(nodeId) {
var node = jsonClone(flow.nodes[nodeId]);
if (node.type === 'link out') {
delete node.wires;
}
delete node.credentials;
return node;
result.nodes.push(node)
})
}
}
@ -680,6 +691,17 @@ function getFlow(id) {
delete node.credentials
return node
});
if (subflow.groups) {
var nodeIds = Object.keys(subflow.groups);
if (nodeIds.length > 0) {
nodeIds.forEach(function(nodeId) {
var node = jsonClone(subflow.groups[nodeId]);
delete node.credentials;
subflow.nodes.push(node)
})
}
delete subflow.groups
}
if (subflow.configs) {
var configIds = Object.keys(subflow.configs);
subflow.configs = configIds.map(function(id) {

View File

@ -23,6 +23,7 @@ module.exports = {
if (existingSessionId) {
connections.delete(opts.session)
const session = sessions.get(existingSessionId)
if (session) {
session.active = false
session.idleTimeout = setTimeout(() => {
sessions.delete(existingSessionId)
@ -32,6 +33,7 @@ module.exports = {
data: { session: existingSessionId }
})
}
}
})
runtime.events.on('comms:message:multiplayer/connect', (opts) => {
let session
@ -91,6 +93,7 @@ module.exports = {
const sessionId = connections.get(opts.session)
const session = sessions.get(sessionId)
if (session) {
if (opts.user) {
if (session.user.anonymous !== opts.user.anonymous) {
session.user = opts.user
@ -114,6 +117,7 @@ module.exports = {
data: payload,
excludeSession: opts.session
})
}
})
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@node-red/runtime",
"version": "4.0.0",
"version": "4.1.0-beta.0",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@ -16,8 +16,8 @@
}
],
"dependencies": {
"@node-red/registry": "4.0.0",
"@node-red/util": "4.0.0",
"@node-red/registry": "4.1.0-beta.0",
"@node-red/util": "4.1.0-beta.0",
"async-mutex": "0.5.0",
"clone": "2.1.2",
"express": "4.19.2",

View File

@ -1,6 +1,6 @@
{
"name": "@node-red/util",
"version": "4.0.0",
"version": "4.1.0-beta.0",
"license": "Apache-2.0",
"repository": {
"type": "git",

View File

@ -1,6 +1,6 @@
{
"name": "node-red",
"version": "4.0.0",
"version": "4.1.0-beta.0",
"description": "Low-code programming for event-driven applications",
"homepage": "https://nodered.org",
"license": "Apache-2.0",
@ -31,10 +31,10 @@
"flow"
],
"dependencies": {
"@node-red/editor-api": "4.0.0",
"@node-red/runtime": "4.0.0",
"@node-red/util": "4.0.0",
"@node-red/nodes": "4.0.0",
"@node-red/editor-api": "4.1.0-beta.0",
"@node-red/runtime": "4.1.0-beta.0",
"@node-red/util": "4.1.0-beta.0",
"@node-red/nodes": "4.1.0-beta.0",
"basic-auth": "2.0.1",
"bcryptjs": "2.4.3",
"cors": "2.8.5",

View File

@ -17,6 +17,8 @@
var http = require("http");
var https = require("https");
var should = require("should");
var sinon = require("sinon");
var httpProxyHelper = require("nr-test-utils").require("@node-red/nodes/core/network/lib/proxyHelper.js");
var express = require("express");
var bodyParser = require('body-parser');
var stoppable = require('stoppable');
@ -493,6 +495,7 @@ describe('HTTP Request Node', function() {
});
afterEach(function() {
sinon.restore();
process.env.http_proxy = preEnvHttpProxyLowerCase;
process.env.HTTP_PROXY = preEnvHttpProxyUpperCase;
// On Windows, if environment variable of NO_PROXY that includes lower cases
@ -1799,27 +1802,80 @@ describe('HTTP Request Node', function() {
})
});
//Removing HTTP Proxy testcases as GOT + Proxy_Agent doesn't work with mock'd proxy
/* */
it('should use http_proxy', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')},
{id:"n2", type:"helper"}];
it('should use env var http_proxy', function(done) {
const url = getTestURL('/postInspect')
const proxyUrl = "http://localhost:" + testProxyPort
const flow = [
{ id: "n1", type: "http request", wires: [["n2"]], method: "POST", ret: "obj", url: url },
{ id: "n2", type: "helper" },
];
const proxySpy = sinon.spy(httpProxyHelper, 'getProxyForUrl')
const testNode = [httpRequestNode, httpProxyNode];
deleteProxySetting();
process.env.http_proxy = "http://localhost:" + testProxyPort;
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
process.env.http_proxy = proxyUrl
helper.load(testNode, flow, function (msg) {
try {
msg.should.have.property('statusCode',200);
msg.payload.should.have.property('headers');
//msg.payload.headers.should.have.property('x-testproxy-header','foobar');
done();
} catch(err) {
// static URL set in the nodes configuration and the proxy will be setup upon initialisation
proxySpy.calledOnce.should.be.true()
proxySpy.calledWith(url, { }).should.be.true()
proxySpy.returnValues[0].should.be.equal(proxyUrl)
done()
} catch (err) {
done(err);
}
});
n1.receive({payload:"foo"});
});
it('should use env var https_proxy', function(done) {
const url = getSslTestURL('/postInspect')
const proxyUrl = "http://localhost:" + testProxyPort
const flow = [
{ id: "n1", type: "http request", wires: [["n2"]], method: "POST", ret: "obj", url: url },
{ id: "n2", type: "helper" },
];
const proxySpy = sinon.spy(httpProxyHelper, 'getProxyForUrl')
const testNode = [httpRequestNode, httpProxyNode];
deleteProxySetting();
process.env.https_proxy = proxyUrl
helper.load(testNode, flow, function (msg) {
try {
// static URL set in the nodes configuration and the proxy will be setup upon initialisation
proxySpy.calledOnce.should.be.true()
proxySpy.calledWith(url, { }).should.be.true()
proxySpy.returnValues[0].should.be.equal(proxyUrl)
done()
} catch (err) {
done(err);
}
});
});
it('should not use env var http*_proxy when no_proxy is set', function(done) {
const url = getSslTestURL('/postInspect')
const proxyUrl = "http://localhost:" + testProxyPort
const flow = [
{ id: "n1", type: "http request", wires: [["n2"]], method: "POST", ret: "obj", url: url },
{ id: "n2", type: "helper" },
];
const proxySpy = sinon.spy(httpProxyHelper, 'getProxyForUrl')
const testNode = [httpRequestNode, httpProxyNode];
deleteProxySetting();
process.env.http_proxy = proxyUrl
process.env.https_proxy = proxyUrl
process.env.no_proxy = "localhost"
helper.load(testNode, flow, function (msg) {
try {
// static URL set in the nodes configuration and the proxy will be setup upon initialisation
proxySpy.calledOnce.should.be.true()
proxySpy.calledWith(url, { }).should.be.true()
proxySpy.returnValues[0].should.be.equal('')
done()
} catch (err) {
done(err);
}
});
});
@ -1997,6 +2053,135 @@ describe('HTTP Request Node', function() {
});
});
it('should use UI proxy for statically configured URL', function (done) {
const url = getTestURL('/postInspect')
const proxyUrl = "http://localhost:" + testProxyPort
const flow = [
{ id: "n1", type: "http request", wires: [["n2"]], method: "POST", ret: "obj", url: url, proxy: "n3" },
{ id: "n2", type: "helper" },
{ id: "n3", type: "http proxy", url: proxyUrl, noproxy: ["foo"] }
];
const proxySpy = sinon.spy(httpProxyHelper, 'getProxyForUrl')
const testNode = [httpRequestNode, httpProxyNode];
deleteProxySetting();
// static URL set in the nodes configuration will cause the proxy setup to be called
// no no need to send a message to the node
helper.load(testNode, flow, function () {
try {
// ensure getProxyForUrl was called and returned the correct proxy URL
proxySpy.calledOnce.should.be.true()
proxySpy.calledWith(url, { env: { no_proxy: "foo", http_proxy: proxyUrl, https_proxy: proxyUrl } }).should.be.true()
proxySpy.returnValues[0].should.be.equal(proxyUrl)
done();
} catch (err) {
done(err);
}
});
});
it('should use UI proxy for HTTP URL passed in via msg', function (done) {
const url = getTestURL('/postInspect')
const proxyUrl = "http://localhost:" + testProxyPort
const flow = [
{ id: "n1", type: "http request", wires: [["n2"]], method: "POST", ret: "obj", url: "", proxy: "n3" },
{ id: "n2", type: "helper" },
{ id: "n3", type: "http proxy", url: proxyUrl, noproxy: ["foo,bar"] }
];
const proxySpy = sinon.spy(httpProxyHelper, 'getProxyForUrl')
const testNode = [httpRequestNode, httpProxyNode];
deleteProxySetting();
helper.load(testNode, flow, function () {
const n1 = helper.getNode("n1");
const n2 = helper.getNode("n2");
try {
proxySpy.calledOnce.should.be.false() // proxy setup should not be called when there is no URL to check needs proxying
} catch (err) {
done(err);
return
}
n2.on("input", function (msg) {
try {
// ensure getProxyForUrl was called and returned the correct proxy URL
proxySpy.calledOnce.should.be.true()
proxySpy.calledWith(url, { env: { no_proxy: "foo,bar", http_proxy: proxyUrl, https_proxy: proxyUrl } }).should.be.true()
proxySpy.returnValues[0].should.be.equal(proxyUrl)
done();
} catch (err) {
done(err);
}
});
n1.receive({ url: url });
});
});
it('should use UI proxy for HTTPS URL passed in via msg', function (done) {
const url = getSslTestURL('/postInspect')
const proxyUrl = "http://localhost:" + testProxyPort
const flow = [
{ id: "n1", type: "http request", wires: [["n2"]], method: "POST", ret: "obj", url: "", proxy: "n3" },
{ id: "n2", type: "helper" },
{ id: "n3", type: "http proxy", url: proxyUrl, noproxy: ["foo,bar,baz"] }
];
const proxySpy = sinon.spy(httpProxyHelper, 'getProxyForUrl')
const testNode = [httpRequestNode, httpProxyNode];
deleteProxySetting();
helper.load(testNode, flow, function () {
const n1 = helper.getNode("n1");
const n2 = helper.getNode("n2");
try {
proxySpy.calledOnce.should.be.false() // proxy setup should not be called when there is no URL to check needs proxying
} catch (err) {
done(err);
return
}
n2.on("input", function (msg) {
try {
// ensure getProxyForUrl was called and returned the correct proxy URL
proxySpy.calledOnce.should.be.true()
proxySpy.calledWith(url, { env: { no_proxy: "foo,bar,baz", http_proxy: proxyUrl, https_proxy: proxyUrl } }).should.be.true()
proxySpy.returnValues[0].should.be.equal(proxyUrl)
done();
} catch (err) {
done(err);
}
});
n1.receive({ url: url });
});
});
it('should not use UI proxy if noproxy excludes it', function (done) {
const url = getSslTestURL('/postInspect')
const proxyUrl = "http://localhost:" + testProxyPort
const flow = [
{ id: "n1", type: "http request", wires: [["n2"]], method: "POST", ret: "obj", url: "", proxy: "n3" },
{ id: "n2", type: "helper" },
{ id: "n3", type: "http proxy", url: proxyUrl, noproxy: ["foo,localhost,baz"] }
];
const proxySpy = sinon.spy(httpProxyHelper, 'getProxyForUrl')
const testNode = [httpRequestNode, httpProxyNode];
deleteProxySetting();
helper.load(testNode, flow, function () {
const n1 = helper.getNode("n1");
const n2 = helper.getNode("n2");
try {
proxySpy.calledOnce.should.be.false() // proxy setup should not be called when there is no URL to check needs proxying
} catch (err) {
done(err);
return
}
n2.on("input", function (msg) {
try {
// ensure getProxyForUrl was called and returned no proxy
proxySpy.calledOnce.should.be.true()
proxySpy.calledWith(url, { env: { no_proxy: "foo,localhost,baz", http_proxy: proxyUrl, https_proxy: proxyUrl } }).should.be.true()
proxySpy.returnValues[0].should.be.equal('')
done();
} catch (err) {
done(err);
}
});
n1.receive({ url: url });
});
});
});
describe('authentication', function() {

View File

@ -98,7 +98,7 @@ describe('BATCH node', function() {
var n2 = helper.getNode("n2");
check_data(n1, n2, results, done);
for(var i = 0; i < 6; i++) {
n1.receive({payload: i});
n1.receive({payload: i, parts: { count:6, index:i }});
}
});
}
@ -168,6 +168,25 @@ describe('BATCH node', function() {
check_count(flow, results, done);
});
it('should create seq. with count (more sent than count)', function(done) {
var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "count", count: 4, overlap: 0, interval: 10, allowEmptySequence: false, topics: [], wires:[["n2"]]},
{id:"n2", type:"helper"}];
var results = [
[0, 1, 2, 3]
];
check_count(flow, results, done);
});
it('should create seq. with count and terminate early if parts honoured', function(done) {
var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "count", count: 4, overlap: 0, interval: 10, allowEmptySequence:false, honourParts:true, topics: [], wires:[["n2"]]},
{id:"n2", type:"helper"}];
var results = [
[0, 1, 2, 3],
[4, 5]
];
check_count(flow, results, done);
});
it('should create seq. with count and overlap', function(done) {
var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "count", count: 3, overlap: 2, interval: 10, allowEmptySequence: false, topics: [], wires:[["n2"]]},
{id:"n2", type:"helper"}];