Compare commits

..

42 Commits

Author SHA1 Message Date
Nick O'Leary
56ed32e4a1 Add background to node status 2023-06-23 16:09:34 +01:00
Nick O'Leary
11f9ad8ca3 Remove debug for wiring 2023-06-23 12:29:33 +01:00
Nick O'Leary
26fc942c79 Improve wiring for horizontally aligned nodes 2023-06-23 12:24:48 +01:00
Nick O'Leary
2448e137c8 Merge pull request #4229 from node-red/4125-httpstatic-middleware
Add support for httpStatic middleware
2023-06-21 16:57:00 +01:00
Nick O'Leary
33899763ef Add support for httpStatic middleware 2023-06-21 16:47:47 +01:00
Nick O'Leary
ce679f90ee Merge pull request #4177 from k1ln/adding-timeout-to-functio-node
adding timeout attribute to function node
2023-06-21 15:57:44 +01:00
Nick O'Leary
8fb379079b Merge pull request #4225 from node-red/4196-fix-jsonata-env-var-async
Add callback to getSetting to support async jsonata access
2023-06-21 15:56:11 +01:00
Nick O'Leary
b382d048de Merge pull request #4227 from node-red/4196-add-callback-opt-to-env.get
Adds optional callback to env.get in function node
2023-06-21 15:55:40 +01:00
Nick O'Leary
4d9fcaeebf Update env.get type hint 2023-06-21 15:00:27 +01:00
Nick O'Leary
aa0225f59f Apply suggestions from code review 2023-06-21 14:27:32 +01:00
Nick O'Leary
2571949e90 Merge pull request #4143 from inNETMonitoring/fix/joinManual
fix: closes #4142
2023-06-21 14:24:53 +01:00
Kilian Hertel
20d2c11154 Merge branch 'dev' into adding-timeout-to-functio-node 2023-06-21 15:24:24 +02:00
Nick O'Leary
3f604e9d93 Adds optional callback to env.get in function node 2023-06-21 14:20:23 +01:00
Nick O'Leary
01c56f6515 Merge pull request #4200 from GogoVega/french-translation-of-welcome-tours
French translation of Welcome Tours
2023-06-21 14:03:56 +01:00
Nick O'Leary
026be5a990 Merge pull request #4199 from GogoVega/french-translation-of-v3.1.0-beta.3
French translation of v3.1.0-beta.3 changes
2023-06-21 14:01:10 +01:00
Nick O'Leary
aafb86ef09 Merge branch 'dev' into adding-timeout-to-functio-node 2023-06-21 13:35:48 +01:00
Nick O'Leary
1a24efe85d Merge pull request #4209 from HiroyasuNishiyama/jp-message
add Japanese message for 3.1.0 beta 3
2023-06-21 13:34:23 +01:00
Nick O'Leary
0b3f7dbb1f Merge pull request #4203 from node-red/fix-delay-node-flush-issue-4202
Fix delay node flush issue
2023-06-21 13:27:32 +01:00
Nick O'Leary
e90007860c Merge pull request #4207 from node-red/4197-status-catch-label
Update status and catch node labels in group mode
2023-06-21 13:27:09 +01:00
Steve-Mcl
502dacd865 fix failure to return after calling callback 2023-06-17 22:44:55 +01:00
Steve-Mcl
31bc99cd61 remove .only 2023-06-17 22:29:39 +01:00
Steve-Mcl
5435c9ebd2 fix test (missing getUserSettings stub 🤷‍♂️) 2023-06-17 22:12:09 +01:00
Steve-Mcl
ceb9a320ba expand existing env var test for all scenarios 2023-06-17 22:11:02 +01:00
Steve-Mcl
ee8b2a0b58 Delete stray it.only 2023-06-17 22:03:59 +01:00
Steve-Mcl
8202f1b7c6 Add env var is JSONata expr test 2023-06-17 21:54:32 +01:00
Steve-Mcl
4808cac89d Add async to all paths that JSONata env var calls 2023-06-17 21:14:56 +01:00
Kilian Hertel
220a621dc6 Merge branch 'dev' into adding-timeout-to-functio-node 2023-06-02 12:20:51 +02:00
BitCaesar
0db288e6dc Merge branch 'dev' into fix/joinManual 2023-05-31 16:10:41 +02:00
Nick O'Leary
fb5b470966 Merge pull request #4208 from node-red/4198-group-edit-stack-overflow
Dont clone the group nodes `node` array when saving edits
2023-05-31 09:50:31 +01:00
Kilian Hertel
876053f858 Merge branch 'dev' into adding-timeout-to-functio-node 2023-05-30 14:55:56 +02:00
GogoVega
9714ef19de Missing french translation of beta 3 changes 2023-05-30 14:19:39 +02:00
HiroyasuNishiyama
613a345771 add additional Japanese translation for status and catch node 2023-05-30 20:58:17 +09:00
BitCaesar
4fe29dd33f Merge branch 'dev' into fix/joinManual 2023-05-30 10:42:04 +02:00
Steve-Mcl
4b24223290 Dont clone group node node array
fixes #4198
2023-05-29 23:55:52 +01:00
Dave Conway-Jones
5e4fce1e12 Fix delay node flush issue
to close #4202
2023-05-27 17:51:21 +01:00
GogoVega
4cb2624a5d French translation of Welcome Tours 2023-05-26 20:58:50 +02:00
GogoVega
58e045f25d French translation of changes from v3.1.0-beta.3 2023-05-26 20:52:25 +02:00
Nick O'Leary
2f9523a586 Merge branch 'dev' into adding-timeout-to-functio-node 2023-05-25 17:43:04 +01:00
Nick O'Leary
0697c26dd1 Merge branch 'dev' into adding-timeout-to-functio-node 2023-05-25 17:33:41 +01:00
Kilian Hertel
c2812b05a4 Merge branch 'master' into adding-timeout-to-functio-node 2023-05-22 17:42:59 +02:00
Kilian Hertel
2253417459 adding timeout attribute to function node
- [x] New feature (non-breaking change which adds functionality)

Discussion here:
https://discourse.nodered.org/t/function-node-doesnt-have-timeout-feature/78483

## Proposed changes

Adding a timeout attribute to the function node, so an endless funciton doesnt break the node red server.

## Checklist

- [x] I have read the [contribution guidelines](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md)
- [x] For non-bugfix PRs, I have discussed this change on the forum/slack team.
- [x] I have run `grunt` to verify the unit tests pass
- [x] I have added suitable unit tests to cover the new/changed functionality
2023-05-22 10:16:37 +02:00
BitCaesar
e16d5cf83d fix: closes #4142
The issue occured because the partId is set to "_" by default and is never overwritten in manual mode.
With concurrent messages and different processing times all parts of all messages have the identifier "_" and are assembled following the FIFO principle.
2023-04-20 12:28:36 +02:00
26 changed files with 675 additions and 225 deletions

View File

@@ -504,6 +504,7 @@
"unassigned": "Non attribué",
"global": "global",
"workspace": "espace de travail",
"editor": "boîte de dialogue d'édition",
"selectAll": "Tout sélectionner",
"selectNone": "Ne rien sélectionner",
"selectAllConnected": "Sélectionner tous les éléments connectés",

View File

@@ -720,7 +720,10 @@ RED.editor = (function() {
if (typeof editing_node[d] === "string" || typeof editing_node[d] === "number") {
oldValues[d] = editing_node[d];
} else {
oldValues[d] = $.extend(true,{},{v:editing_node[d]}).v;
// Dont clone the group node `nodes` array
if (editing_node.type !== 'group' || d !== "nodes") {
oldValues[d] = $.extend(true,{},{v:editing_node[d]}).v;
}
}
}
}

View File

@@ -1033,7 +1033,7 @@ RED.view = (function() {
})
}
function generateLinkPath(origX,origY, destX, destY, sc) {
function generateLinkPath(origX,origY, destX, destY, sc, hasStatus = false) {
var dy = destY-origY;
var dx = destX-origX;
var delta = Math.sqrt(dy*dy+dx*dx);
@@ -1050,62 +1050,110 @@ RED.view = (function() {
} else {
scale = 0.4-0.2*(Math.max(0,(node_width-Math.min(Math.abs(dx),Math.abs(dy)))/node_width));
}
function genCP(cp) {
return ` M ${cp[0]-5} ${cp[1]} h 10 M ${cp[0]} ${cp[1]-5} v 10 `
}
if (dx*sc > 0) {
return "M "+origX+" "+origY+
" C "+(origX+sc*(node_width*scale))+" "+(origY+scaleY*node_height)+" "+
(destX-sc*(scale)*node_width)+" "+(destY-scaleY*node_height)+" "+
destX+" "+destY
let cp = [
[(origX+sc*(node_width*scale)), (origY+scaleY*node_height)],
[(destX-sc*(scale)*node_width), (destY-scaleY*node_height)]
]
return `M ${origX} ${origY} C ${cp[0][0]} ${cp[0][1]} ${cp[1][0]} ${cp[1][1]} ${destX} ${destY}`
// + ` ${genCP(cp[0])} ${genCP(cp[1])}`
} else {
let topX, topY, bottomX, bottomY
let cp
let midX = Math.floor(destX-dx/2);
let midY = Math.floor(destY-dy/2);
if (Math.abs(dy) < 10) {
bottomY = Math.max(origY, destY) + (hasStatus?35:25)
let startCurveHeight = bottomY - origY
let endCurveHeight = bottomY - destY
cp = [
[ origX + sc*15 , origY ],
[ origX + sc*25 , origY + 5 ],
[ origX + sc*25 , origY + startCurveHeight/2 ],
var midX = Math.floor(destX-dx/2);
var midY = Math.floor(destY-dy/2);
//
if (dy === 0) {
midY = destY + node_height;
}
var cp_height = node_height/2;
var y1 = (destY + midY)/2
var topX =origX + sc*node_width*scale;
var topY = dy>0?Math.min(y1 - dy/2 , origY+cp_height):Math.max(y1 - dy/2 , origY-cp_height);
var bottomX = destX - sc*node_width*scale;
var bottomY = dy>0?Math.max(y1, destY-cp_height):Math.min(y1, destY+cp_height);
var x1 = (origX+topX)/2;
var scy = dy>0?1:-1;
var cp = [
// Orig -> Top
[x1,origY],
[topX,dy>0?Math.max(origY, topY-cp_height):Math.min(origY, topY+cp_height)],
// Top -> Mid
// [Mirror previous cp]
[x1,dy>0?Math.min(midY, topY+cp_height):Math.max(midY, topY-cp_height)],
// Mid -> Bottom
// [Mirror previous cp]
[bottomX,dy>0?Math.max(midY, bottomY-cp_height):Math.min(midY, bottomY+cp_height)],
// Bottom -> Dest
// [Mirror previous cp]
[(destX+bottomX)/2,destY]
];
if (cp[2][1] === topY+scy*cp_height) {
if (Math.abs(dy) < cp_height*10) {
cp[1][1] = topY-scy*cp_height/2;
cp[3][1] = bottomY-scy*cp_height/2;
}
cp[2][0] = topX;
}
return "M "+origX+" "+origY+
" C "+
cp[0][0]+" "+cp[0][1]+" "+
cp[1][0]+" "+cp[1][1]+" "+
topX+" "+topY+
" S "+
cp[2][0]+" "+cp[2][1]+" "+
midX+" "+midY+
" S "+
cp[3][0]+" "+cp[3][1]+" "+
bottomX+" "+bottomY+
" S "+
[ origX + sc*25 , origY + startCurveHeight - 5 ],
[ origX + sc*15 , origY + startCurveHeight ],
[ origX , origY + startCurveHeight ],
[ destX - sc*15, origY + startCurveHeight ],
[ destX - sc*25, origY + startCurveHeight - 5 ],
[ destX - sc*25, destY + endCurveHeight/2 ],
[ destX - sc*25, destY + 5 ],
[ destX - sc*15, destY ],
[ destX, destY ],
]
return "M "+origX+" "+origY+
" C "+
cp[0][0]+" "+cp[0][1]+" "+
cp[1][0]+" "+cp[1][1]+" "+
cp[2][0]+" "+cp[2][1]+" "+
" C " +
cp[3][0]+" "+cp[3][1]+" "+
cp[4][0]+" "+cp[4][1]+" "+
destX+" "+destY
cp[5][0]+" "+cp[5][1]+" "+
" h "+dx+
" C "+
cp[6][0]+" "+cp[6][1]+" "+
cp[7][0]+" "+cp[7][1]+" "+
cp[8][0]+" "+cp[8][1]+" "+
" C " +
cp[9][0]+" "+cp[9][1]+" "+
cp[10][0]+" "+cp[10][1]+" "+
cp[11][0]+" "+cp[11][1]+" "
// +genCP(cp[0])+genCP(cp[1])+genCP(cp[2])+genCP(cp[3])+genCP(cp[4])
// +genCP(cp[5])+genCP(cp[6])+genCP(cp[7])+genCP(cp[8])+genCP(cp[9])+genCP(cp[10])
} else {
var cp_height = node_height/2;
var y1 = (destY + midY)/2
topX = origX + sc*node_width*scale;
topY = dy>0?Math.min(y1 - dy/2 , origY+cp_height):Math.max(y1 - dy/2 , origY-cp_height);
bottomX = destX - sc*node_width*scale;
bottomY = dy>0?Math.max(y1, destY-cp_height):Math.min(y1, destY+cp_height);
var x1 = (origX+topX)/2;
var scy = dy>0?1:-1;
cp = [
// Orig -> Top
[x1,origY],
[topX,dy>0?Math.max(origY, topY-cp_height):Math.min(origY, topY+cp_height)],
// Top -> Mid
// [Mirror previous cp]
[x1,dy>0?Math.min(midY, topY+cp_height):Math.max(midY, topY-cp_height)],
// Mid -> Bottom
// [Mirror previous cp]
[bottomX,dy>0?Math.max(midY, bottomY-cp_height):Math.min(midY, bottomY+cp_height)],
// Bottom -> Dest
// [Mirror previous cp]
[(destX+bottomX)/2,destY]
];
if (cp[2][1] === topY+scy*cp_height) {
if (Math.abs(dy) < cp_height*10) {
cp[1][1] = topY-scy*cp_height/2;
cp[3][1] = bottomY-scy*cp_height/2;
}
cp[2][0] = topX;
}
return "M "+origX+" "+origY+
" C "+
cp[0][0]+" "+cp[0][1]+" "+
cp[1][0]+" "+cp[1][1]+" "+
topX+" "+topY+
" S "+
cp[2][0]+" "+cp[2][1]+" "+
midX+" "+midY+
" S "+
cp[3][0]+" "+cp[3][1]+" "+
bottomX+" "+bottomY+
" S "+
cp[4][0]+" "+cp[4][1]+" "+
destX+" "+destY
// +genCP(cp[0])+genCP(cp[1])+genCP(cp[2])+genCP(cp[3])+genCP(cp[4])
}
}
}
@@ -1722,7 +1770,7 @@ RED.view = (function() {
var portY = -((numOutputs-1)/2)*13 +13*sourcePort;
var sc = (drag_line.portType === PORT_TYPE_OUTPUT)?1:-1;
drag_line.el.attr("d",generateLinkPath(drag_line.node.x+sc*drag_line.node.w/2,drag_line.node.y+portY,mousePos[0],mousePos[1],sc));
drag_line.el.attr("d",generateLinkPath(drag_line.node.x+sc*drag_line.node.w/2,drag_line.node.y+portY,mousePos[0],mousePos[1],sc, !!drag_line.node.status));
}
d3.event.preventDefault();
} else if (mouse_mode == RED.state.MOVING) {
@@ -4112,21 +4160,27 @@ RED.view = (function() {
nodeEl.__statusGroup__.style.display = "none";
} else {
nodeEl.__statusGroup__.style.display = "inline";
let backgroundWidth = 12
var fill = status_colours[d.status.fill]; // Only allow our colours for now
if (d.status.shape == null && fill == null) {
backgroundWidth = 0
nodeEl.__statusShape__.style.display = "none";
nodeEl.__statusBackground__.setAttribute("x", 17)
nodeEl.__statusGroup__.setAttribute("transform","translate(-14,"+(d.h+3)+")");
} else {
nodeEl.__statusGroup__.setAttribute("transform","translate(3,"+(d.h+3)+")");
var statusClass = "red-ui-flow-node-status-"+(d.status.shape||"dot")+"-"+d.status.fill;
nodeEl.__statusShape__.style.display = "inline";
nodeEl.__statusShape__.setAttribute("class","red-ui-flow-node-status "+statusClass);
nodeEl.__statusBackground__.setAttribute("x", 3)
}
if (d.status.hasOwnProperty('text')) {
nodeEl.__statusLabel__.textContent = d.status.text;
} else {
nodeEl.__statusLabel__.textContent = "";
}
const textSize = nodeEl.__statusLabel__.getBBox()
nodeEl.__statusBackground__.setAttribute('width', backgroundWidth + textSize.width + 6)
}
delete d.dirtyStatus;
}
@@ -4532,17 +4586,30 @@ RED.view = (function() {
statusEl.style.display = "none";
node[0][0].__statusGroup__ = statusEl;
var statusRect = document.createElementNS("http://www.w3.org/2000/svg","rect");
statusRect.setAttribute("class","red-ui-flow-node-status");
statusRect.setAttribute("x",6);
statusRect.setAttribute("y",1);
statusRect.setAttribute("width",9);
statusRect.setAttribute("height",9);
statusRect.setAttribute("rx",2);
statusRect.setAttribute("ry",2);
statusRect.setAttribute("stroke-width","3");
statusEl.appendChild(statusRect);
node[0][0].__statusShape__ = statusRect;
var statusBackground = document.createElementNS("http://www.w3.org/2000/svg","rect");
statusBackground.setAttribute("class","red-ui-flow-node-status-background");
statusBackground.setAttribute("x",3);
statusBackground.setAttribute("y",-1);
statusBackground.setAttribute("width",200);
statusBackground.setAttribute("height",13);
statusBackground.setAttribute("rx",1);
statusBackground.setAttribute("ry",1);
statusEl.appendChild(statusBackground);
node[0][0].__statusBackground__ = statusBackground;
var statusIcon = document.createElementNS("http://www.w3.org/2000/svg","rect");
statusIcon.setAttribute("class","red-ui-flow-node-status");
statusIcon.setAttribute("x",6);
statusIcon.setAttribute("y",1);
statusIcon.setAttribute("width",9);
statusIcon.setAttribute("height",9);
statusIcon.setAttribute("rx",2);
statusIcon.setAttribute("ry",2);
statusIcon.setAttribute("stroke-width","3");
statusEl.appendChild(statusIcon);
node[0][0].__statusShape__ = statusIcon;
var statusLabel = document.createElementNS("http://www.w3.org/2000/svg","text");
statusLabel.setAttribute("class","red-ui-flow-node-status-label");
@@ -5067,7 +5134,7 @@ RED.view = (function() {
// " C "+(d.x1+scale*node_width)+" "+(d.y1+scaleY*node_height)+" "+
// (d.x2-scale*node_width)+" "+(d.y2-scaleY*node_height)+" "+
// d.x2+" "+d.y2;
var path = generateLinkPath(d.x1,d.y1,d.x2,d.y2,1);
var path = generateLinkPath(d.x1,d.y1,d.x2,d.y2,1, !!(d.source.status || d.target.status));
if (/NaN/.test(path)) {
path = ""
}

View File

@@ -304,7 +304,11 @@ g.red-ui-flow-node-selected {
stroke: var(--red-ui-node-status-colors-#{"" + $current-color});
}
}
.red-ui-flow-node-status-background {
stroke: none;
fill: var(--red-ui-view-background);
fill-opacity: 0.9;
}
.red-ui-flow-node-status-label {
@include disable-selection;
stroke-width: 0;

View File

@@ -5,17 +5,20 @@ export default {
titleIcon: "fa fa-map-o",
title: {
"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: {
"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: {
"en-US": "Context Menu",
"ja": "コンテキストメニュー"
"ja": "コンテキストメニュー",
"fr": "Menu contextuel"
},
image: '3.0/images/context-menu.png',
description: {
@@ -24,13 +27,17 @@ export default {
<p>This makes many of the built-in actions much easier
to access.</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: {
"en-US": "Wire Junctions",
"ja": "分岐点をワイヤーに追加"
"ja": "分岐点をワイヤーに追加",
"fr": "Jonctions de fils"
},
image: '3.0/images/junction-slice.gif',
description: {
@@ -40,13 +47,18 @@ export default {
<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>`,
"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: {
"en-US": "Wire Junctions",
"ja": "分岐点をワイヤーに追加"
"ja": "分岐点をワイヤーに追加",
"fr": "Jonctions de fils"
},
image: '3.0/images/junction-quick-add.png',
description: {
@@ -54,13 +66,16 @@ export default {
<p>The dialog is opened by holding the Ctrl (or Cmd) key when
clicking in the workspace.</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: {
"en-US": "Debug Path Tooltip",
"ja": "デバッグパスのツールチップ"
"ja": "デバッグパスのツールチップ",
"fr": "Info-bulle du chemin de débogage"
},
image: '3.0/images/debug-path-tooltip.png',
description: {
@@ -73,26 +88,33 @@ export default {
the workspace.</p>`,
"ja": `<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: {
"en-US": "Continuous Search",
"ja": "連続した検索"
"ja": "連続した検索",
"fr": "Recherche continue"
},
image: '3.0/images/continuous-search.png',
description: {
"en-US": `<p>When searching for things in the editor, a new toolbar in
the workspace provides options to quickly jump between
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: {
"en-US": "New wiring actions",
"ja": "新しいワイヤー操作"
"ja": "新しいワイヤー操作",
"fr": "Nouvelles actions de câblage"
},
image: "3.0/images/split-wire-with-links.gif",
description: {
@@ -106,12 +128,18 @@ export default {
<li><b><code>ワイヤーをlinkードで分割</code></b></li>
</ul>
<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: {
"en-US": "Default node names",
"ja": "標準ノードの名前"
"ja": "標準ノードの名前",
"fr": "Noms de noeud par défaut"
},
// image: "images/",
description: {
@@ -129,13 +157,22 @@ export default {
<ul>
<li><b><code>ノード名を生成</code></b></li>
</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: {
"en-US": "Node Updates",
"ja": "ノードの更新"
"ja": "ノードの更新",
"fr": "Mises à jour des noeuds"
},
// image: "images/",
description: {
@@ -148,6 +185,11 @@ export default {
<li>Debugードは、受信したメッセージの数をカウントするよう設定できるようになりました。</li>
<li>Link Callードは、メッセージのプロパティによって、呼び出し対象のlinkを動的に指定できるようになりました。</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>`
}
}

View File

@@ -3,12 +3,14 @@ export default {
{
title: {
'en-US': 'Create your first flow',
'ja': 'はじめてのフローを作成'
'ja': 'はじめてのフローを作成',
'fr': "Créer votre premier flux"
},
width: 400,
description: {
'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'
},
@@ -16,7 +18,8 @@ export default {
element: "#red-ui-workspace .red-ui-tab-button.red-ui-tabs-add",
description: {
'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: {
type: "dom-event",
@@ -29,7 +32,8 @@ export default {
direction: 'right',
description: {
'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',
wait: {
@@ -52,7 +56,8 @@ export default {
direction: 'right',
description: {
'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',
wait: {
@@ -74,7 +79,8 @@ export default {
element: function() { return $("#"+this.injectNode.id+" .red-ui-flow-port") },
description: {
'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',
wait: {
@@ -89,7 +95,8 @@ export default {
element: "#red-ui-header-button-deploy",
description: {
'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,
wait: {

View File

@@ -5,17 +5,20 @@ export default {
titleIcon: "fa fa-map-o",
title: {
"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: {
"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: {
"en-US": "New ways to work with groups",
"ja": "グループの新たな操作方法"
"ja": "グループの新たな操作方法",
"fr": "De nouvelles façons de travailler avec les groupes"
},
description: {
"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><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>`
}
},
{
title: {
"en-US": "Change notification on tabs",
"ja": "タブ上の変更通知"
"ja": "タブ上の変更通知",
"fr": "Notification de changement sur les onglets"
},
image: 'images/tab-changes.png',
description: {
"en-US": `<p>When a tab contains undeployed changes it now shows the
same style of change icon used by nodes.</p>
<p>This will make it much easier to track down changes when you're
working across multiple flows.</p>`,
same style of change icon used by nodes.</p>
<p>This will make it much easier to track down changes when you're
working across multiple flows.</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: {
"en-US": "A bigger canvas to work with",
"ja": "より広くなった作業キャンバス"
"ja": "より広くなった作業キャンバス",
"fr": "Un canevas plus grand pour travailler"
},
description: {
"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
keep things organised, but now you have more room to work in.</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: {
"en-US": "Finding help",
"ja": "ヘルプを見つける"
"ja": "ヘルプを見つける",
"fr": "Trouver de l'aide"
},
image: 'images/node-help.png',
description: {
@@ -74,23 +95,29 @@ export default {
in the footer.</p>
<p>Clicking it will open up the Help sidebar showing the help for that node.</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: {
"en-US": "And lots more...",
"ja": "そしてさらに沢山あります..."
"ja": "そしてさらに沢山あります...",
"fr": "Et plus encore..."
},
description: {
"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: {
"en-US": "Improved Context Menu",
"ja": "コンテキストメニューの改善"
"ja": "コンテキストメニューの改善",
"fr": "Menu contextuel amélioré"
},
image: 'images/context-menu.png',
description: {
@@ -102,13 +129,17 @@ export default {
with your flows much easier.</p>`,
"ja": `<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: {
"en-US": "Hiding Flows",
"ja": "フローを非表示"
"ja": "フローを非表示",
"fr": "Masquage de flux"
},
image: 'images/hiding-flows.png',
description: {
@@ -116,13 +147,17 @@ export default {
<p>The 'hide' button in previous releases has been removed from the tabs
as they were being clicked accidentally too often.</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: {
"en-US": "Locking Flows",
"ja": "フローを固定"
"ja": "フローを固定",
"fr": "Verrouillage de flux"
},
image: 'images/locking-flows.png',
description: {
@@ -132,13 +167,18 @@ export default {
as well as in the Info sidebar explorer.</p>`,
"ja": `<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: {
"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',
description: {
@@ -147,44 +187,56 @@ export default {
<p>When the description is shown in the Info sidebar, the image will be displayed.</p>`,
"ja": `<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: {
"en-US": "Adding Mermaid Diagrams",
"ja": "Mermaid図を追加"
"ja": "Mermaid図を追加",
"fr": "Ajout de diagrammes Mermaid"
},
image: 'images/mermaid.png',
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>
<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>
<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: {
"en-US": "Managing Global Environment Variables",
"ja": "グローバル環境変数の管理"
"ja": "グローバル環境変数の管理",
"fr": "Gestion des variables d'environnement globales"
},
image: 'images/global-env-vars.png',
description: {
"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>`,
"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: {
"en-US": "Node Updates",
"ja": "ノードの更新"
"ja": "ノードの更新",
"fr": "Mises à jour des noeuds"
},
// image: "images/",
description: {
"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>`,
"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");```
*/
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

@@ -82,6 +82,11 @@
<input id="node-input-outputs" style="width: 60px;" value="1">
</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;">
<label><i class="fa fa-cubes"></i> <span data-i18n="function.label.modules"></span></label>
</div>
@@ -360,6 +365,7 @@
name: {value:"_DEFAULT_"},
func: {value:"\nreturn msg;"},
outputs: {value:1},
timeout:{value:0},
noerr: {value:0,required:true,
validate: function(v, opt) {
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 editor = RED.editor.createEditor({
id: id,
@@ -503,7 +529,7 @@
editor:this.editor, // the field name the main text body goes to
mode:"ace/mode/nrjavascript",
fields:[
'name', 'outputs',
'name', 'outputs', 'timeout',
{
name: 'initialize',
get: function() {

View File

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

View File

@@ -229,6 +229,7 @@ module.exports = function(RED) {
node.on("input", function(msg, send, done) {
if (!node.drop) {
var m = RED.util.cloneMessage(msg);
delete m.flush;
if (Object.keys(m).length > 1) {
if (node.intervalID !== -1) {
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;
if (n.count === "" && msg.hasOwnProperty('parts')) {
targetCount = msg.parts.count || 0;
if (msg.parts.hasOwnProperty('id')) {
partId = msg.parts.id;
}
}
if (node.build === 'object') {
propertyKey = RED.util.getMessageProperty(msg,node.key);

View File

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

View File

@@ -252,7 +252,8 @@
"initialize": "On Start",
"finalize": "On Stop",
"outputs": "Outputs",
"modules": "Modules"
"modules": "Modules",
"timeout": "Timeout"
},
"text": {
"initialize": "// Code added here will be run once\n// whenever the node is started.\n",

View File

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

View File

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

View File

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

View File

@@ -416,23 +416,50 @@ class Flow {
return this.activeNodes;
}
/*!
* Get value of environment variable defined in group node.
* @param {String} group - group node
* @param {String} name - name of variable
* @return {Object} object containing the value in val property or null if not defined
/**
* Group callback signature
*
* @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 (name === "NR_GROUP_NAME") {
return [{
val: group.name
}, null];
return returnOrCallback(null, [{ val: group.name }, null]);
}
if (name === "NR_GROUP_ID") {
return [{
val: group.id
}, null];
return returnOrCallback(null, [{ val: group.id }, null]);
}
if (group.credentials === undefined) {
@@ -457,33 +484,32 @@ class Flow {
if (env) {
let value = env.value;
const type = env.type;
if ((type !== "env") ||
(value !== name)) {
if ((type !== "env") || (value !== name)) {
if (type === "env") {
value = value.replace(new RegExp("\\${"+name+"}","g"),"${$parent."+name+"}");
}
if (type === "bool") {
const val
= ((value === "true") ||
(value === true));
return [{
val: val
}, null];
} else if (type === "bool") {
const val = ((value === "true") || (value === true));
return returnOrCallback(null, [{ val: val }, null])
}
if (type === "cred") {
return [{
val: value
}, null];
return returnOrCallback(null, [{ val: value }, null])
}
try {
var val = redUtil.evaluateNodeProperty(value, type, node, null, null);
return [{
val: val
}, null];
if (!callback) {
var val = redUtil.evaluateNodeProperty(value, type, node, null, null);
return [{ val: val }, null];
} else {
redUtil.evaluateNodeProperty(value, type, node, null, (err, value) => {
return returnOrCallback(err, [{ val: value }, null])
});
return
}
}
catch (e) {
this.error(e);
return [null, null];
if (!callback) {
this.error(e);
}
return returnOrCallback(e, null);
}
}
}
@@ -494,27 +520,47 @@ class Flow {
}
if (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
* flow which, as defined in ./index.js returns `process.env[key]`.
* This lays the groundwork for Subflow to have instance-specific settings
* @param {[type]} key [description]
* @return {[type]} [description]
* @param {String} key The settings key
* @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;
if (key === "NR_FLOW_NAME") {
return flow.label;
return returnOrCallback(null, flow.label);
}
if (key === "NR_FLOW_ID") {
return flow.id;
return returnOrCallback(null, flow.id);
}
if (flow.credentials === undefined) {
flow.credentials = credentials.get(flow.id) || {};
@@ -544,15 +590,14 @@ class Flow {
}
try {
if (type === "bool") {
const val = ((value === "true") ||
(value === true));
return val;
const val = ((value === "true") || (value === true));
return returnOrCallback(null, val);
}
if (type === "cred") {
return value;
return returnOrCallback(null, value);
}
var val = redUtil.evaluateNodeProperty(value, type, null, null, null);
return val;
return returnOrCallback(null, val);
}
catch (e) {
this.error(e);
@@ -564,7 +609,11 @@ class Flow {
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,
handleError: () => false,
handleStatus: () => false,
getSetting: k => flowUtil.getEnvVar(k),
getSetting: (k, callback) => flowUtil.getEnvVar(k, callback),
log: m => log.log(m)
}

View File

@@ -308,16 +308,34 @@ module.exports = {
runtime.settings.envVarExcludes.forEach(v => envVarExcludes[v] = true);
}
},
getEnvVar: function(k) {
if (!envVarExcludes[k]) {
const item = getGlobalEnv(k);
/**
* Get the value of an environment variable
* 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) {
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 process.env[k];
return returnOrCallback(null, process.env[key]);
}
return undefined;
return returnOrCallback(undefined);
},
diffNodes: diffNodes,
mapEnvVarProperties: mapEnvVarProperties,

View File

@@ -18,6 +18,7 @@
/**
* @mixin @node-red/util_util
*/
/** @typedef {import('../../runtime/lib/flows/Flow.js').Flow} RuntimeLibFlowsFlow */
const clonedeep = require("lodash.clonedeep");
const jsonata = require("jsonata");
@@ -526,37 +527,68 @@ function setObjectProperty(msg,prop,value,createMissing) {
return true;
}
/*!
/**
* Get value of environment variable.
* @param {Node} node - accessing node
* @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
*/
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 (name === "NR_NODE_NAME") {
return node.name;
return returnOrCallback(null, node.name);
}
if (name === "NR_NODE_ID") {
return node.id;
return returnOrCallback(null, node.id);
}
if (name === "NR_NODE_PATH") {
return node._path;
return returnOrCallback(null, node._path);
}
}
/** @type {RuntimeLibFlowsFlow} */
var flow = (flow_ ? flow_ : (node ? node._flow : null));
if (flow) {
if (node && node.g) {
const group = flow.getGroupNode(node.g);
const [result, newName] = flow.getGroupEnvSetting(node, group, name);
if (result) {
return result.val;
if (callback) {
flow.getGroupEnvSetting(node, group, name, (e, [result, newName]) => {
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!`.
* @param {String} value - the string to parse
* @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
* @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 result;
if (/^\${[^}]+}$/.test(value)) {
// ${ENV_VAR}
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)) {
// ENV_VAR
result = getSetting(node, value, flow);
result = getSetting(node, value, flow, callback);
if (callback) {
return
}
} else {
// FOO${ENV_VAR}BAR
return value.replace(/\${([^}]+)}/g, function(match, name) {
@@ -588,8 +635,7 @@ function evaluateEnvProperty(value, node) {
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
}
} else if (type === 'env') {
result = evaluateEnvProperty(value, node);
result = evaluateEnvProperty(value, node, callback);
if (callback) {
return
}
}
if (callback) {
callback(null,result);

View File

@@ -302,7 +302,7 @@ httpsPromise.then(function(startupHttps) {
settings.httpNodeAuth = settings.httpNodeAuth || settings.httpAuth;
}
if(settings.httpStatic) {
if (settings.httpStatic) {
settings.httpStaticRoot = formatRoot(settings.httpStaticRoot || "/");
const statics = Array.isArray(settings.httpStatic) ? settings.httpStatic : [settings.httpStatic];
const sanitised = [];
@@ -414,13 +414,7 @@ httpsPromise.then(function(startupHttps) {
if (settings.httpNodeRoot !== false) {
app.use(settings.httpNodeRoot,RED.httpNode);
}
// if (settings.httpStatic) {
// settings.httpStaticAuth = settings.httpStaticAuth || settings.httpAuth;
// if (settings.httpStaticAuth) {
// app.use("/",basicAuthMiddleware(settings.httpStaticAuth.user,settings.httpStaticAuth.pass));
// }
// app.use("/",express.static(settings.httpStatic));
// }
if (settings.httpStatic) {
let appUseMem = {};
for (let si = 0; si < settings.httpStatic.length; si++) {
@@ -428,6 +422,7 @@ httpsPromise.then(function(startupHttps) {
const filePath = sp.path;
const thisRoot = sp.root || "/";
const options = sp.options;
const middleware = sp.middleware;
if(appUseMem[filePath + "::" + thisRoot]) {
continue;// this path and root already registered!
}
@@ -435,6 +430,9 @@ httpsPromise.then(function(startupHttps) {
if (settings.httpStaticAuth) {
app.use(thisRoot, basicAuthMiddleware(settings.httpStaticAuth.user, settings.httpStaticAuth.pass));
}
if (middleware) {
app.use(thisRoot, middleware)
}
app.use(thisRoot, express.static(filePath, options));
}
}

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() {

View File

@@ -686,7 +686,7 @@ describe('Flow', function() {
},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([
{id:"t1",type:"tab"},
{id: "g1", type: "group", g: "g3" },
@@ -1311,33 +1311,42 @@ describe('Flow', function() {
})
process.env.V0 = "gv0";
process.env.V1 = "gv1";
process.env.V3 = "gv3";
var config = flowUtils.parseConfig([
{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:[
{"name": "V0", value: "v1", type: "str"},
{"name": "V1", value: "v2", type: "str"}
{"name": "V0", value: "g1v0", type: "str"},
{"name": "V1", value: "g1v1", type: "str"}
]},
{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:"2",x:10,y:10,z:"t1",g:"g1",type:"test",foo:"$(V0)",wires:[]},
{id:"3",x:10,y:10,z:"t1",g:"g1",type:"test",foo:"$(V1)",wires:[]},
{id:"4",x:10,y:10,z:"t1",g:"g2",type:"test",foo:"$(V1)",wires:[]},
{id:"5",x:10,y:10,z:"t1",type:"test",foo:"$(V1)",wires:[]},
{id:"t1__V0",x:10,y:10,z:"t1",type:"test",foo:"${V0}",wires:[]}, // V0 will come from tab env V0
{id:"t1g1V0",x:10,y:10,z:"t1",g:"g1",type:"test",foo:"${V0}",wires:[]}, // V0 will come from group 1 env V0
{id:"t1g1V1",x:10,y:10,z:"t1",g:"g1",type:"test",foo:"${V1}",wires:[]}, // V1 will come from group 1 env V1
{id:"t1g2V0",x:10,y:10,z:"t1",g:"g2",type:"test",foo:"${V0}",wires:[]}, // V0 will come from group 1 env V0
{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"]);
flow.start();
var activeNodes = flow.getActiveNodes();
activeNodes["1"].foo.should.equal("v0");
activeNodes["2"].foo.should.equal("v1");
activeNodes["3"].foo.should.equal("v2");
activeNodes["4"].foo.should.equal("v3");
activeNodes["5"].foo.should.equal("gv1");
activeNodes.t1__V0.foo.should.equal("t1v0"); // node in tab 1, get tab 1 env V0
activeNodes.t1__V1.foo.should.equal("gv1"); // node in tab 1, get V1, (tab 1 no V1) --> parent (global has V1)
activeNodes.t1g1V0.foo.should.equal("g1v0"); // node in group 1, get V0, (group 1 has V0)
activeNodes.t1g1V1.foo.should.equal("g1v1"); // node in group 1, get V1, (group 1 has V1)
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() {
done();
@@ -1347,7 +1356,6 @@ describe('Flow', function() {
console.log(e.stack);
done(e);
}
});
it("can access environment variable property using $parent", function (done) {
try {
@@ -1393,7 +1401,6 @@ describe('Flow', function() {
console.log(e.stack);
done(e);
}
});
it("can define environment variable using JSONata", function (done) {
@@ -1427,9 +1434,40 @@ describe('Flow', function() {
console.log(e.stack);
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');
// make it into a local UNC path
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();
localfilesystem.saveFlows(testFlow).then(function() {
fs.existsSync(flowFile).should.be.true();