1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Merge remote-tracking branch 'upstream/dev' into dev

This commit is contained in:
Steve-Mcl 2023-06-21 16:39:32 +01:00
commit d8f4f92e1d
24 changed files with 541 additions and 151 deletions

View File

@ -504,6 +504,7 @@
"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",
"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",

View File

@ -5,17 +5,20 @@ export default {
titleIcon: "fa fa-map-o", titleIcon: "fa fa-map-o",
title: { title: {
"en-US": "Welcome to Node-RED 3.0!", "en-US": "Welcome to Node-RED 3.0!",
"ja": "Node-RED 3.0へようこそ!" "ja": "Node-RED 3.0へようこそ!",
"fr": "Bienvenue dans Node-RED 3.0 !"
}, },
description: { description: {
"en-US": "<p>Let's take a moment to discover the new features in this release.</p>", "en-US": "<p>Let's take a moment to discover the new features in this release.</p>",
"ja": "<p>本リリースの新機能を見つけてみましょう。</p>" "ja": "<p>本リリースの新機能を見つけてみましょう。</p>",
"fr": "<p>Prenons un moment pour découvrir les nouvelles fonctionnalités de cette version.</p>"
} }
}, },
{ {
title: { title: {
"en-US": "Context Menu", "en-US": "Context Menu",
"ja": "コンテキストメニュー" "ja": "コンテキストメニュー",
"fr": "Menu contextuel"
}, },
image: '3.0/images/context-menu.png', image: '3.0/images/context-menu.png',
description: { description: {
@ -24,13 +27,17 @@ export default {
<p>This makes many of the built-in actions much easier <p>This makes many of the built-in actions much easier
to access.</p>`, to access.</p>`,
"ja": `<p>ワークスペースで右クリックすると、エディタに独自のコンテキストメニューが表示されるようになりました。</p> "ja": `<p>ワークスペースで右クリックすると、エディタに独自のコンテキストメニューが表示されるようになりました。</p>
<p>これによって多くの組み込み動作をより簡単に利用できます</p>` <p>これによって多くの組み込み動作をより簡単に利用できます</p>`,
"fr": `<p>L'éditeur a maintenant son propre menu contextuel lorsque vous
faites un clic droit dans l'espace de travail.</p>
<p>Cela facilite l'accès à de nombreuses actions intégrées.</p>`
} }
}, },
{ {
title: { title: {
"en-US": "Wire Junctions", "en-US": "Wire Junctions",
"ja": "分岐点をワイヤーに追加" "ja": "分岐点をワイヤーに追加",
"fr": "Jonctions de fils"
}, },
image: '3.0/images/junction-slice.gif', image: '3.0/images/junction-slice.gif',
description: { description: {
@ -40,13 +47,18 @@ export default {
<p>Junctions can be added to wires by holding both the Alt key and the Shift key <p>Junctions can be added to wires by holding both the Alt key and the Shift key
then click and drag the mouse across the wires.</p>`, then click and drag the mouse across the wires.</p>`,
"ja": `<p>フローのワイヤーの経路をより制御しやすくするために、分岐点ノードを追加できるようになりました。</p> "ja": `<p>フローのワイヤーの経路をより制御しやすくするために、分岐点ノードを追加できるようになりました。</p>
<p>Altキーとシフトキーを押しながらマウスをクリックしワイヤーを横切るようにドラッグすることで分岐点を追加できます</p>` <p>Altキーとシフトキーを押しながらマウスをクリックしワイヤーを横切るようにドラッグすることで分岐点を追加できます</p>`,
"fr": `<p>Pour faciliter le routage des câbles autour de vos flux, il est désormais possible d'ajouter des noeuds
de jonction qui vous donnent plus de contrôle.</p>
<p>Les jonctions peuvent être ajoutées aux fils en maintenant les touches Alt et Maj enfoncées, puis en cliquant
et en faisant glisser la souris sur les fils.</p>`
}, },
}, },
{ {
title: { title: {
"en-US": "Wire Junctions", "en-US": "Wire Junctions",
"ja": "分岐点をワイヤーに追加" "ja": "分岐点をワイヤーに追加",
"fr": "Jonctions de fils"
}, },
image: '3.0/images/junction-quick-add.png', image: '3.0/images/junction-quick-add.png',
description: { description: {
@ -54,13 +66,16 @@ export default {
<p>The dialog is opened by holding the Ctrl (or Cmd) key when <p>The dialog is opened by holding the Ctrl (or Cmd) key when
clicking in the workspace.</p>`, clicking in the workspace.</p>`,
"ja": `<p>クイック追加ダイアログを用いて、分岐点を追加することもできます。</p> "ja": `<p>クイック追加ダイアログを用いて、分岐点を追加することもできます。</p>
<p>本ダイアログを開くにはCtrl(またはCmd)キーを押しながらワークスペース上でクリックします</p>` <p>本ダイアログを開くにはCtrl(またはCmd)キーを押しながらワークスペース上でクリックします</p>`,
"fr": `<p>Les jonctions peuvent également être ajoutées à l'aide de la boîte de dialogue d'ajout rapide.</p>
<p>La boîte de dialogue s'ouvre en maintenant la touche Ctrl (ou Cmd) enfoncée lors d'un clic dans l'espace de travail.</p>`
}, },
}, },
{ {
title: { title: {
"en-US": "Debug Path Tooltip", "en-US": "Debug Path Tooltip",
"ja": "デバッグパスのツールチップ" "ja": "デバッグパスのツールチップ",
"fr": "Info-bulle du chemin de débogage"
}, },
image: '3.0/images/debug-path-tooltip.png', image: '3.0/images/debug-path-tooltip.png',
description: { description: {
@ -73,26 +88,33 @@ export default {
the workspace.</p>`, the workspace.</p>`,
"ja": `<p>デバックサイドバー内のノード名の上にマウスカーソルを乗せると、新たにツールチップが表示され、ノードの場所が分かるようになっています。</p> "ja": `<p>デバックサイドバー内のノード名の上にマウスカーソルを乗せると、新たにツールチップが表示され、ノードの場所が分かるようになっています。</p>
<p>これはサブフローを用いる時に役立つ機能でありメッセージがどのノードから出力されたかを正確に特定することが遥かに簡単になります</p> <p>これはサブフローを用いる時に役立つ機能でありメッセージがどのノードから出力されたかを正確に特定することが遥かに簡単になります</p>
<p>本リスト内の要素をクリックするとワークスペース内にその要素が表示されます</p>` <p>本リスト内の要素をクリックするとワークスペース内にその要素が表示されます</p>`,
"fr": `<p>Lorsque vous passez la souris sur un nom de noeud dans la barre latérale de débogage, une nouvelle info-bulle affiche l'emplacement complet du noeud.</p>
<p>C'est utile lorsque vous travaillez avec des sous-flux, ce qui facilite l'identification exacte du noeud qui a généré le message.</p>
<p>Cliquer sur n'importe quel élément de la liste le révélera dans l'espace de travail.</p>`
}, },
}, },
{ {
title: { title: {
"en-US": "Continuous Search", "en-US": "Continuous Search",
"ja": "連続した検索" "ja": "連続した検索",
"fr": "Recherche continue"
}, },
image: '3.0/images/continuous-search.png', image: '3.0/images/continuous-search.png',
description: { description: {
"en-US": `<p>When searching for things in the editor, a new toolbar in "en-US": `<p>When searching for things in the editor, a new toolbar in
the workspace provides options to quickly jump between the workspace provides options to quickly jump between
the search results.</p>`, the search results.</p>`,
"ja": `<p>ワークスペース内の新しいツールバーにあるオプションによって、エディタ内を検索する際に、検索結果の間を素早く移動できます。</p>` "ja": `<p>ワークスペース内の新しいツールバーにあるオプションによって、エディタ内を検索する際に、検索結果の間を素早く移動できます。</p>`,
"fr": `<p>Lorsque vous recherchez des éléments dans l'éditeur, une nouvelle barre d'outils dans l'espace de travail fournit des options pour passer
rapidement d'un résultat de recherche à l'autre.</p>`
}, },
}, },
{ {
title: { title: {
"en-US": "New wiring actions", "en-US": "New wiring actions",
"ja": "新しいワイヤー操作" "ja": "新しいワイヤー操作",
"fr": "Nouvelles actions de câblage"
}, },
image: "3.0/images/split-wire-with-links.gif", image: "3.0/images/split-wire-with-links.gif",
description: { description: {
@ -106,12 +128,18 @@ export default {
<li><b><code>ワイヤーをlinkードで分割</code></b></li> <li><b><code>ワイヤーをlinkードで分割</code></b></li>
</ul> </ul>
<p>本アクションはメインメニュー内の動作一覧から呼び出せます</p>`, <p>本アクションはメインメニュー内の動作一覧から呼び出せます</p>`,
"fr": `<p>Une nouvelle action a été ajoutée pour remplacer un fil par une paire de noeuds de lien connectés :</p>
<ul>
<li><b><code>Diviser le fil avec les noeuds de liaison</code></b></li>
</ul>
<p>Les actions sont accessibles à partir de la liste d'actions dans le menu principal.</p>`
}, },
}, },
{ {
title: { title: {
"en-US": "Default node names", "en-US": "Default node names",
"ja": "標準ノードの名前" "ja": "標準ノードの名前",
"fr": "Noms de noeud par défaut"
}, },
// image: "images/", // image: "images/",
description: { description: {
@ -129,13 +157,22 @@ export default {
<ul> <ul>
<li><b><code>ノード名を生成</code></b></li> <li><b><code>ノード名を生成</code></b></li>
</ul><p></p> </ul><p></p>
` `,
"fr": `<p>Certains noeuds ont été mis à jour pour générer un nom unique lorsque
de nouvelles instances sont ajoutées à l'espace de travail. Ceci s'applique aux
noeuds <code>Debug</code>, <code>Function</code> et <code>Link</code>.</p>
<p>Une nouvelle action a également été ajoutée pour générer des noms par défaut pour les noeuds sélectionnés :</p>
<ul>
<li><b><code>Générer des noms de noeud</code></b></li>
</ul>
<p>Les actions sont accessibles à partir de la liste d'actions dans le menu principal.</p>`
} }
}, },
{ {
title: { title: {
"en-US": "Node Updates", "en-US": "Node Updates",
"ja": "ノードの更新" "ja": "ノードの更新",
"fr": "Mises à jour des noeuds"
}, },
// image: "images/", // image: "images/",
description: { description: {
@ -148,6 +185,11 @@ export default {
<li>Debugードは受信したメッセージの数をカウントするよう設定できるようになりました</li> <li>Debugードは受信したメッセージの数をカウントするよう設定できるようになりました</li>
<li>Link Callードはメッセージのプロパティによって呼び出し対象のlinkを動的に指定できるようになりました</li> <li>Link Callードはメッセージのプロパティによって呼び出し対象のlinkを動的に指定できるようになりました</li>
<li>HTTP RequestードはHTTPヘッダを事前設定できるようになりました</li> <li>HTTP RequestードはHTTPヘッダを事前設定できるようになりました</li>
</ul>`,
"fr": `<ul>
<li>Le noeud de débogage peut être configuré pour compter les messages qu'il reçoit</li>
<li>Le noeud Link Call peut utiliser une propriété de message pour cibler dynamiquement le lien qu'il doit appeler</li>
<li>Le noeud de requête HTTP peut être préconfiguré avec des en-têtes HTTP</li>
</ul>` </ul>`
} }
} }

View File

@ -3,12 +3,14 @@ export default {
{ {
title: { title: {
'en-US': 'Create your first flow', 'en-US': 'Create your first flow',
'ja': 'はじめてのフローを作成' 'ja': 'はじめてのフローを作成',
'fr': "Créer votre premier flux"
}, },
width: 400, width: 400,
description: { description: {
'en-US': 'This tutorial will guide you through creating your first flow', 'en-US': 'This tutorial will guide you through creating your first flow',
'ja': '本チュートリアルでは、はじめてのフローを作成する方法について説明します。' 'ja': '本チュートリアルでは、はじめてのフローを作成する方法について説明します。',
'fr': "Ce didacticiel vous guidera dans la création de votre premier flux"
}, },
nextButton: 'start' nextButton: 'start'
}, },
@ -16,7 +18,8 @@ export default {
element: "#red-ui-workspace .red-ui-tab-button.red-ui-tabs-add", element: "#red-ui-workspace .red-ui-tab-button.red-ui-tabs-add",
description: { description: {
'en-US': 'To add a new tab, click the <i class="fa fa-plus"></i> button', 'en-US': 'To add a new tab, click the <i class="fa fa-plus"></i> button',
'ja': '新しいタブを追加するため、 <i class="fa fa-plus"></i> ボタンをクリックします。' 'ja': '新しいタブを追加するため、 <i class="fa fa-plus"></i> ボタンをクリックします。',
'fr': 'Pour ajouter un nouvel onglet, cliquez sur le bouton <i class="fa fa-plus"></i>'
}, },
wait: { wait: {
type: "dom-event", type: "dom-event",
@ -29,7 +32,8 @@ export default {
direction: 'right', direction: 'right',
description: { description: {
'en-US': 'The palette lists all of the nodes available to use. Drag a new Inject node into the workspace.', 'en-US': 'The palette lists all of the nodes available to use. Drag a new Inject node into the workspace.',
'ja': 'パレットには、利用できる全てのードが一覧表示されます。injectードをワークスペースにドラッグします。' 'ja': 'パレットには、利用できる全てのードが一覧表示されます。injectードをワークスペースにドラッグします。',
'fr': "La palette répertorie tous les noeuds disponibles à utiliser. Faites glisser un nouveau noeud Inject dans l'espace de travail."
}, },
fallback: 'inset-bottom-right', fallback: 'inset-bottom-right',
wait: { wait: {
@ -52,7 +56,8 @@ export default {
direction: 'right', direction: 'right',
description: { description: {
'en-US': 'Next, drag a new Debug node into the workspace.', 'en-US': 'Next, drag a new Debug node into the workspace.',
'ja': '次に、debugードをワークスペースにドラッグします。' 'ja': '次に、debugードをワークスペースにドラッグします。',
'fr': "Ensuite, faites glisser un nouveau noeud Debug dans l'espace de travail."
}, },
fallback: 'inset-bottom-right', fallback: 'inset-bottom-right',
wait: { wait: {
@ -74,7 +79,8 @@ export default {
element: function() { return $("#"+this.injectNode.id+" .red-ui-flow-port") }, element: function() { return $("#"+this.injectNode.id+" .red-ui-flow-port") },
description: { description: {
'en-US': 'Add a wire from the output of the Inject node to the input of the Debug node', 'en-US': 'Add a wire from the output of the Inject node to the input of the Debug node',
'ja': 'injectードの出力から、debugードの入力へワイヤーで接続します。' 'ja': 'injectードの出力から、debugードの入力へワイヤーで接続します。',
'fr': "Ajoutez un fil de la sortie du noeud Inject à l'entrée du noeud Debug"
}, },
fallback: 'inset-bottom-right', fallback: 'inset-bottom-right',
wait: { wait: {
@ -89,7 +95,8 @@ export default {
element: "#red-ui-header-button-deploy", element: "#red-ui-header-button-deploy",
description: { description: {
'en-US': 'Deploy your changes so the flow is active in the runtime', 'en-US': 'Deploy your changes so the flow is active in the runtime',
'ja': 'フローをランタイムで実行させるため、変更をデプロイします。' 'ja': 'フローをランタイムで実行させるため、変更をデプロイします。',
'fr': "Déployez vos modifications afin que le flux soit actif dans le runtime"
}, },
width: 200, width: 200,
wait: { wait: {

View File

@ -5,17 +5,20 @@ export default {
titleIcon: "fa fa-map-o", titleIcon: "fa fa-map-o",
title: { title: {
"en-US": "Welcome to Node-RED 3.1 Beta 3!", "en-US": "Welcome to Node-RED 3.1 Beta 3!",
"ja": "Node-RED 3.1 ベータ3へようこそ!" "ja": "Node-RED 3.1 ベータ3へようこそ!",
"fr": "Bienvenue dans Node-RED 3.1 Bêta 3 !"
}, },
description: { description: {
"en-US": "<p>This is the third beta release for 3.1.0. This is mostly a bug fix release, so you can skip this tour if you've tried the other betas.</p><p>If not, stick around to see what's new in Node-RED 3.1.</p>", "en-US": "<p>This is the third beta release for 3.1.0. This is mostly a bug fix release, so you can skip this tour if you've tried the other betas.</p><p>If not, stick around to see what's new in Node-RED 3.1.</p>",
"ja": "<p>これは3.1.0の3回目のベータリリースです。不具合修正のリリースのため、もし他のベータ版を試したことがある場合は、このツアーを読み飛ばしてもかまいません。</p><p>そうでない場合は、Node-RED 3.1の新機能を確認してください。</p>" "ja": "<p>これは3.1.0の3回目のベータリリースです。不具合修正のリリースのため、もし他のベータ版を試したことがある場合は、このツアーを読み飛ばしてもかまいません。</p><p>そうでない場合は、Node-RED 3.1の新機能を確認してください。</p>",
"fr": "<p>Il s'agit de la troisième bêta de la version 3.1.0. Cette version apporte principalement la correction de bugs, vous pouvez donc ignorer cette visite guidée si vous avez essayé les autres versions bêta.</p><p>Si ce n'est pas le cas, restez dans les parages pour voir les nouveautés de Node-RED 3.1.</p>"
} }
}, },
{ {
title: { title: {
"en-US": "New ways to work with groups", "en-US": "New ways to work with groups",
"ja": "グループの新たな操作方法" "ja": "グループの新たな操作方法",
"fr": "De nouvelles façons de travailler avec les groupes"
}, },
description: { description: {
"en-US": `<p>We have changed how you interact with groups in the editor.</p> "en-US": `<p>We have changed how you interact with groups in the editor.</p>
@ -31,28 +34,41 @@ export default {
<li>前面へ移動背面へ移動の動作を用いて複数のグループの表示順序を変えることができます</li> <li>前面へ移動背面へ移動の動作を用いて複数のグループの表示順序を変えることができます</li>
<li>グループ内へ一度に複数のノードをドラッグできるようになりました</li> <li>グループ内へ一度に複数のノードをドラッグできるようになりました</li>
<li><code>Alt</code> ** </li> <li><code>Alt</code> ** </li>
</ul>`,
"fr": `<p>Nous avons modifié la façon dont vous interagissez avec les groupes dans l'éditeur.</p>
<ul>
<li>Ils ne gênent plus lorsque vous cliquez sur un noeud</li>
<li>Ils peuvent être réorganisés à l'aide des actions Avancer et Reculer</li>
<li>Plusieurs noeuds peuvent être glissés dans un groupe en une seule fois</li>
<li>Maintenir <code>Alt</code> lors du déplacement d'un noeud le *supprimera* de son groupe</li>
</ul>` </ul>`
} }
}, },
{ {
title: { title: {
"en-US": "Change notification on tabs", "en-US": "Change notification on tabs",
"ja": "タブ上の変更通知" "ja": "タブ上の変更通知",
"fr": "Notification de changement sur les onglets"
}, },
image: 'images/tab-changes.png', image: 'images/tab-changes.png',
description: { description: {
"en-US": `<p>When a tab contains undeployed changes it now shows the "en-US": `<p>When a tab contains undeployed changes it now shows the
same style of change icon used by nodes.</p> same style of change icon used by nodes.</p>
<p>This will make it much easier to track down changes when you're <p>This will make it much easier to track down changes when you're
working across multiple flows.</p>`, working across multiple flows.</p>`,
"ja": `<p>タブ内にデプロイされていない変更が存在する時は、ノードと同じスタイルで変更の印が表示されるようになりました。</p> "ja": `<p>タブ内にデプロイされていない変更が存在する時は、ノードと同じスタイルで変更の印が表示されるようになりました。</p>
<p>これによって複数のフローを編集している時に変更を見つけるのが簡単になりました</p>` <p>これによって複数のフローを編集している時に変更を見つけるのが簡単になりました</p>`,
"fr": `<p>Lorsqu'un onglet contient des modifications non déployées, il affiche désormais le
même style d'icône de changement utilisé par les noeuds.</p>
<p>Cela facilitera grandement le suivi des modifications lorsque vous
travaillez sur plusieurs flux.</p>`
} }
}, },
{ {
title: { title: {
"en-US": "A bigger canvas to work with", "en-US": "A bigger canvas to work with",
"ja": "より広くなった作業キャンバス" "ja": "より広くなった作業キャンバス",
"fr": "Un canevas plus grand pour travailler"
}, },
description: { description: {
"en-US": `<p>The default canvas size has been increased so you can fit more "en-US": `<p>The default canvas size has been increased so you can fit more
@ -60,13 +76,18 @@ export default {
<p>We still recommend using tools such as subflows and Link Nodes to help <p>We still recommend using tools such as subflows and Link Nodes to help
keep things organised, but now you have more room to work in.</p>`, keep things organised, but now you have more room to work in.</p>`,
"ja": `<p>標準のキャンバスが広くなったため、1つのフローに沢山のものを含めることができるようになりました。</p> "ja": `<p>標準のキャンバスが広くなったため、1つのフローに沢山のものを含めることができるようになりました。</p>
<p>引き続きサブフローやリンクノードなどの方法を用いて整理することをお勧めしますが作業できる場所が増えました</p>` <p>引き続きサブフローやリンクノードなどの方法を用いて整理することをお勧めしますが作業できる場所が増えました</p>`,
"fr": `<p>La taille par défaut du canevas a été augmentée pour que vous puissiez en mettre plus
sur un seul flux.</p>
<p>Nous recommandons toujours d'utiliser des outils tels que les sous-flux et les noeuds de lien pour vous aider
à garder les choses organisées, mais vous avez maintenant plus d'espace pour travailler.</p>`
} }
}, },
{ {
title: { title: {
"en-US": "Finding help", "en-US": "Finding help",
"ja": "ヘルプを見つける" "ja": "ヘルプを見つける",
"fr": "Trouver de l'aide"
}, },
image: 'images/node-help.png', image: 'images/node-help.png',
description: { description: {
@ -74,23 +95,29 @@ export default {
in the footer.</p> in the footer.</p>
<p>Clicking it will open up the Help sidebar showing the help for that node.</p>`, <p>Clicking it will open up the Help sidebar showing the help for that node.</p>`,
"ja": `<p>全てのノードの編集ダイアログの下に、ノードのヘルプへのリンクが追加されました。</p> "ja": `<p>全てのノードの編集ダイアログの下に、ノードのヘルプへのリンクが追加されました。</p>
<p>これをクリックするとノードのヘルプサイドバーが表示されます</p>` <p>これをクリックするとノードのヘルプサイドバーが表示されます</p>`,
"fr": `<p>Toutes les boîtes de dialogue d'édition de noeud incluent désormais un lien vers l'aide de ce noeud
dans le pied de page.</p>
<p>Cliquer dessus ouvrira la barre latérale d'aide affichant l'aide pour ce noeud.</p>`
} }
}, },
{ {
title: { title: {
"en-US": "And lots more...", "en-US": "And lots more...",
"ja": "そしてさらに沢山あります..." "ja": "そしてさらに沢山あります...",
"fr": "Et plus encore..."
}, },
description: { description: {
"en-US": `<p>Of course we have everything from 3.1.0-beta.1 as well....</p>`, "en-US": `<p>Of course we have everything from 3.1.0-beta.1 as well....</p>`,
"ja": `<p>もちろん3.1.0 ベータ1の全ての機能があります....</p>` "ja": `<p>もちろん3.1.0 ベータ1の全ての機能があります....</p>`,
"fr": `<p>Bien sûr, nous avons également tout ce qui concerne la version 3.1.0-beta.1...</p>`
} }
}, },
{ {
title: { title: {
"en-US": "Improved Context Menu", "en-US": "Improved Context Menu",
"ja": "コンテキストメニューの改善" "ja": "コンテキストメニューの改善",
"fr": "Menu contextuel amélioré"
}, },
image: 'images/context-menu.png', image: 'images/context-menu.png',
description: { description: {
@ -102,13 +129,17 @@ export default {
with your flows much easier.</p>`, with your flows much easier.</p>`,
"ja": `<p>より多くの組み込み動作を利用できるように、エディタのコンテキストメニューが拡張されました。</p> "ja": `<p>より多くの組み込み動作を利用できるように、エディタのコンテキストメニューが拡張されました。</p>
<p>ノードの追加グループの操作その他の便利なツールをクリックするだけで実行できるようになりました</p> <p>ノードの追加グループの操作その他の便利なツールをクリックするだけで実行できるようになりました</p>
<p>フローのタブバーにはフローの操作をより簡単にする独自のコンテキストメニューもあります</p>` <p>フローのタブバーにはフローの操作をより簡単にする独自のコンテキストメニューもあります</p>`,
"fr": `<p>Le menu contextuel de l'éditeur a été étendu pour faire beaucoup plus d'actions intégrées disponibles.</p>
<p>Ajouter des noeuds, travailler avec des groupes et beaucoup d'autres outils utiles sont désormais à portée de clic.</p>
<p>La barre d'onglets de flux possède également son propre menu contextuel pour faciliter l'utilisation de vos flux.</p>`
} }
}, },
{ {
title: { title: {
"en-US": "Hiding Flows", "en-US": "Hiding Flows",
"ja": "フローを非表示" "ja": "フローを非表示",
"fr": "Masquage de flux"
}, },
image: 'images/hiding-flows.png', image: 'images/hiding-flows.png',
description: { description: {
@ -116,13 +147,17 @@ export default {
<p>The 'hide' button in previous releases has been removed from the tabs <p>The 'hide' button in previous releases has been removed from the tabs
as they were being clicked accidentally too often.</p>`, as they were being clicked accidentally too often.</p>`,
"ja": `<p>フローを非表示にする機能は、フローのコンテキストメニューから実行するようになりました。</p> "ja": `<p>フローを非表示にする機能は、フローのコンテキストメニューから実行するようになりました。</p>
<p>これまでのリリースでタブに存在していた非表示ボタンはよく誤ってクリックされていたため削除されました</p>` <p>これまでのリリースでタブに存在していた非表示ボタンはよく誤ってクリックされていたため削除されました</p>`,
"fr": `<p>Le masquage des flux s'effectue désormais via le menu contextuel du flux.</p>
<p>Le bouton "Masquer" des versions précédentes a été supprimé des onglets
car il était cliqué accidentellement trop souvent.</p>`
}, },
}, },
{ {
title: { title: {
"en-US": "Locking Flows", "en-US": "Locking Flows",
"ja": "フローを固定" "ja": "フローを固定",
"fr": "Verrouillage de flux"
}, },
image: 'images/locking-flows.png', image: 'images/locking-flows.png',
description: { description: {
@ -132,13 +167,18 @@ export default {
as well as in the Info sidebar explorer.</p>`, as well as in the Info sidebar explorer.</p>`,
"ja": `<p>誤ってフローに変更が加えられてしまうのを防ぐために、フローを固定できるようになりました。</p> "ja": `<p>誤ってフローに変更が加えられてしまうのを防ぐために、フローを固定できるようになりました。</p>
<p>固定されている時はノードを修正することはできません</p> <p>固定されている時はノードを修正することはできません</p>
<p>フローのコンテキストメニューと情報サイドバーのエクスプローラにはフローの固定や解除をするためのオプションが用意されています</p>` <p>フローのコンテキストメニューと情報サイドバーのエクスプローラにはフローの固定や解除をするためのオプションが用意されています</p>`,
"fr": `<p>Les flux peuvent désormais être verrouillés pour éviter toute modification accidentelle.</p>
<p>Lorsqu'il est verrouillé, vous ne pouvez en aucun cas modifier les noeuds.</p>
<p>Le menu contextuel du flux fournit les options pour verrouiller et déverrouiller les flux,
ainsi que dans l'explorateur de la barre latérale d'informations.</p>`
}, },
}, },
{ {
title: { title: {
"en-US": "Adding Images to node/flow descriptions", "en-US": "Adding Images to node/flow descriptions",
"ja": "ノードやフローの説明へ画像を追加" "ja": "ノードやフローの説明へ画像を追加",
"fr": "Ajout d'images aux descriptions de noeud/flux"
}, },
// image: 'images/debug-path-tooltip.png', // image: 'images/debug-path-tooltip.png',
description: { description: {
@ -147,44 +187,56 @@ export default {
<p>When the description is shown in the Info sidebar, the image will be displayed.</p>`, <p>When the description is shown in the Info sidebar, the image will be displayed.</p>`,
"ja": `<p>ノードまたはフローの説明に、画像を追加できるようになりました。</p> "ja": `<p>ノードまたはフローの説明に、画像を追加できるようになりました。</p>
<p>画像をテキストエディタにドラッグするだけで行内に埋め込まれます</p> <p>画像をテキストエディタにドラッグするだけで行内に埋め込まれます</p>
<p>情報サイドバーの説明を開くとその画像が表示されます</p>` <p>情報サイドバーの説明を開くとその画像が表示されます</p>`,
"fr": `<p>Vous pouvez désormais ajouter des images à la description d'un noeud ou d'un flux.</p>
<p>Faites simplement glisser l'image dans l'éditeur de texte et elle sera ajoutée en ligne.</p>
<p>Lorsque la description s'affiche dans la barre latérale d'informations, l'image s'affiche.</p>`
}, },
}, },
{ {
title: { title: {
"en-US": "Adding Mermaid Diagrams", "en-US": "Adding Mermaid Diagrams",
"ja": "Mermaid図を追加" "ja": "Mermaid図を追加",
"fr": "Ajout de diagrammes Mermaid"
}, },
image: 'images/mermaid.png', image: 'images/mermaid.png',
description: { description: {
"en-US": `<p>You can also add <a href="https://github.com/mermaid-js/mermaid">Mermaid</a> diagrams directly into your node or flow descriptions.</p> "en-US": `<p>You can also add <a href="https://github.com/mermaid-js/mermaid">Mermaid</a> diagrams directly into your node or flow descriptions.</p>
<p>This gives you much richer options for documenting your flows.</p>`, <p>This gives you much richer options for documenting your flows.</p>`,
"ja": `<p>ノードやフローの説明に、<a href="https://github.com/mermaid-js/mermaid">Mermaid</a>図を直接追加することもできます。</p> "ja": `<p>ノードやフローの説明に、<a href="https://github.com/mermaid-js/mermaid">Mermaid</a>図を直接追加することもできます。</p>
<p>これによってフローを説明する文書作成の選択肢がより多くなります</p>` <p>これによってフローを説明する文書作成の選択肢がより多くなります</p>`,
"fr": `<p>Vous pouvez également ajouter des diagrammes <a href="https://github.com/mermaid-js/mermaid">Mermaid</a> directement dans vos descriptions de noeud ou de flux.</p>
<p>Cela vous offre des options beaucoup plus riches pour documenter vos flux.</p>`
}, },
}, },
{ {
title: { title: {
"en-US": "Managing Global Environment Variables", "en-US": "Managing Global Environment Variables",
"ja": "グローバル環境変数の管理" "ja": "グローバル環境変数の管理",
"fr": "Gestion des variables d'environnement globales"
}, },
image: 'images/global-env-vars.png', image: 'images/global-env-vars.png',
description: { description: {
"en-US": `<p>You can set environment variables that apply to all nodes and flows in the new "en-US": `<p>You can set environment variables that apply to all nodes and flows in the new
'Global Environment Variables' section of User Settings.</p>`, 'Global Environment Variables' section of User Settings.</p>`,
"ja": `<p>ユーザ設定に新しく追加された「大域環境変数」のセクションで、全てのノードとフローに適用される環境変数を登録できます。</p>` "ja": `<p>ユーザ設定に新しく追加された「大域環境変数」のセクションで、全てのノードとフローに適用される環境変数を登録できます。</p>`,
"fr": `<p>Vous pouvez définir des variables d'environnement qui s'appliquent à tous les noeuds et flux dans la nouvelle
section "Global Environment Variables" des paramètres utilisateur.</p>`
}, },
}, },
{ {
title: { title: {
"en-US": "Node Updates", "en-US": "Node Updates",
"ja": "ノードの更新" "ja": "ノードの更新",
"fr": "Mises à jour des noeuds"
}, },
// image: "images/", // image: "images/",
description: { description: {
"en-US": `<p>The core nodes have received lots of minor fixes, documentation updates and "en-US": `<p>The core nodes have received lots of minor fixes, documentation updates and
small enhancements. Check the full changelog in the Help sidebar for a full list.</p>`, small enhancements. Check the full changelog in the Help sidebar for a full list.</p>`,
"ja": `<p>コアノードにマイナーな修正、ドキュメント更新、小規模な拡張が数多く追加されています。全ての一覧は、ヘルプサイドバーの全ての更新履歴を確認してください。</p>` "ja": `<p>コアノードにマイナーな修正、ドキュメント更新、小規模な拡張が数多く追加されています。全ての一覧は、ヘルプサイドバーの全ての更新履歴を確認してください。</p>`,
"fr": `<p>Les noeuds principaux ont reçu de nombreux correctifs mineurs, mises à jour de la documentation et
petites améliorations. Consulter le journal des modifications complet dans la barre latérale d'aide.</p>`
} }
} }
] ]

View File

@ -281,4 +281,21 @@ declare class env {
* ```const flowName = env.get("NR_FLOW_NAME");``` * ```const flowName = env.get("NR_FLOW_NAME");```
*/ */
static get(name:string) :any; static get(name:string) :any;
/**
* Get an environment variable value (asynchronous).
*
* Predefined node-red variables...
* * `NR_NODE_ID` - the ID of the node
* * `NR_NODE_NAME` - the Name of the node
* * `NR_NODE_PATH` - the Path of the node
* * `NR_GROUP_ID` - the ID of the containing group
* * `NR_GROUP_NAME` - the Name of the containing group
* * `NR_FLOW_ID` - the ID of the flow the node is on
* * `NR_FLOW_NAME` - the Name of the flow the node is on
* @param name Name of the environment variable to get
* @param callback Callback function (`(err,value) => {}`)
* @example
* ```const flowName = env.get("NR_FLOW_NAME");```
*/
static get(name:string, callback: Function) :void;
} }

View File

@ -41,7 +41,9 @@
if (this.name) { if (this.name) {
return this.name; return this.name;
} }
if (this.scope) { if (this.scope === "group") {
return this._("catch.catchGroup");
} else if (Array.isArray(this.scope)) {
return this._("catch.catchNodes",{number:this.scope.length}); return this._("catch.catchNodes",{number:this.scope.length});
} }
return this.uncaught?this._("catch.catchUncaught"):this._("catch.catch") return this.uncaught?this._("catch.catchUncaught"):this._("catch.catch")

View File

@ -33,7 +33,15 @@
outputs:1, outputs:1,
icon: "status.svg", icon: "status.svg",
label: function() { label: function() {
return this.name||(this.scope?this._("status.statusNodes",{number:this.scope.length}):this._("status.status")); if (this.name) {
return this.name;
}
if (this.scope === "group") {
return this._("status.statusGroup");
} else if (Array.isArray(this.scope)) {
return this._("status.statusNodes",{number:this.scope.length});
}
return this._("status.status")
}, },
labelStyle: function() { labelStyle: function() {
return this.name?"node_label_italic":""; return this.name?"node_label_italic":"";

View File

@ -82,6 +82,11 @@
<input id="node-input-outputs" style="width: 60px;" value="1"> <input id="node-input-outputs" style="width: 60px;" value="1">
</div> </div>
<div class="form-row">
<label for="node-input-timeout"><i class="fa fa-clock"></i> <span data-i18n="function.label.timeout"></span></label>
<input id="node-input-timeout" style="width: 60px;" data-i18n="[placeholder]join.seconds">
</div>
<div class="form-row node-input-libs-row hide" style="margin-bottom: 0px;"> <div class="form-row node-input-libs-row hide" style="margin-bottom: 0px;">
<label><i class="fa fa-cubes"></i> <span data-i18n="function.label.modules"></span></label> <label><i class="fa fa-cubes"></i> <span data-i18n="function.label.modules"></span></label>
</div> </div>
@ -360,6 +365,7 @@
name: {value:"_DEFAULT_"}, name: {value:"_DEFAULT_"},
func: {value:"\nreturn msg;"}, func: {value:"\nreturn msg;"},
outputs: {value:1}, outputs: {value:1},
timeout:{value:0},
noerr: {value:0,required:true, noerr: {value:0,required:true,
validate: function(v, opt) { validate: function(v, opt) {
if (!v) { if (!v) {
@ -464,6 +470,26 @@
} }
}); });
// 4294967 is max in node.js timeout.
$( "#node-input-timeout" ).spinner({
min: 0,
max: 4294967,
change: function(event, ui) {
var value = this.value;
if(value == ""){
value = 0;
}
else
{
value = parseInt(value);
}
value = isNaN(value) ? 1 : value;
value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
if (value !== this.value) { $(this).spinner("value", value); }
}
});
var buildEditor = function(id, stateId, focus, value, defaultValue, extraLibs, offset) { var buildEditor = function(id, stateId, focus, value, defaultValue, extraLibs, offset) {
var editor = RED.editor.createEditor({ var editor = RED.editor.createEditor({
id: id, id: id,
@ -503,7 +529,7 @@
editor:this.editor, // the field name the main text body goes to editor:this.editor, // the field name the main text body goes to
mode:"ace/mode/nrjavascript", mode:"ace/mode/nrjavascript",
fields:[ fields:[
'name', 'outputs', 'name', 'outputs', 'timeout',
{ {
name: 'initialize', name: 'initialize',
get: function() { get: function() {

View File

@ -96,6 +96,13 @@ module.exports = function(RED) {
node.name = n.name; node.name = n.name;
node.func = n.func; node.func = n.func;
node.outputs = n.outputs; node.outputs = n.outputs;
node.timeout = n.timeout*1000;
if(node.timeout>0){
node.timeoutOptions = {
timeout:node.timeout,
breakOnSigint:true
}
}
node.ini = n.initialize ? n.initialize.trim() : ""; node.ini = n.initialize ? n.initialize.trim() : "";
node.fin = n.finalize ? n.finalize.trim() : ""; node.fin = n.finalize ? n.finalize.trim() : "";
node.libs = n.libs || []; node.libs = n.libs || [];
@ -235,8 +242,8 @@ module.exports = function(RED) {
} }
}, },
env: { env: {
get: function(envVar) { get: function(envVar, callback) {
return RED.util.getSetting(node, envVar); return RED.util.getSetting(node, envVar, node._flow, callback);
} }
}, },
setTimeout: function () { setTimeout: function () {
@ -362,6 +369,10 @@ module.exports = function(RED) {
})(__initSend__);`; })(__initSend__);`;
iniOpt = createVMOpt(node, " setup"); iniOpt = createVMOpt(node, " setup");
iniScript = new vm.Script(iniText, iniOpt); iniScript = new vm.Script(iniText, iniOpt);
if(node.timeout>0){
iniOpt.timeout = node.timeout;
iniOpt.breakOnSigint = true;
}
} }
node.script = vm.createScript(functionText, createVMOpt(node, "")); node.script = vm.createScript(functionText, createVMOpt(node, ""));
if (node.fin && (node.fin !== "")) { if (node.fin && (node.fin !== "")) {
@ -385,6 +396,10 @@ module.exports = function(RED) {
})();`; })();`;
finOpt = createVMOpt(node, " cleanup"); finOpt = createVMOpt(node, " cleanup");
finScript = new vm.Script(finText, finOpt); finScript = new vm.Script(finText, finOpt);
if(node.timeout>0){
finOpt.timeout = node.timeout;
finOpt.breakOnSigint = true;
}
} }
var promise = Promise.resolve(); var promise = Promise.resolve();
if (iniScript) { if (iniScript) {
@ -396,9 +411,12 @@ module.exports = function(RED) {
var start = process.hrtime(); var start = process.hrtime();
context.msg = msg; context.msg = msg;
context.__send__ = send; context.__send__ = send;
context.__done__ = done; context.__done__ = done;
var opts = {};
node.script.runInContext(context); if (node.timeout>0){
opts = node.timeoutOptions;
}
node.script.runInContext(context,opts);
context.results.then(function(results) { context.results.then(function(results) {
sendResults(node,send,msg._msgid,results,false); sendResults(node,send,msg._msgid,results,false);
if (handleNodeDoneCall) { if (handleNodeDoneCall) {

View File

@ -229,6 +229,7 @@ module.exports = function(RED) {
node.on("input", function(msg, send, done) { node.on("input", function(msg, send, done) {
if (!node.drop) { if (!node.drop) {
var m = RED.util.cloneMessage(msg); var m = RED.util.cloneMessage(msg);
delete m.flush;
if (Object.keys(m).length > 1) { if (Object.keys(m).length > 1) {
if (node.intervalID !== -1) { if (node.intervalID !== -1) {
if (node.allowrate && m.hasOwnProperty("rate") && !isNaN(parseFloat(m.rate)) && node.rate !== m.rate) { if (node.allowrate && m.hasOwnProperty("rate") && !isNaN(parseFloat(m.rate)) && node.rate !== m.rate) {

View File

@ -629,6 +629,9 @@ module.exports = function(RED) {
joinChar = node.joiner; joinChar = node.joiner;
if (n.count === "" && msg.hasOwnProperty('parts')) { if (n.count === "" && msg.hasOwnProperty('parts')) {
targetCount = msg.parts.count || 0; targetCount = msg.parts.count || 0;
if (msg.parts.hasOwnProperty('id')) {
partId = msg.parts.id;
}
} }
if (node.build === 'object') { if (node.build === 'object') {
propertyKey = RED.util.getMessageProperty(msg,node.key); propertyKey = RED.util.getMessageProperty(msg,node.key);

View File

@ -216,7 +216,8 @@
"initialize": "Start", "initialize": "Start",
"finalize": "Stopp", "finalize": "Stopp",
"outputs": "Ausgänge", "outputs": "Ausgänge",
"modules": "Module" "modules": "Module",
"timeout": "Timeout"
}, },
"text": { "text": {
"initialize": "// Der Code hier wird ausgeführt,\n// wenn der Node gestartet wird\n", "initialize": "// Der Code hier wird ausgeführt,\n// wenn der Node gestartet wird\n",

View File

@ -94,6 +94,7 @@
}, },
"catch": { "catch": {
"catch": "catch: all", "catch": "catch: all",
"catchGroup": "catch: group",
"catchNodes": "catch: __number__", "catchNodes": "catch: __number__",
"catchUncaught": "catch: uncaught", "catchUncaught": "catch: uncaught",
"label": { "label": {
@ -109,6 +110,7 @@
}, },
"status": { "status": {
"status": "status: all", "status": "status: all",
"statusGroup": "status: group",
"statusNodes": "status: __number__", "statusNodes": "status: __number__",
"label": { "label": {
"source": "Report status from", "source": "Report status from",
@ -250,7 +252,8 @@
"initialize": "On Start", "initialize": "On Start",
"finalize": "On Stop", "finalize": "On Stop",
"outputs": "Outputs", "outputs": "Outputs",
"modules": "Modules" "modules": "Modules",
"timeout": "Timeout"
}, },
"text": { "text": {
"initialize": "// Code added here will be run once\n// whenever the node is started.\n", "initialize": "// Code added here will be run once\n// whenever the node is started.\n",

View File

@ -103,6 +103,7 @@
}, },
"scope": { "scope": {
"all": "tous les noeuds", "all": "tous les noeuds",
"group": "dans le même groupe",
"selected": "noeuds sélectionnés" "selected": "noeuds sélectionnés"
} }
}, },
@ -115,6 +116,7 @@
}, },
"scope": { "scope": {
"all": "tous les noeuds", "all": "tous les noeuds",
"group": "dans le même groupe",
"selected": "noeuds sélectionnés" "selected": "noeuds sélectionnés"
} }
}, },
@ -414,6 +416,7 @@
"port": "Port", "port": "Port",
"keepalive": "Rester en vie", "keepalive": "Rester en vie",
"cleansession": "Utiliser une session propre", "cleansession": "Utiliser une session propre",
"autoUnsubscribe": "Se désabonner automatiquement lors de la déconnexion",
"cleanstart": "Utiliser un démarrage propre", "cleanstart": "Utiliser un démarrage propre",
"use-tls": "Utiliser TLS", "use-tls": "Utiliser TLS",
"tls-config": "Configuration TLS", "tls-config": "Configuration TLS",

View File

@ -103,6 +103,7 @@
}, },
"scope": { "scope": {
"all": "全てのノード", "all": "全てのノード",
"group": "同一グループ内",
"selected": "選択したノード" "selected": "選択したノード"
} }
}, },
@ -115,6 +116,7 @@
}, },
"scope": { "scope": {
"all": "全てのノード", "all": "全てのノード",
"group": "同一グループ内",
"selected": "選択したノード" "selected": "選択したノード"
} }
}, },

View File

@ -212,7 +212,8 @@
"function": "Функция", "function": "Функция",
"initialize": "Настройка", "initialize": "Настройка",
"finalize": "Закрытие", "finalize": "Закрытие",
"outputs": "Выходы" "outputs": "Выходы",
"timeout":"Время ожидания"
}, },
"text": { "text": {
"initialize": "// Добавленный здесь код будет исполняться\n// однократно при развертывании узла.\n", "initialize": "// Добавленный здесь код будет исполняться\n// однократно при развертывании узла.\n",

View File

@ -416,23 +416,50 @@ class Flow {
return this.activeNodes; return this.activeNodes;
} }
/*!
* Get value of environment variable defined in group node. /**
* @param {String} group - group node * Group callback signature
* @param {String} name - name of variable *
* @return {Object} object containing the value in val property or null if not defined * @callback GroupEnvCallback
* @param {Error} err The error object (or null)
* @param {[result: {val:Any}, name: String]} result The result of the callback
* @returns {void}
*/ */
getGroupEnvSetting(node, group, name) {
/**
* @function getGroupEnvSetting
* Get a group setting value synchronously.
* This currently automatically defers to the parent
* @overload
* @param {Object} node
* @param {Object} group
* @param {String} name
* @returns {Any}
*
* Get a group setting value asynchronously.
* @overload
* @param {Object} node
* @param {Object} group
* @param {String} name
* @param {GroupEnvCallback} callback
* @returns {void}
*/
getGroupEnvSetting(node, group, name, callback) {
/** @type {GroupEnvCallback} */
const returnOrCallback = (err, [result, newName]) => {
if (callback) {
callback(err, [result, newName]);
return
}
return [result, newName];
}
if (group) { if (group) {
if (name === "NR_GROUP_NAME") { if (name === "NR_GROUP_NAME") {
return [{ return returnOrCallback(null, [{ val: group.name }, null]);
val: group.name
}, null];
} }
if (name === "NR_GROUP_ID") { if (name === "NR_GROUP_ID") {
return [{ return returnOrCallback(null, [{ val: group.id }, null]);
val: group.id
}, null];
} }
if (group.credentials === undefined) { if (group.credentials === undefined) {
@ -457,33 +484,32 @@ class Flow {
if (env) { if (env) {
let value = env.value; let value = env.value;
const type = env.type; const type = env.type;
if ((type !== "env") || if ((type !== "env") || (value !== name)) {
(value !== name)) {
if (type === "env") { if (type === "env") {
value = value.replace(new RegExp("\\${"+name+"}","g"),"${$parent."+name+"}"); value = value.replace(new RegExp("\\${"+name+"}","g"),"${$parent."+name+"}");
} } else if (type === "bool") {
if (type === "bool") { const val = ((value === "true") || (value === true));
const val return returnOrCallback(null, [{ val: val }, null])
= ((value === "true") ||
(value === true));
return [{
val: val
}, null];
} }
if (type === "cred") { if (type === "cred") {
return [{ return returnOrCallback(null, [{ val: value }, null])
val: value
}, null];
} }
try { try {
var val = redUtil.evaluateNodeProperty(value, type, node, null, null); if (!callback) {
return [{ var val = redUtil.evaluateNodeProperty(value, type, node, null, null);
val: val return [{ val: val }, null];
}, null]; } else {
redUtil.evaluateNodeProperty(value, type, node, null, (err, value) => {
return returnOrCallback(err, [{ val: value }, null])
});
return
}
} }
catch (e) { catch (e) {
this.error(e); if (!callback) {
return [null, null]; this.error(e);
}
return returnOrCallback(e, null);
} }
} }
} }
@ -494,27 +520,47 @@ class Flow {
} }
if (group.g) { if (group.g) {
const parent = this.getGroupNode(group.g); const parent = this.getGroupNode(group.g);
return this.getGroupEnvSetting(node, parent, name); const gVal = this.getGroupEnvSetting(node, parent, name, callback);
if (callback) {
return;
}
return gVal;
} }
} }
return [null, name]; return returnOrCallback(null, [null, name]);
} }
/**
* Settings callback signature
*
* @callback SettingsCallback
* @param {Error} err The error object (or null)
* @param {Any} result The result of the callback
* @returns {void}
*/
/** /**
* Get a flow setting value. This currently automatically defers to the parent * Get a flow setting value. This currently automatically defers to the parent
* flow which, as defined in ./index.js returns `process.env[key]`. * flow which, as defined in ./index.js returns `process.env[key]`.
* This lays the groundwork for Subflow to have instance-specific settings * This lays the groundwork for Subflow to have instance-specific settings
* @param {[type]} key [description] * @param {String} key The settings key
* @return {[type]} [description] * @param {SettingsCallback} callback Optional callback function
* @return {Any}
*/ */
getSetting(key) { getSetting(key, callback) {
/** @type {SettingsCallback} */
const returnOrCallback = (err, result) => {
if (callback) {
callback(err, result);
return
}
return result;
}
const flow = this.flow; const flow = this.flow;
if (key === "NR_FLOW_NAME") { if (key === "NR_FLOW_NAME") {
return flow.label; return returnOrCallback(null, flow.label);
} }
if (key === "NR_FLOW_ID") { if (key === "NR_FLOW_ID") {
return flow.id; return returnOrCallback(null, flow.id);
} }
if (flow.credentials === undefined) { if (flow.credentials === undefined) {
flow.credentials = credentials.get(flow.id) || {}; flow.credentials = credentials.get(flow.id) || {};
@ -544,15 +590,14 @@ class Flow {
} }
try { try {
if (type === "bool") { if (type === "bool") {
const val = ((value === "true") || const val = ((value === "true") || (value === true));
(value === true)); return returnOrCallback(null, val);
return val;
} }
if (type === "cred") { if (type === "cred") {
return value; return returnOrCallback(null, value);
} }
var val = redUtil.evaluateNodeProperty(value, type, null, null, null); var val = redUtil.evaluateNodeProperty(value, type, null, null, null);
return val; return returnOrCallback(null, val);
} }
catch (e) { catch (e) {
this.error(e); this.error(e);
@ -564,7 +609,11 @@ class Flow {
key = key.substring(8); key = key.substring(8);
} }
} }
return this.parent.getSetting(key); const pVal = this.parent.getSetting(key, callback);
if (callback) {
return;
}
return pVal;
} }
/** /**

View File

@ -780,7 +780,7 @@ const flowAPI = {
getNode: getNode, getNode: getNode,
handleError: () => false, handleError: () => false,
handleStatus: () => false, handleStatus: () => false,
getSetting: k => flowUtil.getEnvVar(k), getSetting: (k, callback) => flowUtil.getEnvVar(k, callback),
log: m => log.log(m) log: m => log.log(m)
} }

View File

@ -308,16 +308,34 @@ module.exports = {
runtime.settings.envVarExcludes.forEach(v => envVarExcludes[v] = true); runtime.settings.envVarExcludes.forEach(v => envVarExcludes[v] = true);
} }
}, },
getEnvVar: function(k) { /**
if (!envVarExcludes[k]) { * Get the value of an environment variable
const item = getGlobalEnv(k); * Call with a callback to get the value asynchronously
* or without to get the value synchronously
* @param {String} key The name of the environment variable
* @param {(err: Error, val: Any)} [callback] Optional callback for asynchronous call
* @returns {Any | void} The value of the environment variable or undefined if not found
*/
getEnvVar: function(key, callback) {
const returnOrCallback = function(err, val) {
if (callback) {
callback(err, val);
return;
}
return val;
}
if (!envVarExcludes[key]) {
const item = getGlobalEnv(key);
if (item) { if (item) {
const val = redUtil.evaluateNodeProperty(item.value, item.type, null, null, null); const val = redUtil.evaluateNodeProperty(item.value, item.type, null, null, callback);
if (callback) {
return;
}
return val; return val;
} }
return process.env[k]; return returnOrCallback(null, process.env[key]);
} }
return undefined; return returnOrCallback(undefined);
}, },
diffNodes: diffNodes, diffNodes: diffNodes,
mapEnvVarProperties: mapEnvVarProperties, mapEnvVarProperties: mapEnvVarProperties,

View File

@ -18,6 +18,7 @@
/** /**
* @mixin @node-red/util_util * @mixin @node-red/util_util
*/ */
/** @typedef {import('../../runtime/lib/flows/Flow.js').Flow} RuntimeLibFlowsFlow */
const clonedeep = require("lodash.clonedeep"); const clonedeep = require("lodash.clonedeep");
const jsonata = require("jsonata"); const jsonata = require("jsonata");
@ -526,37 +527,68 @@ function setObjectProperty(msg,prop,value,createMissing) {
return true; return true;
} }
/*! /**
* Get value of environment variable. * Get value of environment variable.
* @param {Node} node - accessing node * @param {Node} node - accessing node
* @param {String} name - name of variable * @param {String} name - name of variable
* @param {RuntimeLibFlowsFlow} flow_ - (optional) flow to check for setting
* @param {(err: Error, result: Any) => void} callback - (optional) called when the property is evaluated
* @return {String} value of env var * @return {String} value of env var
*/ */
function getSetting(node, name, flow_) { function getSetting(node, name, flow_, callback) {
const returnOrCallback = (err, result) => {
if (callback) {
callback(err, result);
return
}
return result;
}
if (node) { if (node) {
if (name === "NR_NODE_NAME") { if (name === "NR_NODE_NAME") {
return node.name; return returnOrCallback(null, node.name);
} }
if (name === "NR_NODE_ID") { if (name === "NR_NODE_ID") {
return node.id; return returnOrCallback(null, node.id);
} }
if (name === "NR_NODE_PATH") { if (name === "NR_NODE_PATH") {
return node._path; return returnOrCallback(null, node._path);
} }
} }
/** @type {RuntimeLibFlowsFlow} */
var flow = (flow_ ? flow_ : (node ? node._flow : null)); var flow = (flow_ ? flow_ : (node ? node._flow : null));
if (flow) { if (flow) {
if (node && node.g) { if (node && node.g) {
const group = flow.getGroupNode(node.g); const group = flow.getGroupNode(node.g);
const [result, newName] = flow.getGroupEnvSetting(node, group, name); if (callback) {
if (result) { flow.getGroupEnvSetting(node, group, name, (e, [result, newName]) => {
return result.val; if (e) {
callback(e);
return
}
if (result) {
callback(null, result.val);
return
}
name = newName;
flow.getSetting(name, callback);
});
return
} else {
const [result, newName] = flow.getGroupEnvSetting(node, group, name);
if (result) {
return result.val;
}
name = newName;
} }
name = newName;
} }
return flow.getSetting(name); const fVal = flow.getSetting(name, callback)
if (callback) {
return
}
return fVal;
} }
return process.env[name]; return returnOrCallback(null, process.env[name]);
} }
@ -568,19 +600,34 @@ function getSetting(node, name, flow_) {
* will return `Hello Joe!`. * will return `Hello Joe!`.
* @param {String} value - the string to parse * @param {String} value - the string to parse
* @param {Node} node - the node evaluating the property * @param {Node} node - the node evaluating the property
* @param {(err: Error, result: Any) => void} callback - (optional) called when the property is evaluated
* @return {String} The parsed string * @return {String} The parsed string
* @memberof @node-red/util_util * @memberof @node-red/util_util
*/ */
function evaluateEnvProperty(value, node) { function evaluateEnvProperty(value, node, callback) {
const returnOrCallback = (err, result) => {
if (callback) {
callback(err, result);
return
}
return result;
}
/** @type {RuntimeLibFlowsFlow} */
var flow = (node && hasOwnProperty.call(node, "_flow")) ? node._flow : null; var flow = (node && hasOwnProperty.call(node, "_flow")) ? node._flow : null;
var result; var result;
if (/^\${[^}]+}$/.test(value)) { if (/^\${[^}]+}$/.test(value)) {
// ${ENV_VAR} // ${ENV_VAR}
var name = value.substring(2,value.length-1); var name = value.substring(2,value.length-1);
result = getSetting(node, name, flow); result = getSetting(node, name, flow, callback);
if (callback) {
return
}
} else if (!/\${\S+}/.test(value)) { } else if (!/\${\S+}/.test(value)) {
// ENV_VAR // ENV_VAR
result = getSetting(node, value, flow); result = getSetting(node, value, flow, callback);
if (callback) {
return
}
} else { } else {
// FOO${ENV_VAR}BAR // FOO${ENV_VAR}BAR
return value.replace(/\${([^}]+)}/g, function(match, name) { return value.replace(/\${([^}]+)}/g, function(match, name) {
@ -588,8 +635,7 @@ function evaluateEnvProperty(value, node) {
return (val === undefined)?"":val; return (val === undefined)?"":val;
}); });
} }
return (result === undefined)?"":result; return returnOrCallback(null, (result === undefined)?"":result);
} }
@ -677,7 +723,10 @@ function evaluateNodeProperty(value, type, node, msg, callback) {
return return
} }
} else if (type === 'env') { } else if (type === 'env') {
result = evaluateEnvProperty(value, node); result = evaluateEnvProperty(value, node, callback);
if (callback) {
return
}
} }
if (callback) { if (callback) {
callback(null,result); callback(null,result);

View File

@ -44,4 +44,30 @@ describe('unknown Node', function() {
}); });
}); });
it('should evaluate a global environment variable that is a JSONata value', function (done) {
const flow = [{
id: "n1", type: "global-config", name: "XYZ",
env: [
{ name: "now-var", type: "jsonata", value: "$millis()" }
]
},
{ id: "n2", type: "inject", topic: "t1", payload: "now-var", payloadType: "env", wires: [["n3"]], z: "flow" },
{ id: "n3", type: "helper" }
];
helper.load([config, inject], flow, function () {
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
n3.on("input", (msg) => {
try {
const now = Date.now();
msg.should.have.property("payload").and.be.approximately(now, 1000);
done();
} catch (err) {
done(err);
}
});
n2.receive({});
});
});
}); });

View File

@ -1424,7 +1424,30 @@ describe('function node', function() {
}); });
}); });
it('should timeout if timeout is set', function(done) {
var flow = [{id:"n1",type:"function",wires:[["n2"]],timeout:"0.010",func:"while(1==1){};\nreturn msg;"}];
helper.load(functionNode, flow, function() {
var n1 = helper.getNode("n1");
n1.receive({payload:"foo",topic: "bar"});
setTimeout(function() {
try {
helper.log().called.should.be.true();
var logEvents = helper.log().args.filter(function(evt) {
return evt[0].type == "function";
});
logEvents.should.have.length(1);
var msg = logEvents[0][0];
msg.should.have.property('level', helper.log().ERROR);
msg.should.have.property('id', 'n1');
msg.should.have.property('type', 'function');
should.equal(msg.msg.message, 'Script execution timed out after 10ms');
done();
} catch(err) {
done(err);
}
},50);
});
});
describe("finalize function", function() { describe("finalize function", function() {

View File

@ -686,7 +686,7 @@ describe('Flow', function() {
},50); },50);
}); });
it.only("passes a status event to the group scoped status node",function(done) { it("passes a status event to the group scoped status node",function(done) {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
{id:"t1",type:"tab"}, {id:"t1",type:"tab"},
{id: "g1", type: "group", g: "g3" }, {id: "g1", type: "group", g: "g3" },
@ -1311,33 +1311,42 @@ describe('Flow', function() {
}) })
process.env.V0 = "gv0"; process.env.V0 = "gv0";
process.env.V1 = "gv1"; process.env.V1 = "gv1";
process.env.V3 = "gv3";
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
{id:"t1",type:"tab",env:[ {id:"t1",type:"tab",env:[
{"name": "V0", value: "v0", type: "str"} {"name": "V0", value: "t1v0", type: "str"},
{"name": "V2", value: "t1v2", type: "str"}
]}, ]},
{id:"g1",type:"group",z:"t1",env:[ {id:"g1",type:"group",z:"t1",env:[
{"name": "V0", value: "v1", type: "str"}, {"name": "V0", value: "g1v0", type: "str"},
{"name": "V1", value: "v2", type: "str"} {"name": "V1", value: "g1v1", type: "str"}
]}, ]},
{id:"g2",type:"group",z:"t1",g:"g1",env:[ {id:"g2",type:"group",z:"t1",g:"g1",env:[
{"name": "V1", value: "v3", type: "str"} {"name": "V1", value: "g2v1", type: "str"}
]}, ]},
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"$(V0)",wires:[]}, {id:"t1__V0",x:10,y:10,z:"t1",type:"test",foo:"${V0}",wires:[]}, // V0 will come from tab env V0
{id:"2",x:10,y:10,z:"t1",g:"g1",type:"test",foo:"$(V0)",wires:[]}, {id:"t1g1V0",x:10,y:10,z:"t1",g:"g1",type:"test",foo:"${V0}",wires:[]}, // V0 will come from group 1 env V0
{id:"3",x:10,y:10,z:"t1",g:"g1",type:"test",foo:"$(V1)",wires:[]}, {id:"t1g1V1",x:10,y:10,z:"t1",g:"g1",type:"test",foo:"${V1}",wires:[]}, // V1 will come from group 1 env V1
{id:"4",x:10,y:10,z:"t1",g:"g2",type:"test",foo:"$(V1)",wires:[]}, {id:"t1g2V0",x:10,y:10,z:"t1",g:"g2",type:"test",foo:"${V0}",wires:[]}, // V0 will come from group 1 env V0
{id:"5",x:10,y:10,z:"t1",type:"test",foo:"$(V1)",wires:[]}, {id:"t1g2V1",x:10,y:10,z:"t1",g:"g2",type:"test",foo:"${V1}",wires:[]}, // V1 will come from group 2 env V1
{id:"t1g2V2",x:10,y:10,z:"t1",g:"g2",type:"test",foo:"${V2}",wires:[]}, // V2 will come from tab 1 env V2
{id:"t1g2V3",x:10,y:10,z:"t1",g:"g2",type:"test",foo:"${V3}",wires:[]}, // V3 will come from process env V3
{id:"t1__V1",x:10,y:10,z:"t1",type:"test",foo:"${V1}",wires:[]},
]); ]);
var flow = Flow.create({getSetting:v=>process.env[v]},config,config.flows["t1"]); var flow = Flow.create({getSetting:v=>process.env[v]},config,config.flows["t1"]);
flow.start(); flow.start();
var activeNodes = flow.getActiveNodes(); var activeNodes = flow.getActiveNodes();
activeNodes["1"].foo.should.equal("v0"); activeNodes.t1__V0.foo.should.equal("t1v0"); // node in tab 1, get tab 1 env V0
activeNodes["2"].foo.should.equal("v1"); activeNodes.t1__V1.foo.should.equal("gv1"); // node in tab 1, get V1, (tab 1 no V1) --> parent (global has V1)
activeNodes["3"].foo.should.equal("v2"); activeNodes.t1g1V0.foo.should.equal("g1v0"); // node in group 1, get V0, (group 1 has V0)
activeNodes["4"].foo.should.equal("v3"); activeNodes.t1g1V1.foo.should.equal("g1v1"); // node in group 1, get V1, (group 1 has V1)
activeNodes["5"].foo.should.equal("gv1"); activeNodes.t1g2V0.foo.should.equal("g1v0"); // node in group 2, get V0, (group 2 no V0) --> parent (group 1 has V0)
activeNodes.t1g2V1.foo.should.equal("g2v1"); // node in group 2, get V1, (group 2 has V1)
activeNodes.t1g2V2.foo.should.equal("t1v2"); // node in group 2, get V2, (group 2 no V2) --> parent (tab 1 has V2)
activeNodes.t1g2V3.foo.should.equal("gv3"); // node in group 2, get V3, (group 2 no V3) --> parent (tab 1 no V2) --> parent (global has V3)
flow.stop().then(function() { flow.stop().then(function() {
done(); done();
@ -1347,7 +1356,6 @@ describe('Flow', function() {
console.log(e.stack); console.log(e.stack);
done(e); done(e);
} }
}); });
it("can access environment variable property using $parent", function (done) { it("can access environment variable property using $parent", function (done) {
try { try {
@ -1393,7 +1401,6 @@ describe('Flow', function() {
console.log(e.stack); console.log(e.stack);
done(e); done(e);
} }
}); });
it("can define environment variable using JSONata", function (done) { it("can define environment variable using JSONata", function (done) {
@ -1427,9 +1434,40 @@ describe('Flow', function() {
console.log(e.stack); console.log(e.stack);
done(e); done(e);
} }
}); });
it("can access global environment variables defined as JSONata values", function (done) {
try {
after(function() {
delete process.env.V0;
})
var config = flowUtils.parseConfig([
{id:"t1",type:"tab",env:[
{"name": "V0", value: "1+2", type: "jsonata"}
]},
{id:"g1",type:"group",z:"t1",env:[
{"name": "V1", value: "2+3", type: "jsonata"},
]},
{id:"1",x:10,y:10,z:"t1",g:"g1",type:"test",foo:"$(V0)",wires:[]},
{id:"2",x:10,y:10,z:"t1",g:"g1",type:"test",foo:"$(V1)",wires:[]},
]);
var flow = Flow.create({getSetting:v=>process.env[v]},config,config.flows["t1"]);
flow.start();
var activeNodes = flow.getActiveNodes();
activeNodes["1"].foo.should.equal(3);
activeNodes["2"].foo.should.equal(5);
flow.stop().then(function() {
done();
});
}
catch (e) {
console.log(e.stack);
done(e);
}
});
}); });
}); });

View File

@ -489,7 +489,7 @@ describe('storage/localfilesystem', function() {
var rootdir = path.win32.resolve(userDir+'/some'); var rootdir = path.win32.resolve(userDir+'/some');
// make it into a local UNC path // make it into a local UNC path
flowFile = flowFile.replace('C:\\', '\\\\localhost\\c$\\'); flowFile = flowFile.replace('C:\\', '\\\\localhost\\c$\\');
localfilesystem.init({userDir:userDir, flowFile:flowFile}, mockRuntime).then(function() { localfilesystem.init({userDir:userDir, flowFile:flowFile, getUserSettings: () => {{}}}, mockRuntime).then(function() {
fs.existsSync(flowFile).should.be.false(); fs.existsSync(flowFile).should.be.false();
localfilesystem.saveFlows(testFlow).then(function() { localfilesystem.saveFlows(testFlow).then(function() {
fs.existsSync(flowFile).should.be.true(); fs.existsSync(flowFile).should.be.true();