',{style:"background: #fefefe; padding: 3px;"}).appendTo(propertiesSection.content);
+ // // var actionBar = $('
',{style:"background: #fefefe; padding: 3px;"}).appendTo(propertiesPanel);
// $('
').appendTo(actionBar);
// $('
').appendTo(actionBar);
// $('
').appendTo(actionBar);
@@ -422,6 +520,7 @@ RED.sidebar.info = (function() {
}
function startTips() {
$(".red-ui-sidebar-info").addClass('show-tips');
+ resizeStack();
if (enabled) {
if (!startTimeout && !refreshTimeout) {
if (tipCount === -1) {
@@ -435,6 +534,7 @@ RED.sidebar.info = (function() {
}
function stopTips() {
$(".red-ui-sidebar-info").removeClass('show-tips');
+ resizeStack();
clearInterval(refreshTimeout);
clearTimeout(startTimeout);
refreshTimeout = null;
@@ -459,15 +559,8 @@ RED.sidebar.info = (function() {
}
function set(html,title) {
- // tips.stop();
- // sections.show();
- refresh(null);
- propertiesSection.container.hide();
- helpSection.container.hide();
- infoSection.container.show();
- infoSection.title.text(title||RED._("sidebar.info.desc"));
- setInfoText(html,infoSection.content);
- $(".red-ui-sidebar-info-stack").scrollTop(0);
+ console.warn("Deprecated use of RED.sidebar.info.set - use RED.sidebar.help.set instead")
+ RED.sidebar.help.set(html,title);
}
function refreshSelection(selection) {
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js
index d35d1f1ba..e8306bd0f 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js
@@ -16,6 +16,28 @@
RED.utils = (function() {
+ window._marked = window.marked;
+ window.marked = function(txt) {
+ console.warn("Use of 'marked()' is deprecated. Use RED.utils.renderMarkdown() instead");
+ return renderMarkdown(txt);
+ }
+
+ _marked.setOptions({
+ renderer: new _marked.Renderer(),
+ gfm: true,
+ tables: true,
+ breaks: false,
+ pedantic: false,
+ smartLists: true,
+ smartypants: false
+ });
+
+ function renderMarkdown(txt) {
+ var rendered = _marked(txt);
+ var cleaned = DOMPurify.sanitize(rendered, {SAFE_FOR_JQUERY: true})
+ return cleaned;
+ }
+
function formatString(str) {
return str.replace(/\r?\n/g,"↵").replace(/\t/g,"→");
}
@@ -784,9 +806,9 @@ RED.utils = (function() {
function separateIconPath(icon) {
var result = {module: "", file: ""};
if (icon) {
- var index = icon.indexOf('icons/');
- if (index !== -1) {
- icon = icon.substring(index+6);
+ var index = icon.indexOf(RED.settings.apiRootUrl+'icons/');
+ if (index === 0) {
+ icon = icon.substring((RED.settings.apiRootUrl+'icons/').length);
}
index = icon.indexOf('/');
if (index !== -1) {
@@ -837,10 +859,15 @@ RED.utils = (function() {
}
function getNodeIcon(def,node) {
- if (def.category === 'config') {
+ if (node && node.type === '_selection_') {
+ return "font-awesome/fa-object-ungroup";
+ } else if (node && node.type === 'group') {
+ return "font-awesome/fa-object-group"
+ } else if (def.category === 'config') {
return RED.settings.apiRootUrl+"icons/node-red/cog.svg"
} else if (node && node.type === 'tab') {
- return RED.settings.apiRootUrl+"icons/node-red/subflow.svg"
+ return "red-ui-icons/red-ui-icons-flow"
+ // return RED.settings.apiRootUrl+"images/subflow_tab.svg"
} else if (node && node.type === 'unknown') {
return RED.settings.apiRootUrl+"icons/node-red/alert.svg"
} else if (node && node.icon) {
@@ -899,6 +926,8 @@ RED.utils = (function() {
var l;
if (node.type === 'tab') {
l = node.label || defaultLabel
+ } else if (node.type === 'group') {
+ l = node.name || defaultLabel
} else {
l = node._def.label;
try {
@@ -1032,11 +1061,63 @@ RED.utils = (function() {
}
// If the specified name is not defined in font-awesome, show arrow-in icon.
iconUrl = RED.settings.apiRootUrl+"icons/node-red/arrow-in.svg"
+ } else if (iconPath.module === "red-ui-icons") {
+ var redIconElement = $('
').appendTo(iconContainer);
+ redIconElement.addClass("red-ui-palette-icon red-ui-icons " + iconPath.file);
+ return;
}
var imageIconElement = $('
',{class:"red-ui-palette-icon"}).appendTo(iconContainer);
imageIconElement.css("backgroundImage", "url("+iconUrl+")");
}
+ function createNodeIcon(node) {
+ var def = node._def;
+ var nodeDiv = $('
',{class:"red-ui-search-result-node"})
+ if (node.type === "_selection_") {
+ nodeDiv.addClass("red-ui-palette-icon-selection");
+ } else if (node.type === "group") {
+ nodeDiv.addClass("red-ui-palette-icon-group");
+ } else if (node.type === 'tab') {
+ nodeDiv.addClass("red-ui-palette-icon-flow");
+ } else {
+ var colour = RED.utils.getNodeColor(node.type,def);
+ // if (node.type === 'tab') {
+ // colour = "#C0DEED";
+ // }
+ nodeDiv.css('backgroundColor',colour);
+ var borderColor = getDarkerColor(colour);
+ if (borderColor !== colour) {
+ nodeDiv.css('border-color',borderColor)
+ }
+ }
+
+ var icon_url = RED.utils.getNodeIcon(def,node);
+ var iconContainer = $('
',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv);
+ RED.utils.createIconElement(icon_url, iconContainer, true);
+ return nodeDiv;
+ }
+
+ function getDarkerColor(c) {
+ var r,g,b;
+ if (/^#[a-f0-9]{6}$/i.test(c)) {
+ r = parseInt(c.substring(1, 3), 16);
+ g = parseInt(c.substring(3, 5), 16);
+ b = parseInt(c.substring(5, 7), 16);
+ } else if (/^#[a-f0-9]{3}$/i.test(c)) {
+ r = parseInt(c.substring(1, 2)+c.substring(1, 2), 16);
+ g = parseInt(c.substring(2, 3)+c.substring(2, 3), 16);
+ b = parseInt(c.substring(3, 4)+c.substring(3, 4), 16);
+ } else {
+ return c;
+ }
+ var l = 0.3 * r/255 + 0.59 * g/255 + 0.11 * b/255 ;
+ r = Math.max(0,r-50);
+ g = Math.max(0,g-50);
+ b = Math.max(0,b-50);
+ var s = ((r<<16) + (g<<8) + b).toString(16);
+ return '#'+'000000'.slice(0, 6-s.length)+s;
+ }
+
return {
createObjectElement: buildMessageElement,
getMessageProperty: getMessageProperty,
@@ -1053,6 +1134,9 @@ RED.utils = (function() {
decodeObject: decodeObject,
parseContextKey: parseContextKey,
createIconElement: createIconElement,
- sanitize: sanitize
+ sanitize: sanitize,
+ renderMarkdown: renderMarkdown,
+ createNodeIcon: createNodeIcon,
+ getDarkerColor: getDarkerColor
}
})();
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js
index 0814892be..631e7ec1e 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js
@@ -67,9 +67,16 @@ RED.view.tools = (function() {
function moveSelection(dx,dy) {
if (moving_set === null) {
+ moving_set = [];
var selection = RED.view.selection();
if (selection.nodes) {
- moving_set = selection.nodes.map(function(n) { return {n:n}});
+ while (selection.nodes.length > 0) {
+ var n = selection.nodes.shift();
+ moving_set.push({n:n});
+ if (n.type === "group") {
+ selection.nodes = selection.nodes.concat(n.nodes);
+ }
+ }
}
}
if (moving_set && moving_set.length > 0) {
@@ -93,6 +100,9 @@ RED.view.tools = (function() {
node.n.x += dx;
node.n.y += dy;
node.n.dirty = true;
+ if (node.n.type === "group") {
+ RED.group.markDirty(node.n);
+ }
minX = Math.min(node.n.x-node.n.w/2-5,minX);
minY = Math.min(node.n.y-node.n.h/2-5,minY);
}
@@ -105,13 +115,86 @@ RED.view.tools = (function() {
}
}
RED.view.redraw();
+ } else {
+ RED.view.scroll(dx*10,dy*10);
}
}
+ function setSelectedNodeLabelState(labelShown) {
+ var selection = RED.view.selection();
+ var historyEvents = [];
+ var nodes = [];
+ if (selection.nodes) {
+ selection.nodes.forEach(function(n) {
+ if (n.type !== 'subflow' && n.type !== 'group') {
+ nodes.push(n);
+ } else if (n.type === 'group') {
+ nodes = nodes.concat( RED.group.getNodes(n,true));
+ }
+ });
+ }
+ nodes.forEach(function(n) {
+ var modified = false;
+ var oldValue = n.l === undefined?true:n.l;
+ var isLink = /^link (in|out)$/.test(n._def.type);
+
+ if (labelShown) {
+ if (n.l === false || (isLink && !n.hasOwnProperty('l'))) {
+ n.l = true;
+ modified = true;
+ }
+ } else {
+ if ((!isLink && (!n.hasOwnProperty('l') || n.l === true)) || (isLink && n.l === true) ) {
+ n.l = false;
+ modified = true;
+ }
+ }
+ if (modified) {
+ historyEvents.push({
+ t: "edit",
+ node: n,
+ changed: n.changed,
+ changes: {
+ l: oldValue
+ }
+ })
+ n.changed = true;
+ n.dirty = true;
+ n.resize = true;
+ }
+ })
+
+ if (historyEvents.length > 0) {
+ RED.history.push({
+ t: "multi",
+ events: historyEvents,
+ dirty: RED.nodes.dirty()
+ })
+ RED.nodes.dirty(true);
+ }
+
+ RED.view.redraw();
+
+
+ }
+
return {
init: function() {
+ RED.actions.add("core:show-selected-node-labels", function() { setSelectedNodeLabelState(true); })
+ RED.actions.add("core:hide-selected-node-labels", function() { setSelectedNodeLabelState(false); })
+
RED.actions.add("core:align-selection-to-grid", alignToGrid);
+ RED.actions.add("core:scroll-view-up", function() { RED.view.scroll(0,-RED.view.gridSize());});
+ RED.actions.add("core:scroll-view-right", function() { RED.view.scroll(RED.view.gridSize(),0);});
+ RED.actions.add("core:scroll-view-down", function() { RED.view.scroll(0,RED.view.gridSize());});
+ RED.actions.add("core:scroll-view-left", function() { RED.view.scroll(-RED.view.gridSize(),0);});
+
+ RED.actions.add("core:step-view-up", function() { RED.view.scroll(0,-5*RED.view.gridSize());});
+ RED.actions.add("core:step-view-right", function() { RED.view.scroll(5*RED.view.gridSize(),0);});
+ RED.actions.add("core:step-view-down", function() { RED.view.scroll(0,5*RED.view.gridSize());});
+ RED.actions.add("core:step-view-left", function() { RED.view.scroll(-5*RED.view.gridSize(),0);});
+
RED.actions.add("core:move-selection-up", function() { moveSelection(0,-1);});
RED.actions.add("core:move-selection-right", function() { moveSelection(1,0);});
RED.actions.add("core:move-selection-down", function() { moveSelection(0,1);});
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js
index 7a561d9cd..7f541204e 100755
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js
@@ -21,12 +21,15 @@
* \-
.red-ui-workspace-chart-event-layer "eventLayer"
* |- .red-ui-workspace-chart-background
* |- .red-ui-workspace-chart-grid "gridLayer"
+ * |- "groupLayer"
+ * |- "groupSelectLayer"
* |- "linkLayer"
* |- "dragGroupLayer"
- * \- "nodeLayer"
+ * |- "nodeLayer"
*/
RED.view = (function() {
+ var DEBUG_EVENTS = false;
var space_width = 5000,
space_height = 5000,
lineCurveScale = 0.75,
@@ -48,34 +51,42 @@ RED.view = (function() {
var activeSpliceLink;
var spliceActive = false;
var spliceTimer;
+ var groupHoverTimer;
var activeSubflow = null;
var activeNodes = [];
var activeLinks = [];
var activeFlowLinks = [];
var activeLinkNodes = {};
+ var activeGroup = null;
+ var activeHoverGroup = null;
+ var activeGroups = [];
+ var dirtyGroups = {};
- var selected_link = null,
- mousedown_link = null,
- mousedown_node = null,
- mousedown_port_type = null,
- mousedown_port_index = 0,
- mouseup_node = null,
- mouse_offset = [0,0],
- mouse_position = null,
- mouse_mode = 0,
- moving_set = [],
- lasso = null,
- ghostNode = null,
- showStatus = false,
- lastClickNode = null,
- dblClickPrimed = null,
- clickTime = 0,
- clickElapsed = 0,
- scroll_position = [],
- quickAddActive = false,
- quickAddLink = null,
- showAllLinkPorts = -1;
+ var selected_link = null;
+ var mousedown_link = null;
+ var mousedown_node = null;
+ var mousedown_group = null;
+ var mousedown_port_type = null;
+ var mousedown_port_index = 0;
+ var mouseup_node = null;
+ var mouse_offset = [0,0];
+ var mouse_position = null;
+ var mouse_mode = 0;
+ var mousedown_group_handle = null;
+ var moving_set = [];
+ var lasso = null;
+ var ghostNode = null;
+ var showStatus = false;
+ var lastClickNode = null;
+ var dblClickPrimed = null;
+ var clickTime = 0;
+ var clickElapsed = 0;
+ var scroll_position = [];
+ var quickAddActive = false;
+ var quickAddLink = null;
+ var showAllLinkPorts = -1;
+ var groupNodeSelectPrimed = false;
var selectNodesOptions;
@@ -100,7 +111,9 @@ RED.view = (function() {
var gridLayer;
var linkLayer;
var dragGroupLayer;
+ var groupSelectLayer;
var nodeLayer;
+ var groupLayer;
var drag_lines;
function init() {
@@ -253,9 +266,12 @@ RED.view = (function() {
gridLayer = eventLayer.append("g").attr("class","red-ui-workspace-chart-grid");
updateGrid();
+ groupLayer = eventLayer.append("g");
+ groupSelectLayer = eventLayer.append("g");
linkLayer = eventLayer.append("g");
dragGroupLayer = eventLayer.append("g");
nodeLayer = eventLayer.append("g");
+
drag_lines = [];
RED.events.on("workspace:change",function(event) {
@@ -377,14 +393,36 @@ RED.view = (function() {
historyEvent.removedLinks = [spliceLink];
}
- RED.history.push(historyEvent);
RED.nodes.add(nn);
+
+ var group = $(ui.helper).data("group");
+ if (group) {
+ RED.group.addToGroup(group, nn);
+ historyEvent = {
+ t: 'multi',
+ events: [historyEvent],
+
+ }
+ historyEvent.events.push({
+ t: "addToGroup",
+ group: group,
+ nodes: nn
+ })
+ }
+
+ RED.history.push(historyEvent);
RED.editor.validateNode(nn);
RED.nodes.dirty(true);
// auto select dropped node - so info shows (if visible)
+ exitActiveGroup();
clearSelection();
nn.selected = true;
moving_set.push({n:nn});
+ if (group) {
+ selectGroup(group,false);
+ enterActiveGroup(group);
+ activeGroup = group;
+ }
updateActiveNodes();
updateSelection();
redraw();
@@ -517,6 +555,53 @@ RED.view = (function() {
source:{z:activeWorkspace},
target:{z:activeWorkspace}
});
+
+ activeGroups = RED.nodes.groups(activeWorkspace)||[];
+ activeGroups.forEach(function(g) {
+ if (g.g) {
+ g._root = g.g;
+ g._depth = 1;
+ } else {
+ g._root = g.id;
+ g._depth = 0;
+ }
+ });
+ var changed = false;
+ do {
+ changed = false;
+ activeGroups.forEach(function(g) {
+ if (g.g) {
+ var parentGroup = RED.nodes.group(g.g);
+ if (parentGroup) {
+ var parentDepth = parentGroup._depth;
+ if (g._depth !== parentDepth + 1) {
+ g._depth = parentDepth + 1;
+ changed = true;
+ }
+ if (g._root !== parentGroup._root) {
+ g._root = parentGroup._root;
+ changed = true;
+ }
+ }
+ }
+ });
+ } while (changed)
+ activeGroups.sort(function(a,b) {
+ if (a._root === b._root) {
+ return a._depth - b._depth;
+ } else {
+ return a._root.localeCompare(b._root);
+ }
+ });
+
+ var group = groupLayer.selectAll(".red-ui-flow-group").data(activeGroups,function(d) { return d.id });
+ group.sort(function(a,b) {
+ if (a._root === b._root) {
+ return a._depth - b._depth;
+ } else {
+ return a._root.localeCompare(b._root);
+ }
+ })
}
function generateLinkPath(origX,origY, destX, destY, sc) {
@@ -671,6 +756,7 @@ RED.view = (function() {
}
function canvasMouseDown() {
+if (DEBUG_EVENTS) { console.warn("canvasMouseDown", mouse_mode); }
var point;
if (mouse_mode === RED.state.SELECTING_NODE) {
d3.event.stopPropagation();
@@ -684,7 +770,7 @@ RED.view = (function() {
scroll_position = [chart.scrollLeft(),chart.scrollTop()];
return;
}
- if (!mousedown_node && !mousedown_link) {
+ if (!mousedown_node && !mousedown_link && !mousedown_group) {
selected_link = null;
updateSelection();
}
@@ -697,7 +783,13 @@ RED.view = (function() {
if (mouse_mode === 0 || mouse_mode === RED.state.QUICK_JOINING) {
if (d3.event.metaKey || d3.event.ctrlKey) {
d3.event.stopPropagation();
- showQuickAddDialog(d3.mouse(this));
+ clearSelection();
+ point = d3.mouse(this);
+ var clickedGroup = getGroupAt(point[0],point[1]);
+ if (drag_lines.length > 0) {
+ clickedGroup = clickedGroup || RED.nodes.group(drag_lines[0].node.g)
+ }
+ showQuickAddDialog(point, null, clickedGroup);
}
}
if (mouse_mode === 0 && !(d3.event.metaKey || d3.event.ctrlKey)) {
@@ -718,7 +810,13 @@ RED.view = (function() {
}
}
- function showQuickAddDialog(point,spliceLink) {
+ function showQuickAddDialog(point, spliceLink, targetGroup) {
+ if (targetGroup && !targetGroup.active) {
+ selectGroup(targetGroup,false);
+ enterActiveGroup(targetGroup);
+ RED.view.redraw();
+ }
+
var ox = point[0];
var oy = point[1];
@@ -946,6 +1044,22 @@ RED.view = (function() {
}
}
}
+ if (targetGroup) {
+ RED.group.addToGroup(targetGroup, nn);
+ if (historyEvent.t !== "multi") {
+ historyEvent = {
+ t:'multi',
+ events: [historyEvent]
+ }
+ }
+ historyEvent.events.push({
+ t: "addToGroup",
+ group: targetGroup,
+ nodes: nn
+ })
+
+ }
+
if (spliceLink) {
resetMouseVars();
// TODO: DRY - droppable/nodeMouseDown/canvasMouseUp/showQuickAddDialog
@@ -972,6 +1086,10 @@ RED.view = (function() {
// auto select dropped node - so info shows (if visible)
clearSelection();
nn.selected = true;
+ if (targetGroup) {
+ selectGroup(targetGroup,false);
+ enterActiveGroup(targetGroup);
+ }
moving_set.push({n:nn});
updateActiveNodes();
updateSelection();
@@ -1076,11 +1194,23 @@ RED.view = (function() {
return;
}
- if (mouse_mode != RED.state.QUICK_JOINING && mouse_mode != RED.state.IMPORT_DRAGGING && !mousedown_node && selected_link == null) {
+ if (mouse_mode != RED.state.QUICK_JOINING && mouse_mode != RED.state.IMPORT_DRAGGING && !mousedown_node && !mousedown_group && selected_link == null) {
return;
}
var mousePos;
+ // if (mouse_mode === RED.state.GROUP_RESIZE) {
+ // mousePos = mouse_position;
+ // var nx = mousePos[0] + mousedown_group.dx;
+ // var ny = mousePos[1] + mousedown_group.dy;
+ // switch(mousedown_group.activeHandle) {
+ // case 0: mousedown_group.pos.x0 = nx; mousedown_group.pos.y0 = ny; break;
+ // case 1: mousedown_group.pos.x1 = nx; mousedown_group.pos.y0 = ny; break;
+ // case 2: mousedown_group.pos.x1 = nx; mousedown_group.pos.y1 = ny; break;
+ // case 3: mousedown_group.pos.x0 = nx; mousedown_group.pos.y1 = ny; break;
+ // }
+ // mousedown_group.dirty = true;
+ // }
if (mouse_mode == RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) {
// update drag line
if (drag_lines.length === 0 && mousedown_port_type !== null) {
@@ -1182,10 +1312,18 @@ RED.view = (function() {
node.n.x = mousePos[0]+node.dx;
node.n.y = mousePos[1]+node.dy;
node.n.dirty = true;
- minX = Math.min(node.n.x-node.n.w/2-5,minX);
- minY = Math.min(node.n.y-node.n.h/2-5,minY);
- maxX = Math.max(node.n.x+node.n.w/2+5,maxX);
- maxY = Math.max(node.n.y+node.n.h/2+5,maxY);
+ if (node.n.type === "group") {
+ RED.group.markDirty(node.n);
+ minX = Math.min(node.n.x-5,minX);
+ minY = Math.min(node.n.y-5,minY);
+ maxX = Math.max(node.n.x+node.n.w+5,maxX);
+ maxY = Math.max(node.n.y+node.n.h+5,maxY);
+ } else {
+ minX = Math.min(node.n.x-node.n.w/2-5,minX);
+ minY = Math.min(node.n.y-node.n.h/2-5,minY);
+ maxX = Math.max(node.n.x+node.n.w/2+5,maxX);
+ maxY = Math.max(node.n.y+node.n.h/2+5,maxY);
+ }
}
if (minX !== 0 || minY !== 0) {
for (i = 0; i 0) {
- var gridOffset = [0,0];
- node = moving_set[0];
- gridOffset[0] = node.n.x-(gridSize*Math.floor((node.n.x-node.n.w/2)/gridSize)+node.n.w/2);
- gridOffset[1] = node.n.y-(gridSize*Math.floor(node.n.y/gridSize));
+ var i = 0;
+
+ // Prefer to snap nodes to the grid if there is one in the selection
+ do {
+ node = moving_set[i++];
+ } while(i x && n.x < x2 && n.y > y && n.y < y2);
- if (n.selected) {
- n.dirty = true;
- moving_set.push({n:n});
+ activeGroups.forEach(function(g) {
+ if (!g.selected) {
+ if (g.x > x && g.x+g.w < x2 && g.y > y && g.y+g.h < y2) {
+ while (g.g) {
+ g = RED.nodes.group(g.g);
+ }
+ if (!g.selected) {
+ selectGroup(g,true);
+ }
+ }
+ }
+ })
+
+ activeNodes.forEach(function(n) {
+ if (!n.selected) {
+ if (n.x > x && n.x < x2 && n.y > y && n.y < y2) {
+ if (n.g) {
+ var group = RED.nodes.group(n.g);
+ while (group.g) {
+ group = RED.nodes.group(group.g);
+ }
+ if (!group.selected) {
+ selectGroup(group,true);
+ }
+ } else {
+ n.selected = true;
+ n.dirty = true;
+ moving_set.push({n:n});
+ }
}
}
});
+
+
+
+ // var selectionChanged = false;
+ // do {
+ // selectionChanged = false;
+ // selectedGroups.forEach(function(g) {
+ // if (g.g && g.selected && RED.nodes.group(g.g).selected) {
+ // g.selected = false;
+ // selectionChanged = true;
+ // }
+ // })
+ // } while(selectionChanged);
+
if (activeSubflow) {
activeSubflow.in.forEach(function(n) {
n.selected = (n.x > x && n.x < x2 && n.y > y && n.y < y2);
@@ -1355,6 +1568,21 @@ RED.view = (function() {
}
if (mouse_mode == RED.state.MOVING_ACTIVE) {
if (moving_set.length > 0) {
+ var addedToGroup = null;
+ if (activeHoverGroup) {
+ for (var j=0;j 0) {
historyEvent = {t:"move",nodes:ns,dirty:RED.nodes.dirty()};
if (activeSpliceLink) {
@@ -1386,12 +1615,31 @@ RED.view = (function() {
historyEvent.removedLinks = [spliceLink];
updateActiveNodes();
}
+ if (addedToGroup) {
+ historyEvent.addToGroup = addedToGroup;
+ }
RED.nodes.dirty(true);
RED.history.push(historyEvent);
}
}
}
+ // if (mouse_mode === RED.state.MOVING && mousedown_node && mousedown_node.g) {
+ // if (mousedown_node.gSelected) {
+ // delete mousedown_node.gSelected
+ // } else {
+ // if (!d3.event.ctrlKey && !d3.event.metaKey) {
+ // clearSelection();
+ // }
+ // RED.nodes.group(mousedown_node.g).selected = true;
+ // mousedown_node.selected = true;
+ // mousedown_node.dirty = true;
+ // moving_set.push({n:mousedown_node});
+ // }
+ // }
if (mouse_mode == RED.state.MOVING || mouse_mode == RED.state.MOVING_ACTIVE) {
+ // if (mousedown_node) {
+ // delete mousedown_node.gSelected;
+ // }
for (i=0;i 0) {
- selection.nodes = moving_set.map(function(n) { return n.n;});
- }
- if (selected_link != null) {
- selection.link = selected_link;
- }
+ selection = getSelection();
activeLinks = RED.nodes.filterLinks({
source:{z:activeWorkspace},
target:{z:activeWorkspace}
@@ -1606,6 +1872,8 @@ RED.view = (function() {
var node = moving_set[0].n;
if (node.type === "subflow") {
RED.editor.editSubflow(activeSubflow);
+ } else if (node.type === "group") {
+ RED.editor.editGroup(node);
} else {
RED.editor.edit(node);
}
@@ -1632,6 +1900,7 @@ RED.view = (function() {
dirty: RED.nodes.dirty(),
nodes: [],
links: [],
+ groups: [],
workspaces: [],
subflows: []
}
@@ -1651,6 +1920,7 @@ RED.view = (function() {
}
historyEvent.nodes = historyEvent.nodes.concat(subEvent.nodes);
historyEvent.links = historyEvent.links.concat(subEvent.links);
+ historyEvent.groups = historyEvent.groups.concat(subEvent.groups);
}
RED.history.push(historyEvent);
RED.nodes.dirty(true);
@@ -1661,6 +1931,7 @@ RED.view = (function() {
var result;
var removedNodes = [];
var removedLinks = [];
+ var removedGroups = [];
var removedSubflowOutputs = [];
var removedSubflowInputs = [];
var removedSubflowStatus;
@@ -1668,11 +1939,14 @@ RED.view = (function() {
var startDirty = RED.nodes.dirty();
var startChanged = false;
+ var selectedGroups = [];
if (moving_set.length > 0) {
for (var i=0;i 0) {
+ var g = selectedGroups.shift();
+ if (removedGroups.indexOf(g) === -1) {
+ removedGroups.push(g);
+ g.nodes.forEach(function(n) {
+ if (n.type === "group") {
+ selectedGroups.push(n);
+ }
+ })
+ RED.nodes.removeGroup(g);
+ }
+ }
if (removedSubflowOutputs.length > 0) {
result = RED.subflow.removeOutput(removedSubflowOutputs);
if (result) {
@@ -1716,7 +2009,7 @@ RED.view = (function() {
subflowInstances = instances.instances;
}
moving_set = [];
- if (removedNodes.length > 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0 || removedSubflowStatus) {
+ if (removedNodes.length > 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0 || removedSubflowStatus || removedGroups.length > 0) {
RED.nodes.dirty(true);
}
}
@@ -1769,6 +2062,7 @@ RED.view = (function() {
t:"delete",
nodes:removedNodes,
links:removedLinks,
+ groups: removedGroups,
subflowOutputs:removedSubflowOutputs,
subflowInputs:removedSubflowInputs,
subflow: {
@@ -1801,20 +2095,36 @@ RED.view = (function() {
selection.forEach(function(n) {
if (n.type === 'tab') {
nodes.push(n);
+ nodes = nodes.concat(RED.nodes.groups(n.id));
nodes = nodes.concat(RED.nodes.filterNodes({z:n.id}));
}
});
- } else if (moving_set.length > 0) {
- nodes = moving_set.map(function(n) { return n.n });
+ } else {
+ selection = RED.view.selection();
+ if (selection.nodes) {
+ selection.nodes.forEach(function(n) {
+ nodes.push(n);
+ if (n.type === 'group') {
+ nodes = nodes.concat(RED.group.getNodes(n,true));
+ }
+ })
+ }
}
if (nodes.length > 0) {
var nns = [];
+ var nodeCount = 0;
+ var groupCount = 0;
for (var n=0;n 0) {
+ RED.notify(RED._("clipboard.nodeCopied",{count:nodeCount}),{id:"clipboard"});
+ } else if (groupCount > 0) {
+ RED.notify(RED._("clipboard.groupCopied",{count:groupCount}),{id:"clipboard"});
+ }
}
}
function calculateTextWidth(str, className, offset) {
- return calculateTextDimensions(str,className,offset,0)[0];
+ var result=convertLineBreakCharacter(str);
+ var width = 0;
+ for (var i=0;i 0 ? 1: 0) : (d.direction == "in" ? 0: 1)
var wasJoining = false;
if (mouse_mode === RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) {
@@ -2276,7 +2666,24 @@ RED.view = (function() {
}
}
+ function prepareDrag(mouse) {
+ mouse_mode = RED.state.MOVING;
+ // Called when moving_set should be prepared to be dragged
+ for (i=0;i 0 && clickElapsed < 750) {
+ mouse_mode = RED.state.DEFAULT;
+ RED.editor.editGroup(g);
+ d3.event.stopPropagation();
+ return;
+ }
+
+ }
+
+ function groupMouseDown(g) {
+ var mouse = d3.touches(this.parentNode)[0]||d3.mouse(this.parentNode);
+ // if (! (mouse[0] < g.x+10 || mouse[0] > g.x+g.w-10 || mouse[1] < g.y+10 || mouse[1] > g.y+g.h-10) ) {
+ // return
+ // }
+
+ focusView();
+ if (d3.event.button === 1) {
+ return;
+ }
+ if (mouse_mode == RED.state.IMPORT_DRAGGING) {
+ RED.keyboard.remove("escape");
+ } else if (mouse_mode == RED.state.QUICK_JOINING) {
+ d3.event.stopPropagation();
+ return;
+ } else if (mouse_mode === RED.state.SELECTING_NODE) {
+ d3.event.stopPropagation();
+ return;
+ }
+
+ mousedown_group = g;
+
+ var now = Date.now();
+ clickElapsed = now-clickTime;
+ clickTime = now;
+
+ dblClickPrimed = (
+ lastClickNode == g &&
+ d3.event.button === 0 &&
+ !d3.event.shiftKey && !d3.event.metaKey && !d3.event.altKey && !d3.event.ctrlKey
+ );
+ lastClickNode = g;
+
+ if (g.selected && (d3.event.ctrlKey||d3.event.metaKey)) {
+ if (g === activeGroup) {
+ exitActiveGroup();
+ }
+ deselectGroup(g);
+ d3.event.stopPropagation();
+ } else {
+ if (!g.selected) {
+ if (!d3.event.ctrlKey && !d3.event.metaKey) {
+ clearSelection();
+ }
+ if (activeGroup) {
+ if (!RED.group.contains(activeGroup,g)) {
+ // Clicked on a group that is outside the activeGroup
+ exitActiveGroup();
+ } else {
+ }
+ }
+ selectGroup(g,true);//!wasSelected);
+ } else {
+ exitActiveGroup();
+ }
+
+
+ if (d3.event.button != 2) {
+ var d = g.nodes[0];
+ prepareDrag(mouse);
+ mousedown_group.dx = mousedown_group.x - mouse[0];
+ mousedown_group.dy = mousedown_group.y - mouse[1];
+ }
+ }
+
+ updateSelection();
+ redraw();
+ d3.event.stopPropagation();
+ }
+
+ function selectGroup(g, includeNodes) {
+ if (!g.selected) {
+ g.selected = true;
+ g.dirty = true;
+ }
+ moving_set.push({n:g});
+ if (includeNodes) {
+ var currentSet = new Set(moving_set.map(function(n) { return n.n }));
+ var allNodes = RED.group.getNodes(g,true);
+ allNodes.forEach(function(n) {
+ if (!currentSet.has(n)) {
+ moving_set.push({n:n})
+ // n.selected = true;
+ }
+ n.dirty = true;
+ })
+ }
+ }
+ function enterActiveGroup(group) {
+ if (activeGroup) {
+ exitActiveGroup();
+ }
+ group.active = true;
+ group.dirty = true;
+ activeGroup = group;
+ for (var i = moving_set.length-1; i >= 0; i -= 1) {
+ if (moving_set[i].n === group) {
+ moving_set.splice(i,1);
+ break;
+ }
+ }
+ }
+ function exitActiveGroup() {
+ if (activeGroup) {
+ activeGroup.active = false;
+ activeGroup.dirty = true;
+ deselectGroup(activeGroup);
+ selectGroup(activeGroup,true);
+ activeGroup = null;
+ }
+ }
+ function deselectGroup(g) {
+ if (g.selected) {
+ g.selected = false;
+ g.dirty = true;
+ }
+ var nodeSet = new Set(g.nodes);
+ nodeSet.add(g);
+ for (var i = moving_set.length-1; i >= 0; i -= 1) {
+ if (nodeSet.has(moving_set[i].n) || moving_set[i].n === g) {
+ moving_set[i].n.selected = false;
+ moving_set[i].n.dirty = true;
+ moving_set.splice(i,1);
+ }
+ }
+ }
+ function getGroupAt(x,y) {
+ var candidateGroups = {};
+ for (var i=0;i= g.x && x <= g.x + g.w && y >= g.y && y <= g.y + g.h) {
+ candidateGroups[g.id] = g;
+ }
+ }
+ var ids = Object.keys(candidateGroups);
+ if (ids.length > 1) {
+ ids.forEach(function(id) {
+ if (candidateGroups[id] && candidateGroups[id].g) {
+ delete candidateGroups[candidateGroups[id].g]
+ }
+ })
+ ids = Object.keys(candidateGroups);
+ }
+ if (ids.length === 0) {
+ return null;
+ } else {
+ return candidateGroups[ids[ids.length-1]]
+ }
+ }
+
function isButtonEnabled(d) {
var buttonEnabled = true;
var ws = RED.nodes.workspace(RED.workspaces.active());
@@ -2424,7 +3066,9 @@ RED.view = (function() {
function nodeButtonClicked(d) {
if (mouse_mode === RED.state.SELECTING_NODE) {
- d3.event.stopPropagation();
+ if (d3.event) {
+ d3.event.stopPropagation();
+ }
return;
}
var activeWorkspace = RED.workspaces.active();
@@ -2451,7 +3095,9 @@ RED.view = (function() {
RED.notify(RED._("notification.warning", {message:RED._("notification.warnings.nodeActionDisabled")}),"warning");
}
}
- d3.event.preventDefault();
+ if (d3.event) {
+ d3.event.preventDefault();
+ }
}
function showTouchMenu(obj,pos) {
@@ -2523,6 +3169,10 @@ RED.view = (function() {
}
function redraw() {
+ requestAnimationFrame(_redraw);
+ }
+
+ function _redraw() {
eventLayer.attr("transform","scale("+scaleFactor+")");
outer.attr("width", space_width*scaleFactor).attr("height", space_height*scaleFactor);
@@ -2702,23 +3352,26 @@ RED.view = (function() {
var nodeEnter = node.enter().insert("svg:g")
.attr("class", "red-ui-flow-node red-ui-flow-node-group")
- .classed("red-ui-flow-subflow",function(d) { return activeSubflow != null; });
+ .classed("red-ui-flow-subflow", activeSubflow != null);
nodeEnter.each(function(d,i) {
var node = d3.select(this);
var isLink = (d.type === "link in" || d.type === "link out")
var hideLabel = d.hasOwnProperty('l')?!d.l : isLink;
node.attr("id",d.id);
- var l = RED.utils.getNodeLabel(d);
+ var labelWidth = calculateTextWidth(RED.utils.getNodeLabel(d), "red-ui-flow-node-label", 50);
if (d.resize || d.w === undefined) {
if (hideLabel) {
d.w = node_height;
} else {
- d.w = Math.max(node_width,20*(Math.ceil((calculateTextWidth(l, "red-ui-flow-node-label", 50)+(d._def.inputs>0?7:0))/20)) );
+ d.w = Math.max(node_width,20*(Math.ceil((labelWidth+(d._def.inputs>0?7:0))/20)) );
}
}
- d.h = Math.max(node_height,(d.outputs||0) * 15);
-
+ if (hideLabel) {
+ d.h = Math.max(node_height,(d.outputs || 0) * 15);
+ } else {
+ d.h = Math.max(6+24*separateTextByLineBreak.length, (d.outputs || 0) * 15, 30);
+ }
// if (d._def.badge) {
// var badge = node.append("svg:g").attr("class","node_badge_group");
// var badgeRect = badge.append("rect").attr("class","node_badge").attr("rx",5).attr("ry",5).attr("width",40).attr("height",15);
@@ -2765,10 +3418,10 @@ RED.view = (function() {
var mainRect = node.append("rect")
.attr("class", "red-ui-flow-node")
- .classed("red-ui-flow-node-unknown",function(d) { return d.type == "unknown"; })
+ .classed("red-ui-flow-node-unknown", d.type == "unknown")
.attr("rx", 5)
.attr("ry", 5)
- .attr("fill",function(d) { return RED.utils.getNodeColor(d.type,d._def); /*d._def.color;*/})
+ .attr("fill", RED.utils.getNodeColor(d.type,d._def))
.on("mouseup",nodeMouseUp)
.on("mousedown",nodeMouseDown)
.on("touchstart",function(d) {
@@ -2876,17 +3529,17 @@ RED.view = (function() {
.attr("x",0).attr("y",0)
.attr("class","red-ui-flow-node-icon-shade")
.attr("width","30")
- .attr("height",function(d){return Math.min(50,d.h-4);});
+ .attr("height", Math.min(50,d.h-4));
createIconAttributes(icon_url, icon_group, d);
var icon_shade_border = icon_group.append("path")
- .attr("d",function(d) { return "M 30 1 l 0 "+(d.h-2)})
+ .attr("d","M 30 1 l 0 "+(d.h-2))
.attr("class","red-ui-flow-node-icon-shade-border");
if ("right" == d._def.align) {
icon_group.attr("class","red-ui-flow-node-icon-group red-ui-flow-node-icon-group-"+d._def.align);
- icon_shade_border.attr("d",function(d) { return "M 0 1 l 0 "+(d.h-2)})
+ icon_shade_border.attr("d", "M 0 1 l 0 "+(d.h-2))
//icon.attr("class","red-ui-flow-node-icon red-ui-flow-node-icon-"+d._def.align);
//icon.attr("class","red-ui-flow-node-icon-shade red-ui-flow-node-icon-shade-"+d._def.align);
//icon.attr("class","red-ui-flow-node-icon-shade-border red-ui-flow-node-icon-shade-border-"+d._def.align);
@@ -2904,17 +3557,22 @@ RED.view = (function() {
//icon.style("pointer-events","none");
icon_group.style("pointer-events","none");
}
- var text = node.append("svg:text")
+ var labelLineNumber = (separateTextByLineBreak.length == 0)? 1:separateTextByLineBreak.length;
+ var labelId = d.id.replace(".","-");
+ for(var i=0;i0?7:0))/20)) );
+ d.w = Math.max(node_width,20*(Math.ceil((labelWidth+(d._def.inputs>0?7:0))/20)) );
}
// d.w = Math.max(node_width,20*(Math.ceil((calculateTextWidth(l, "red-ui-flow-node-label", 50)+(d._def.inputs>0?7:0))/20)) );
- d.h = Math.max(node_height,(d.outputs||0) * 15);
d.x += (d.w-ow)/2;
d.resize = false;
}
+ if (hideLabel) {
+ d.h = Math.max(node_height,(d.outputs || 0) * 15);
+ } else {
+ d.h = Math.max(6+24*separateTextByLineBreak.length,(d.outputs || 0) * 15, 30);
+ }
+
var thisNode = d3.select(this);
- thisNode.classed("red-ui-flow-node-disabled", function(d) { return d.d === true});
- thisNode.classed("red-ui-flow-subflow",function(d) { return activeSubflow != null; })
+ thisNode.classed("red-ui-flow-node-disabled", d.d === true);
+ thisNode.classed("red-ui-flow-subflow", activeSubflow != null)
//thisNode.selectAll(".centerDot").attr({"cx":function(d) { return d.w/2;},"cy":function(d){return d.h/2}});
- thisNode.attr("transform", function(d) { return "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"; });
+ thisNode.attr("transform", "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")");
if (mouse_mode != RED.state.MOVING_ACTIVE) {
- thisNode.classed("red-ui-flow-node-selected",function(d) { return d.selected })
+ thisNode.classed("red-ui-flow-node-selected", d.selected )
thisNode.selectAll(".red-ui-flow-node")
- .attr("width",function(d){return d.w})
- .attr("height",function(d){return d.h})
- .classed("red-ui-flow-node-highlighted",function(d) { return d.highlighted; })
+ .attr("width", d.w)
+ .attr("height", d.h)
+ .classed("red-ui-flow-node-highlighted",d.highlighted )
;
+ var l = "";
+ if (d._def.label) {
+ l = d._def.label;
+ try {
+ l = (typeof l === "function" ? l.call(d) : l)||"";
+ l = RED.text.bidi.enforceTextDirectionWithUCC(l);
+ } catch(err) {
+ console.log("Definition error: "+d.type+".label",err);
+ l = d.type;
+ }
+ }
+ var sa = convertLineBreakCharacter(l);
+ var sn = sa.length;
+ var st = "";
+ var yp = d.h / 2 - (sn / 2) * 24 + 16
+ var labelId = d.id.replace(".","-");
+ if(labelLineNumber0?5:0);});
//thisNode.selectAll(".red-ui-flow-node-icon-shade-right").attr("x",function(d){return d.w-30;});
//thisNode.selectAll(".red-ui-flow-node-icon-shade-border-right").attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2)});
@@ -3067,35 +3790,6 @@ RED.view = (function() {
port.attr("transform", function(d) { return "translate("+x+","+((y+13*i)-5)+")";});
});
}
- thisNode.selectAll("text.red-ui-flow-node-label").text(function(d,i){
- var l = "";
- if (d._def.label) {
- l = d._def.label;
- try {
- l = (typeof l === "function" ? l.call(d) : l)||"";
- l = RED.text.bidi.enforceTextDirectionWithUCC(l);
- } catch(err) {
- console.log("Definition error: "+d.type+".label",err);
- l = d.type;
- }
- }
- return l;
- })
- .attr("y", function(d){return (d.h/2)-1;})
- .attr("class",function(d){
- var s = "";
- if (d._def.labelStyle) {
- s = d._def.labelStyle;
- try {
- s = (typeof s === "function" ? s.call(d) : s)||"";
- } catch(err) {
- console.log("Definition error: "+d.type+".labelStyle",err);
- s = "";
- }
- s = " "+s;
- }
- return "red-ui-flow-node-label"+(d._def.align?" red-ui-flow-node-label-"+d._def.align:"")+s;
- }).classed("hide",hideLabel);
if (d._def.icon) {
var icon = thisNode.select(".red-ui-flow-node-icon");
var faIcon = thisNode.select(".fa-lg");
@@ -3118,12 +3812,12 @@ RED.view = (function() {
}
thisNode.selectAll(".red-ui-flow-node-changed")
- .attr("transform",function(d){return "translate("+(d.w-10)+", -2)"})
- .classed("hide",function(d) { return !(d.changed||d.moved); });
+ .attr("transform", "translate("+(d.w-10)+", -2)")
+ .classed("hide", !(d.changed||d.moved));
thisNode.selectAll(".red-ui-flow-node-error")
- .attr("transform",function(d){ return "translate("+(d.w-10-((d.changed||d.moved)?14:0))+", -2)"})
- .classed("hide",function(d) { return d.valid; });
+ .attr("transform", "translate("+(d.w-10-((d.changed||d.moved)?14:0))+", -2)")
+ .classed("hide", d.valid);
thisNode.selectAll(".red-ui-flow-port-input").each(function(d,i) {
var port = d3.select(this);
@@ -3131,18 +3825,14 @@ RED.view = (function() {
});
thisNode.selectAll(".red-ui-flow-node-icon").attr("y",function(d){return (d.h-d3.select(this).attr("height"))/2;});
- thisNode.selectAll(".red-ui-flow-node-icon-shade").attr("height",function(d){return d.h;});
- thisNode.selectAll(".red-ui-flow-node-icon-shade-border").attr("d", function (d) {
- return "M " + (((!d._def.align && d.inputs !== 0 && d.outputs === 0) || "right" === d._def.align) ? 0 : 30) + " 1 l 0 " + (d.h - 2);
- });
- thisNode.selectAll(".fa-lg").attr("y",function(d){return (d.h+13)/2;});
+ thisNode.selectAll(".red-ui-flow-node-icon-shade").attr("height", d.h );
+ thisNode.selectAll(".red-ui-flow-node-icon-shade-border").attr("d",
+ "M " + (((!d._def.align && d.inputs !== 0 && d.outputs === 0) || "right" === d._def.align) ? 0 : 30) + " 1 l 0 " + (d.h - 2)
+ );
+ thisNode.selectAll(".fa-lg").attr("y",(d.h+13)/2);
- thisNode.selectAll(".red-ui-flow-node-button").attr("opacity",function(d) {
- return (!isButtonEnabled(d))?0.4:1
- });
- thisNode.selectAll(".red-ui-flow-node-button-button").attr("cursor",function(d) {
- return (!isButtonEnabled(d))?"":"pointer";
- });
+ thisNode.selectAll(".red-ui-flow-node-button").attr("opacity", function(d2) { return !isButtonEnabled(d2)?0.4:1 });
+ thisNode.selectAll(".red-ui-flow-node-button-button").attr("cursor",function(d2) { return isButtonEnabled(d2)?"":"pointer"});
thisNode.selectAll(".red-ui-flow-node-button").attr("transform",function(d){
var x = d._def.align == "right"?d.w-6:-25;
if (d._def.button.toggle && !d[d._def.button.toggle]) {
@@ -3208,6 +3898,15 @@ RED.view = (function() {
}
d.dirty = false;
+ if (d.g) {
+ if (!dirtyGroups[d.g]) {
+ var gg = d.g;
+ while (gg && !dirtyGroups[gg]) {
+ dirtyGroups[gg] = RED.nodes.group(gg);
+ gg = dirtyGroups[gg].g;
+ }
+ }
+ }
}
});
@@ -3238,7 +3937,9 @@ RED.view = (function() {
d3.event.stopPropagation();
if (d3.event.metaKey || d3.event.ctrlKey) {
l.classed("red-ui-flow-link-splice",true);
- showQuickAddDialog(d3.mouse(this), selected_link);
+ var point = d3.mouse(this);
+ var clickedGroup = getGroupAt(point[0],point[1]);
+ showQuickAddDialog(point, selected_link, clickedGroup);
}
})
.on("touchstart",function(d) {
@@ -3423,6 +4124,194 @@ RED.view = (function() {
})
+
+ var group = groupLayer.selectAll(".red-ui-flow-group").data(activeGroups,function(d) { return d.id });
+ group.exit().each(function(d,i) {
+ document.getElementById("group_select_"+d.id).remove()
+ }).remove();
+ var groupEnter = group.enter().insert("svg:g").attr("class", "red-ui-flow-group")
+
+ var addedGroups = false;
+ groupEnter.each(function(d,i) {
+ addedGroups = true;
+ var g = d3.select(this);
+ g.attr("id",d.id);
+
+ var groupBorderRadius = 4;
+
+ var selectGroup = groupSelectLayer.append('g').attr("class", "red-ui-flow-group").attr("id","group_select_"+d.id);
+ selectGroup.append('rect').classed("red-ui-flow-group-outline-select",true)
+ .attr('rx',groupBorderRadius).attr('ry',groupBorderRadius)
+ .attr("x",-4)
+ .attr("y",-4)
+ .style("stroke","rgba(255,255,255,0.8)")
+ .style("stroke-width",6)
+
+ selectGroup.append('rect').classed("red-ui-flow-group-outline-select",true)
+ .attr('rx',groupBorderRadius).attr('ry',groupBorderRadius)
+ .attr("x",-4)
+ .attr("y",-4)
+ selectGroup.on("mousedown", function() {groupMouseDown.call(g[0][0],d)});
+ selectGroup.on("mouseup", function() {groupMouseUp.call(g[0][0],d)});
+
+ g.append('rect').classed("red-ui-flow-group-outline",true).attr('rx',0.5).attr('ry',0.5);
+
+ g.append('rect').classed("red-ui-flow-group-body",true)
+ .attr('rx',groupBorderRadius).attr('ry',groupBorderRadius).style({
+ "fill":d.fill||"none",
+ "stroke": d.stroke||"none",
+ })
+ g.on("mousedown",groupMouseDown).on("mouseup",groupMouseUp)
+ g.append('svg:text').attr("class","red-ui-flow-group-label");
+ d.dirty = true;
+ });
+ if (addedGroups) {
+ group.sort(function(a,b) {
+ if (a._root === b._root) {
+ return a._depth - b._depth;
+ } else {
+ return a._root.localeCompare(b._root);
+ }
+ })
+ }
+ group[0].reverse();
+
+ group.each(function(d,i) {
+ if (d.resize) {
+ d.minWidth = 0;
+ delete d.resize;
+ }
+ if (d.dirty || dirtyGroups[d.id]) {
+ var g = d3.select(this);
+ if (d.nodes.length > 0) {
+ var minX = Number.POSITIVE_INFINITY;
+ var minY = Number.POSITIVE_INFINITY;
+ var maxX = 0;
+ var maxY = 0;
+ d.nodes.forEach(function(n) {
+ if (n.type !== "group") {
+ minX = Math.min(minX,n.x-n.w/2-25-((n._def.button && n._def.align!=="right")?20:0));
+ minY = Math.min(minY,n.y-n.h/2-25);
+ maxX = Math.max(maxX,n.x+n.w/2+25+((n._def.button && n._def.align=="right")?20:0));
+ maxY = Math.max(maxY,n.y+n.h/2+25);
+ } else {
+ minX = Math.min(minX,n.x-25)
+ minY = Math.min(minY,n.y-25)
+ maxX = Math.max(maxX,n.x+n.w+25)
+ maxY = Math.max(maxY,n.y+n.h+25)
+ }
+ });
+
+ d.x = minX;
+ d.y = minY;
+ d.w = maxX - minX;
+ d.h = maxY - minY;
+ } else {
+ d.w = 40;
+ d.h = 40;
+ }
+ if (!d.minWidth) {
+ if (d.style.label && d.name) {
+ d.minWidth = calculateTextWidth(d.name||"","red-ui-flow-group-label",8);
+ d.labels = separateTextByLineBreak;
+ } else {
+ d.minWidth = 40;
+ }
+ }
+ d.w = Math.max(d.minWidth,d.w);
+ if (d.style.label && d.labels) {
+ var h = (d.labels.length -1) * 16;
+ var labelPos = d.style["label-position"] || "nw";
+ d.h += h;
+ if (labelPos[0] === "n") {
+ d.y -= h;
+ }
+ }
+
+ g.attr("transform","translate("+d.x+","+d.y+")")
+ g.selectAll(".red-ui-flow-group-outline")
+ .attr("width",d.w)
+ .attr("height",d.h)
+
+
+ var selectGroup = document.getElementById("group_select_"+d.id);
+ selectGroup.setAttribute("transform","translate("+d.x+","+d.y+")");
+ if (d.hovered) {
+ selectGroup.classList.add("red-ui-flow-group-hovered")
+ } else {
+ selectGroup.classList.remove("red-ui-flow-group-hovered")
+ }
+ var selectGroupRect = selectGroup.children[0];
+ selectGroupRect.setAttribute("width",d.w+8)
+ selectGroupRect.setAttribute("height",d.h+8)
+ selectGroupRect.style.strokeOpacity = (d.selected || d.highlighted)?0.8:0;
+ selectGroupRect.style.strokeDasharray = (d.active)?"10 4":"";
+ selectGroupRect = selectGroup.children[1];
+ selectGroupRect.setAttribute("width",d.w+8)
+ selectGroupRect.setAttribute("height",d.h+8)
+ selectGroupRect.style.strokeOpacity = (d.selected || d.highlighted)?0.8:0;
+ selectGroupRect.style.strokeDasharray = (d.active)?"10 4":"";
+
+ if (d.highlighted) {
+ selectGroup.classList.add("red-ui-flow-node-highlighted");
+ } else {
+ selectGroup.classList.remove("red-ui-flow-node-highlighted");
+ }
+
+
+ g.selectAll(".red-ui-flow-group-body")
+ .attr("width",d.w)
+ .attr("height",d.h)
+ .style("stroke", d.style.stroke || "")
+ .style("stroke-opacity", d.style.hasOwnProperty('stroke-opacity') ? d.style['stroke-opacity'] : "")
+ .style("fill", d.style.fill || "")
+ .style("fill-opacity", d.style.hasOwnProperty('fill-opacity') ? d.style['fill-opacity'] : "")
+
+ var label = g.selectAll(".red-ui-flow-group-label");
+ label.classed("hide",!!!d.style.label)
+ if (d.style.label) {
+ var labelPos = d.style["label-position"] || "nw";
+ var labelX = 0;
+ var labelY = 0;
+
+ if (labelPos[0] === 'n') {
+ labelY = 0+15; // Allow for font-height
+ } else {
+ labelY = d.h - 5 -(d.labels.length -1) * 16;
+ }
+ if (labelPos[1] === 'w') {
+ labelX = 5;
+ labelAnchor = "start"
+ } else if (labelPos[1] === 'e') {
+ labelX = d.w-5;
+ labelAnchor = "end"
+ } else {
+ labelX = d.w/2;
+ labelAnchor = "middle"
+ }
+ label
+ .style("fill", d.style.hasOwnProperty('color')?d.style.color:"#999")
+ .attr("transform","translate("+labelX+","+labelY+")")
+ .attr("text-anchor",labelAnchor);
+ if (d.labels) {
+ var ypos = 0;
+ g.selectAll(".red-ui-flow-group-label-text").remove();
+ d.labels.forEach(function (name) {
+ label.append("tspan")
+ .classed("red-ui-flow-group-label-text", true)
+ .text(name)
+ .attr("x", 0)
+ .attr("y", ypos);
+ ypos += 16;
+ });
+ }
+ }
+
+ delete dirtyGroups[d.id];
+ delete d.dirty;
+ }
+ })
+
} else {
// JOINING - unselect any selected links
linkLayer.selectAll(".red-ui-flow-link-selected").data(
@@ -3436,7 +4325,6 @@ RED.view = (function() {
if (d3.event) {
d3.event.preventDefault();
}
-
}
function focusView() {
@@ -3474,29 +4362,36 @@ RED.view = (function() {
if (result) {
var new_nodes = result[0];
var new_links = result[1];
- var new_workspaces = result[2];
- var new_subflows = result[3];
- var new_default_workspace = result[4];
+ var new_groups = result[2];
+ var new_workspaces = result[3];
+ var new_subflows = result[4];
+ var new_default_workspace = result[5];
if (addNewFlow && new_default_workspace) {
RED.workspaces.show(new_default_workspace.id);
}
var new_ms = new_nodes.filter(function(n) { return n.hasOwnProperty("x") && n.hasOwnProperty("y") && n.z == RED.workspaces.active() }).map(function(n) { return {n:n};});
+ new_ms = new_ms.concat(new_groups.filter(function(g) { return g.z === RED.workspaces.active()}).map(function(g) { return {n:g}}))
var new_node_ids = new_nodes.map(function(n){ n.changed = true; return n.id; });
-
// TODO: pick a more sensible root node
if (new_ms.length > 0) {
- var root_node = new_ms[0].n;
- var dx = root_node.x;
- var dy = root_node.y;
+
if (mouse_position == null) {
mouse_position = [0,0];
}
+ var dx = mouse_position[0];
+ var dy = mouse_position[1];
+ if (new_ms.length > 0) {
+ var root_node = new_ms[0].n;
+ dx = root_node.x;
+ dy = root_node.y;
+ }
+
var minX = 0;
var minY = 0;
var i;
- var node;
+ var node,group;
for (i=0;i 0 &&
- node.n._def.outputs > 0;
+ ((node.n.hasOwnProperty("inputs") && node.n.inputs > 0) || (!node.n.hasOwnProperty("inputs") && node.n._def.inputs > 0)) &&
+ ((node.n.hasOwnProperty("outputs") && node.n.outputs > 0) || (!node.n.hasOwnProperty("outputs") && node.n._def.outputs > 0))
+
+
}
}
RED.keyboard.add("*","escape",function(){
@@ -3549,6 +4454,7 @@ RED.view = (function() {
t:"add",
nodes:new_node_ids,
links:new_links,
+ groups:new_groups,
workspaces:new_workspaces,
subflows:new_subflows,
dirty:RED.nodes.dirty()
@@ -3581,12 +4487,16 @@ RED.view = (function() {
newConfigNodeCount++;
}
})
+ var newGroupCount = new_groups.length;
if (new_workspaces.length > 0) {
counts.push(RED._("clipboard.flow",{count:new_workspaces.length}));
}
if (newNodeCount > 0) {
counts.push(RED._("clipboard.node",{count:newNodeCount}));
}
+ if (newGroupCount > 0) {
+ counts.push(RED._("clipboard.group",{count:newGroupCount}));
+ }
if (newConfigNodeCount > 0) {
counts.push(RED._("clipboard.configNode",{count:newNodeCount}));
}
@@ -3638,22 +4548,25 @@ RED.view = (function() {
var historyEvents = [];
for (var i=0;i 0) {
@@ -3668,7 +4581,40 @@ RED.view = (function() {
RED.view.redraw();
}
+ function getSelection() {
+ var selection = {};
+ var allNodes = new Set();
+
+ if (moving_set.length > 0) {
+ moving_set.forEach(function(n) {
+ if (n.n.type !== 'group') {
+ allNodes.add(n.n);
+ }
+ });
+ }
+ var selectedGroups = activeGroups.filter(function(g) { return g.selected && !g.active });
+ if (selectedGroups.length > 0) {
+ if (selectedGroups.length === 1 && selectedGroups[0].active) {
+ // Let nodes be nodes
+ } else {
+ selectedGroups.forEach(function(g) {
+ var groupNodes = RED.group.getNodes(g,true);
+ groupNodes.forEach(function(n) {
+ allNodes.delete(n);
+ });
+ allNodes.add(g);
+ });
+ }
+ }
+ if (allNodes.size > 0) {
+ selection.nodes = Array.from(allNodes);
+ }
+ if (selected_link != null) {
+ selection.link = selected_link;
+ }
+ return selection;
+ }
return {
init: init,
state:function(state) {
@@ -3679,12 +4625,17 @@ RED.view = (function() {
}
},
- redraw: function(updateActive) {
+ updateActive: updateActiveNodes,
+ redraw: function(updateActive, syncRedraw) {
if (updateActive) {
updateActiveNodes();
updateSelection();
}
- redraw();
+ if (syncRedraw) {
+ _redraw();
+ } else {
+ redraw();
+ }
},
focus: focusView,
importNodes: importNodes,
@@ -3699,21 +4650,31 @@ RED.view = (function() {
selectedNode.dirty = true;
moving_set = [{n:selectedNode}];
}
+ } else if (selection) {
+ if (selection.nodes) {
+ updateActiveNodes();
+ moving_set = [];
+ // TODO: this selection group span groups
+ // - if all in one group -> activate the group
+ // - if in multiple groups (or group/no-group)
+ // -> select the first 'set' of things in the same group/no-group
+ selection.nodes.forEach(function(n) {
+ if (n.type !== "group") {
+ n.selected = true;
+ n.dirty = true;
+ moving_set.push({n:n});
+ } else {
+ selectGroup(n,true);
+ }
+ })
+ }
}
}
updateSelection();
- redraw();
- },
- selection: function() {
- var selection = {};
- if (moving_set.length > 0) {
- selection.nodes = moving_set.map(function(n) { return n.n;});
- }
- if (selected_link != null) {
- selection.link = selected_link;
- }
- return selection;
+ redraw(true);
},
+ selection: getSelection,
+
scale: function() {
return scaleFactor;
},
@@ -3728,47 +4689,57 @@ RED.view = (function() {
}
return result;
},
- reveal: function(id) {
+ getGroupAtPoint: getGroupAt,
+ getActiveGroup: function() { return activeGroup },
+ reveal: function(id,triggerHighlight) {
if (RED.nodes.workspace(id) || RED.nodes.subflow(id)) {
RED.workspaces.show(id);
} else {
- var node = RED.nodes.node(id);
- if (node._def.category !== 'config' && node.z) {
- node.highlighted = true;
- node.dirty = true;
- RED.workspaces.show(node.z);
+ var node = RED.nodes.node(id) || RED.nodes.group(id);
+ if (node) {
+ if (node.z && (node.type === "group" || node._def.category !== 'config')) {
+ node.dirty = true;
+ RED.workspaces.show(node.z);
- var screenSize = [chart.width()/scaleFactor,chart.height()/scaleFactor];
- var scrollPos = [chart.scrollLeft()/scaleFactor,chart.scrollTop()/scaleFactor];
-
- if (node.x < scrollPos[0] || node.y < scrollPos[1] || node.x > screenSize[0]+scrollPos[0] || node.y > screenSize[1]+scrollPos[1]) {
- var deltaX = '-='+(((scrollPos[0] - node.x) + screenSize[0]/2)*scaleFactor);
- var deltaY = '-='+(((scrollPos[1] - node.y) + screenSize[1]/2)*scaleFactor);
- chart.animate({
- scrollLeft: deltaX,
- scrollTop: deltaY
- },200);
- }
-
- if (!node._flashing) {
- node._flashing = true;
- var flash = 22;
- var flashFunc = function() {
- flash--;
- node.dirty = true;
- if (flash >= 0) {
- node.highlighted = !node.highlighted;
- setTimeout(flashFunc,100);
- } else {
- node.highlighted = false;
- delete node._flashing;
- }
- RED.view.redraw();
+ var screenSize = [chart.width()/scaleFactor,chart.height()/scaleFactor];
+ var scrollPos = [chart.scrollLeft()/scaleFactor,chart.scrollTop()/scaleFactor];
+ var cx = node.x;
+ var cy = node.y;
+ if (node.type === "group") {
+ cx += node.w/2;
+ cy += node.h/2;
}
- flashFunc();
+ if (cx < scrollPos[0] || cy < scrollPos[1] || cx > screenSize[0]+scrollPos[0] || cy > screenSize[1]+scrollPos[1]) {
+ var deltaX = '-='+(((scrollPos[0] - cx) + screenSize[0]/2)*scaleFactor);
+ var deltaY = '-='+(((scrollPos[1] - cy) + screenSize[1]/2)*scaleFactor);
+ chart.animate({
+ scrollLeft: deltaX,
+ scrollTop: deltaY
+ },200);
+ }
+ if (triggerHighlight !== false) {
+ node.highlighted = true;
+ if (!node._flashing) {
+ node._flashing = true;
+ var flash = 22;
+ var flashFunc = function() {
+ flash--;
+ node.dirty = true;
+ if (flash >= 0) {
+ node.highlighted = !node.highlighted;
+ setTimeout(flashFunc,100);
+ } else {
+ node.highlighted = false;
+ delete node._flashing;
+ }
+ RED.view.redraw();
+ }
+ flashFunc();
+ }
+ }
+ } else if (node._def.category === 'config') {
+ RED.sidebar.config.show(id);
}
- } else if (node._def.category === 'config') {
- RED.sidebar.config.show(id);
}
}
},
@@ -3793,7 +4764,6 @@ RED.view = (function() {
mouse_mode = RED.state.SELECTING_NODE;
clearSelection();
if (options.selected) {
- console.log(options.selected);
options.selected.forEach(function(id) {
var n = RED.nodes.node(id);
if (n) {
@@ -3846,6 +4816,15 @@ RED.view = (function() {
type: "compact",
buttons: buttons
})
+ },
+ scroll: function(x,y) {
+ chart.scrollLeft(chart.scrollLeft()+x);
+ chart.scrollTop(chart.scrollTop()+y)
+ },
+ clickNodeButton: function(n) {
+ if (n._def.button) {
+ nodeButtonClicked(n);
+ }
}
};
})();
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js b/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js
index 1f361d0f7..290083ea6 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js
@@ -128,10 +128,6 @@ RED.workspaces = (function() {
RED.history.push(historyEvent);
RED.nodes.dirty(true);
RED.sidebar.config.refresh();
- var selection = RED.view.selection();
- if (!selection.nodes && !selection.links) {
- RED.sidebar.info.refresh(workspace);
- }
if (changes.hasOwnProperty('disabled')) {
RED.nodes.eachNode(function(n) {
if (n.z === workspace.id) {
@@ -140,6 +136,7 @@ RED.workspaces = (function() {
});
RED.view.redraw();
}
+ RED.events.emit("flows:change",workspace);
}
RED.tray.close();
}
@@ -219,7 +216,10 @@ RED.workspaces = (function() {
if (RED.view.state() != RED.state.IMPORT_DRAGGING) {
RED.view.state(RED.state.DEFAULT);
}
- RED.sidebar.info.refresh(workspace);
+ var selection = RED.view.selection();
+ if (!selection.nodes && !selection.links && workspace.id === activeWorkspace) {
+ RED.sidebar.info.refresh(workspace);
+ }
tabflowEditor.destroy();
}
}
@@ -371,7 +371,9 @@ RED.workspaces = (function() {
var changes = { disabled: workspace.disabled };
workspace.disabled = disabled;
$("#red-ui-tab-"+(workspace.id.replace(".","-"))).toggleClass('red-ui-workspace-disabled',!!workspace.disabled);
- $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!workspace.disabled);
+ if (id === activeWorkspace) {
+ $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!workspace.disabled);
+ }
var historyEvent = {
t: "edit",
changes:changes,
@@ -380,10 +382,11 @@ RED.workspaces = (function() {
}
workspace.changed = true;
RED.history.push(historyEvent);
+ RED.events.emit("flows:change",workspace);
RED.nodes.dirty(true);
RED.sidebar.config.refresh();
var selection = RED.view.selection();
- if (!selection.nodes && !selection.links) {
+ if (!selection.nodes && !selection.links && workspace.id === activeWorkspace) {
RED.sidebar.info.refresh(workspace);
}
if (changes.hasOwnProperty('disabled')) {
@@ -412,9 +415,14 @@ RED.workspaces = (function() {
}
function setWorkspaceOrder(order) {
- RED.nodes.setWorkspaceOrder(order.filter(function(id) {
+ var newOrder = order.filter(function(id) {
return RED.nodes.workspace(id) !== undefined;
- }));
+ })
+ var currentOrder = RED.nodes.getWorkspaceOrder();
+ if (JSON.stringify(newOrder) !== JSON.stringify(currentOrder)) {
+ RED.nodes.setWorkspaceOrder(newOrder);
+ RED.events.emit("flows:reorder",newOrder);
+ }
workspace_tabs.order(order);
}
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ace.scss b/packages/node_modules/@node-red/editor-client/src/sass/ace.scss
index fb6eaf8eb..cd3739800 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/ace.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/ace.scss
@@ -9,19 +9,15 @@
color: transparent !important;
}
}
-
-
.ace_gutter {
+ background: $text-editor-gutter-background;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
.ace_scroller {
+ background: $text-editor-background;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
- }
-
- .ace_scroller {
- background: $text-editor-background;
color: $text-editor-color;
}
.ace_marker-layer .ace_active-line {
@@ -37,9 +33,6 @@
.ace_gutter-active-line {
background: $text-editor-gutter-active-line-background;
}
- .ace_gutter {
- background: $text-editor-gutter-background;
- }
.ace_tooltip {
font-family: $primary-font;
line-height: 1.4em;
@@ -52,6 +45,9 @@
@include component-shadow;
border-color: $popover-background;
}
+ .ace_content {
+ line-height: 1;
+ }
textarea.ace_text-input {
overflow: hidden;
padding: 0px 1px !important;
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/colors.scss b/packages/node_modules/@node-red/editor-client/src/sass/colors.scss
index 8411343c5..96be7b5ab 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/colors.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/colors.scss
@@ -23,7 +23,7 @@ $primary-background: #f3f3f3;//#0ff;
$secondary-background: #fff;//#ff0;
$secondary-background-selected: #efefef;//#e9e900;
$secondary-background-inactive: #f0f0f0;//#f0f000;
-$secondary-background-hover: #ddd;//#dd0;
+$secondary-background-hover: #e6e6e6;//#dd0;
$secondary-background-disabled: #f9f9f9;//#fafa0;
$tertiary-background: #f7f7f7;//#f0f;
@@ -94,7 +94,7 @@ $list-item-secondary-color: $secondary-text-color;
$list-item-background: $secondary-background;
$list-item-background-disabled: $secondary-background-inactive;
$list-item-background-hover: $secondary-background-hover;
-$list-item-background-selected: $secondary-background-selected;
+$list-item-background-selected: #ffebc7; // #fff1e5;
$list-item-border-selected: $secondary-text-color-selected;
$tab-text-color-active: $header-text-color;
@@ -284,3 +284,8 @@ $debug-message-border: #eee;
$debug-message-border-hover: #999;
$debug-message-border-warning: #ffdf9d;
$debug-message-border-error: #f99;
+
+$group-default-fill: none;
+$group-default-fill-opacity: 1;
+$group-default-stroke: #999;
+$group-default-stroke-opacity: 1;
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/debug.scss b/packages/node_modules/@node-red/editor-client/src/sass/debug.scss
index d767aaed3..1e77e46e1 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/debug.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/debug.scss
@@ -160,6 +160,7 @@
.red-ui-debug-msg-element {
color: $debug-message-text-color;
line-height: 1.3em;
+ overflow-wrap: break-word;
}
.red-ui-debug-msg-object-key {
color: $debug-message-text-color-object-key;
@@ -216,6 +217,10 @@
.red-ui-debug-msg-type-number { color: $debug-message-text-color-msg-type-number; };
.red-ui-debug-msg-type-number-toggle { cursor: pointer;}
+.red-ui-debug-msg-type-string {
+ white-space: pre-wrap;
+}
+
.red-ui-debug-msg-row {
display: block;
padding: 4px 2px 2px;
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/editor.scss b/packages/node_modules/@node-red/editor-client/src/sass/editor.scss
index b13941f22..d8ef3e89c 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/editor.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/editor.scss
@@ -304,9 +304,6 @@ button.red-ui-button-small
&:first-child {
padding: 20px 20px 0;
}
- &:last-child {
- padding-bottom: 20px;
- }
}
}
.red-ui-editor-type-expression-tab-content {
@@ -411,6 +408,133 @@ button.red-ui-button.red-ui-editor-node-appearance-button {
}
}
+.red-ui-group-layout-picker {
+ padding: 5px;
+ background: $primary-background;
+}
+.red-ui-group-layout-picker-cell-text {
+ position: absolute;
+ width: 14px;
+ height: 2px;
+ border-top: 2px solid $secondary-text-color;
+ border-bottom: 2px solid $secondary-text-color;
+ margin: 2px;
+
+ &.red-ui-group-layout-text-pos-nw { top: 0; left: 0; }
+ &.red-ui-group-layout-text-pos-n { top: 0; left: calc(50% - 9px); }
+ &.red-ui-group-layout-text-pos-ne { top: 0; right: 0; }
+ &.red-ui-group-layout-text-pos-sw { bottom: 0; left: 0; }
+ &.red-ui-group-layout-text-pos-s { bottom: 0; left: calc(50% - 9px); }
+ &.red-ui-group-layout-text-pos-se { bottom: 0; right: 0; }
+ &.red-ui-group-layout-text-pos- {
+ width: 100%;
+ height: 100%;
+ border-radius: 5px;
+ margin: 0;
+ background-color: #FFF;
+ background-size: 100% 100%;
+ background-position: 0 0, 50% 50%;
+ background-image: linear-gradient(45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent),linear-gradient(-45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent);
+ border: none;
+ }
+}
+
+.red-ui-group-layout-picker button.red-ui-search-result-node {
+ float: none;
+ position: relative;
+ padding: 0;
+ margin: 5px;
+ width: 32px;
+ height: 27px;
+}
+
+button.red-ui-group-layout-picker-none {
+ width: 100%;
+}
+
+.red-ui-color-picker {
+ input[type="text"] {
+ border-radius:0;
+ width: 100%;
+ margin-bottom: 0;
+ border: none;
+ border-bottom: 1px solid $form-input-border-color;
+ }
+ small {
+ color: $secondary-text-color;
+ margin-left: 5px;
+ margin-right: 4px;
+ display: inline-block;
+ min-width: 35px;
+ text-align: right;
+ }
+ background: $primary-background;
+}
+.red-ui-editor-node-appearance-button {
+ .red-ui-search-result-node {
+ overflow: hidden
+ }
+}
+.red-ui-color-picker-cell {
+ padding: 0;
+ border-style: solid;
+ border-width: 1px;
+ border-color: $secondary-border-color;
+}
+.red-ui-color-picker-swatch {
+ position: absolute;
+ top:-1px;right:-1px;left:-1px;bottom:-1px;
+ border-radius: 4px;
+}
+
+.red-ui-color-picker-cell-none {
+ height: 100%;
+ background-color: #FFF;
+ background-size: 100% 100%;
+ background-position: 0 0, 50% 50%;
+ background-image: linear-gradient(45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent),linear-gradient(-45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent)
+}
+.red-ui-search-result-node .red-ui-color-picker-cell-none {
+ border-radius: 4px;
+ background-size: 50% 50%;
+ background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee), linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
+}
+
+.red-ui-color-picker-opacity-slider {
+ position:relative;
+ vertical-align: middle;
+ display: inline-block;
+ width: calc(100% - 50px);
+ height: 14px;
+ margin: 6px 3px 8px;
+ box-sizing: border-box;
+ background-color: white;
+ background-image:
+ linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 25%),
+ linear-gradient(-45deg, #eee 25%, transparent 25%, transparent 75%, #eee 25%);
+ background-size: 6px 6px;
+}
+.red-ui-color-picker-opacity-slider-overlay {
+ position: absolute;
+ top:0;right:0;left:0;bottom:0;
+ background-image:linear-gradient(90deg, transparent 0%, #f00 100%);
+ background-size: 100% 100%;
+ border: 1px solid $primary-border-color;
+}
+
+div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle {
+ z-Index: 10;
+ top: -4px;
+ cursor: pointer;
+ min-width: 0;
+ width: 10px;
+ height: 22px;
+ padding: 0;
+ border: 1px solid $primary-border-color;
+ border-radius: 1px;
+ background: $secondary-background;
+ box-sizing: border-box;
+}
.red-ui-icon-picker {
select {
box-sizing: border-box;
@@ -633,7 +757,7 @@ button.red-ui-toggleButton.toggle {
.red-ui-typedInput-value-label,.red-ui-typedInput-option-label {
select,.placeholder-input {
margin: 3px;
- height: 26px;
+ height: 24px;
width: calc(100% - 10px);
padding-left: 3px;
}
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/flow.scss b/packages/node_modules/@node-red/editor-client/src/sass/flow.scss
index d92d1e964..60de209cc 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/flow.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/flow.scss
@@ -71,6 +71,48 @@
}
}
+.red-ui-flow-group {
+ &.red-ui-flow-group-hovered {
+ .red-ui-flow-group-outline-select {
+ stroke-opacity: 0.8 !important;
+ stroke-dasharray: 10 4 !important;
+ }
+ }
+ &.red-ui-flow-group-active-hovered:not(.red-ui-flow-group-hovered) {
+ .red-ui-flow-group-outline-select {
+ stroke: $link-link-color;
+ }
+ }
+}
+
+.red-ui-flow-group-outline {
+ fill: none;
+ stroke: $node-selected-color;
+ stroke-opacity: 0;
+ stroke-width: 12;
+ pointer-events: stroke;
+}
+.red-ui-flow-group-outline-select {
+ fill: none;
+ stroke: $node-selected-color;
+ pointer-events: stroke;
+ stroke-opacity: 0;
+ stroke-width: 3;
+}
+.red-ui-flow-group-body {
+ pointer-events: none;
+ fill: $group-default-fill;
+ fill-opacity: $group-default-fill-opacity;
+ stroke-width: 2;
+ stroke: $group-default-stroke;
+ stroke-opacity: $group-default-stroke-opacity;
+}
+.red-ui-flow-group-label {
+ @include disable-selection;
+}
+
+
+
.red-ui-flow-node-unknown {
stroke-dasharray:10,4;
stroke: $node-border-unknown;
@@ -166,6 +208,9 @@ g.red-ui-flow-node-selected {
fill-opacity: 1;
stroke-dasharray: none;
}
+ .red-ui-flow-group, .red-ui-flow-group-body {
+ stroke-dasharray: 8, 3;
+ }
}
.red-ui-flow-node-disabled {
&.red-ui-flow-node, .red-ui-flow-node {
@@ -248,6 +293,7 @@ g.red-ui-flow-node-selected {
.red-ui-flow-link-outline {
stroke: $view-background;
+ stroke-opacity: 0.4;
stroke-width: 5;
cursor: crosshair;
fill: none;
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/library.scss b/packages/node_modules/@node-red/editor-client/src/sass/library.scss
index 60014b2e6..5cff53f44 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/library.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/library.scss
@@ -37,7 +37,7 @@
border-radius: 4px;
font-family: $monospace-font !important;
font-size: 13px !important;
- height: 300px;
+ height: 100%;
line-height: 1.3em;
padding: 6px 10px;
background: $clipboard-textarea-background;
@@ -62,6 +62,7 @@
background: $form-input-background;
&>div {
height: 100%;
+ box-sizing: border-box;
}
}
.red-ui-clipboard-dialog-box {
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/palette.scss b/packages/node_modules/@node-red/editor-client/src/sass/palette.scss
index ea4b06c52..922a31e33 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/palette.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/palette.scss
@@ -186,6 +186,21 @@
background-size: contain;
background-repeat: no-repeat;
}
+.red-ui-search-result-node {
+ &.red-ui-palette-icon-flow,
+ &.red-ui-palette-icon-group,
+ &.red-ui-palette-icon-selection {
+ background: none;
+ border-color: transparent;
+ .red-ui-palette-icon-container {
+ background: none;
+ }
+ .red-ui-palette-icon-fa {
+ color: $secondary-text-color;
+ font-size: 18px;
+ }
+ }
+}
.red-ui-palette-icon-fa {
color: white;
position: absolute;
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/panels.scss b/packages/node_modules/@node-red/editor-client/src/sass/panels.scss
index 9f99db5d4..455aab891 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/panels.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/panels.scss
@@ -22,9 +22,19 @@
// border: 1px solid red;
box-sizing: border-box;
}
+ display: flex;
+ flex-direction: column;
+
+ >.red-ui-panel:first-child {
+ flex: 0 0 auto;
+ }
+ >.red-ui-panel:last-child {
+ flex: 1 1 auto;
+ }
}
.red-ui-panels-separator {
+ flex: 0 0 auto;
border-top: 1px solid $secondary-border-color;
border-bottom: 1px solid $secondary-border-color;
height: 7px;
@@ -37,10 +47,13 @@
.red-ui-panel {
overflow: auto;
height: calc(50% - 4px);
+ position: relative;
}
.red-ui-panels.red-ui-panels-horizontal {
height: 100%;
+ flex-direction: row;
+
&>.red-ui-panel {
vertical-align: top;
display: inline-block;
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/popover.scss b/packages/node_modules/@node-red/editor-client/src/sass/popover.scss
index 95097a30e..872f32024 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/popover.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/popover.scss
@@ -150,6 +150,16 @@
.red-ui-popover a.red-ui-button,
.red-ui-popover button.red-ui-button {
+ &:not(.primary) {
+ border-color: $popover-button-border-color;
+ background: $popover-background;
+ color: $popover-color !important;
+ }
+ &:not(.primary):not(.disabled):not(.ui-button-disabled):hover {
+ border-color: $popover-button-border-color-hover;
+ }
+
+
&.primary {
border-color: $popover-button-border-color;
}
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/projects.scss b/packages/node_modules/@node-red/editor-client/src/sass/projects.scss
index 9b1005f22..e08551297 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/projects.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/projects.scss
@@ -18,7 +18,12 @@
.red-ui-editableList-container {
padding: 0px;
}
-
+ padding: 0;
+ .red-ui-projects-dialog-box {
+ box-sizing: border-box;
+ overflow-y: auto;
+ padding: 25px 25px 10px 25px;
+ }
}
#red-ui-project-settings-tab-settings {
overflow-y: scroll;
@@ -99,6 +104,7 @@
.red-ui-projects-dialog-screen-create {
min-height: 500px;
button.red-ui-projects-dialog-screen-create-type {
+ position: relative;
height: auto;
padding: 10px;
}
@@ -169,9 +175,14 @@
.red-ui-projects-dialog-project-list-container {
border: 1px solid $secondary-border-color;
border-radius: 2px;
+ display: flex;
+ flex-direction: column;
+ .red-ui-search-container {
+ flex-grow: 0;
+ }
}
.red-ui-projects-dialog-project-list-inner-container {
- height: 300px;
+ flex-grow: 1 ;
overflow-y: scroll;
position:relative;
.red-ui-editableList-border {
@@ -254,6 +265,9 @@
}
}
}
+.red-ui-projects-dialog-screen-create-type {
+ position: relative;
+}
.red-ui-projects-dialog-screen-create-type.red-ui-button.toggle.selected:not(.disabled):not(:disabled) {
color: $secondary-text-color-active !important;
}
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/search.scss b/packages/node_modules/@node-red/editor-client/src/sass/search.scss
index 0ec8b6525..27ebb1a04 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/search.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/search.scss
@@ -24,6 +24,13 @@
top: 0px;
border: 1px solid $primary-border-color;
box-shadow: 0 0 10px $shadow;
+ background: $secondary-background;
+
+ .red-ui-searchBox-container {
+ display: inline-block;
+ margin-right: 6px;
+ width: calc(100% - 30px);
+ }
}
.red-ui-type-search {
@@ -87,6 +94,8 @@
}
.red-ui-palette-icon {
width: 15px;
+ position:relative;
+ left: -1px;
}
.red-ui-search-result-description {
margin-left:28px;
@@ -153,7 +162,7 @@
width: 30px;
float:left;
height: 25px;
- border-radius: 5px;
+ border-radius: 3px;
border: 1px solid $node-border;
background-position: 5% 50%;
background-repeat: no-repeat;
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/style.scss b/packages/node_modules/@node-red/editor-client/src/sass/style.scss
index 088e5c1b8..ca572ea46 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/style.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/style.scss
@@ -42,6 +42,7 @@
@import "tab-config";
@import "tab-context";
@import "tab-info";
+@import "tab-help";
@import "popover";
@import "flow";
@import "palette-editor";
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-context.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-context.scss
index 46b09de43..4be9761f7 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/tab-context.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-context.scss
@@ -27,9 +27,22 @@
display: none;
}
}
+
+ .red-ui-info-table {
+ table-layout: fixed;
+ }
+
+ table.red-ui-info-table tr:not(.blank) td:first-child {
+ width: 30%;
+ }
+ table.red-ui-info-table tr:not(.blank) td:last-child {
+ vertical-align: top;
+ }
+
}
.red-ui-sidebar-context-property {
+ overflow-wrap: break-word;
position: relative;
.red-ui-debug-msg-tools {
right: 0px;
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-help.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-help.scss
new file mode 100644
index 000000000..fe4f9fb84
--- /dev/null
+++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-help.scss
@@ -0,0 +1,27 @@
+.red-ui-sidebar-help-stack {
+ // height: calc(100% - 39px);
+}
+.red-ui-help-search {
+ border-bottom: 1px solid $secondary-border-color;
+}
+
+.red-ui-sidebar-help-toc {
+ .red-ui-treeList-label {
+ font-size: 13px;
+ padding: 2px 0;
+ overflow: hidden;
+ white-space: nowrap;
+ }
+
+}
+#red-ui-sidebar-help-show-toc {
+ i.fa-angle-right {
+ transition: all 0.2s ease-in-out;
+ }
+ &.selected {
+ i.fa-angle-right {
+ transform: rotate(90deg);
+ }
+ }
+
+}
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-info.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-info.scss
index bc72f7532..c98bdb6ad 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/tab-info.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-info.scss
@@ -14,9 +14,25 @@
* limitations under the License.
**/
+.red-ui-sidebar-info {
+ height: 100%;
+}
.red-ui-sidebar-info hr {
margin: 10px 0;
}
+.red-ui-info-header {
+ padding-left: 9px;
+ line-height: 21px;
+ cursor: default;
+ > * {
+ vertical-align: middle
+ }
+ > span {
+ display: inline-block;
+ margin-left: 5px;
+ }
+ border-bottom: 1px solid $secondary-border-color;
+}
table.red-ui-info-table {
font-size: 14px;
margin: 0 0 10px;
@@ -125,6 +141,9 @@ div.red-ui-info-table {
font-size: 1.296em;
line-height: 1.3em;
margin: 8px auto;
+ &.red-ui-help-title {
+ border-bottom: 1px solid $tertiary-border-color;
+ }
}
h2 {
font-weight: 500;
@@ -214,12 +233,13 @@ div.red-ui-info-table {
}
.red-ui-sidebar-info-stack {
- position: absolute;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- overflow-y: scroll;
+ height: 100%;
+ // position: absolute;
+ // top: 0;
+ // bottom: 0;
+ // left: 0;
+ // right: 0;
+ // overflow-y: scroll;
}
.red-ui-help-tips {
display: none;
@@ -227,20 +247,23 @@ div.red-ui-info-table {
left:0;
right:0;
bottom: 0;
- height: 150px;
+ height: 0;
+ transition: height 0.2s, padding 0.2s;
box-sizing: border-box;
border-top: 1px solid $secondary-border-color;
background-color: $secondary-background;
- padding: 20px;
+ padding: 0;
box-shadow: 0 5px 20px 0px $shadow;
overflow-y: auto;
}
.red-ui-sidebar-info.show-tips {
.red-ui-sidebar-info-stack {
- bottom: 150px;
+ height: calc(100% - 150px);
}
.red-ui-help-tips {
display: block;
+ height: 150px;
+ padding: 20px;
}
}
@@ -279,3 +302,220 @@ div.red-ui-info-table {
border-radius: 4px;
padding: 2px 4px 2px;
}
+
+.red-ui-info-outline,.red-ui-sidebar-help-toc {
+ display: flex;
+ flex-direction: column;
+
+ .red-ui-treeList {
+ flex-grow: 1;
+ position: relative;
+ }
+ .red-ui-treeList-container {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ }
+
+ .red-ui-treeList-container,.red-ui-editableList-border {
+ border: none;
+ border-radius: 0;
+ }
+ .red-ui-treeList-label {
+ font-size: 13px;
+ padding: 2px 0;
+ overflow: hidden;
+ }
+ .red-ui-info-outline-project {
+ border-bottom: 1px solid $secondary-border-color;
+ }
+
+ .red-ui-info-outline-item {
+ display: inline-block;
+ padding: 0;
+ font-size: 13px;
+ border: none;
+ .red-ui-palette-icon-fa {
+ position: relative;
+ top: 1px;
+ left: 0px;
+ }
+ &:hover {
+ background: inherit
+ }
+
+ &.red-ui-info-outline-item-flow {
+ .red-ui-search-result-description {
+ margin-left: 4px;
+ }
+ }
+ &.red-ui-info-outline-item-group .red-ui-search-result-node {
+ background: none;
+ border-color: transparent;
+ .red-ui-palette-icon-container {
+ background: none;
+ }
+ .red-ui-palette-icon-fa {
+ color: $secondary-text-color;
+ font-size: 18px;
+ }
+ }
+ &.red-ui-info-outline-item-empty {
+ font-style: italic;
+ color: $form-placeholder-color;
+ }
+ }
+
+ .red-ui-search-result-node {
+ width: 24px;
+ height: 20px;
+ margin-top: 1px;
+ }
+
+ .red-ui-palette-icon-container {
+ width: 24px;
+ }
+ .red-ui-palette-icon {
+ width: 20px;
+ }
+ .red-ui-search-result-description {
+ margin-left: 32px;
+ line-height: 22px;
+ white-space: nowrap;
+ }
+ .red-ui-search-result-node-label {
+ color: $secondary-text-color;
+ }
+}
+.red-ui-info-outline-item-control-spacer {
+ display: inline-block;
+ width: 23px;
+}
+.red-ui-info-outline-gutter {
+ display:none;
+ button {
+ position: absolute;
+ top: 1px;
+ left: 2px;
+ }
+ .red-ui-treeList-label:hover & {
+ display: inline;
+ }
+}
+.red-ui-info-outline-item-controls {
+ position: absolute;
+ top:0;
+ bottom: 0;
+ right: 0px;
+ padding: 2px 3px 0 1px;
+ text-align: right;
+ background: $list-item-background;
+
+ .red-ui-treeList-label:hover & {
+ background: $list-item-background-hover;
+ }
+ .red-ui-treeList-label.selected & {
+ background: $list-item-background-selected;
+ }
+
+
+ &.red-ui-info-outline-item-hover-controls button {
+ min-width: 23px;
+ }
+
+ .red-ui-treeList-label:not(:hover) &.red-ui-info-outline-item-hover-controls {
+ button {
+ border: none;
+ background: none;
+ }
+ }
+ .red-ui-info-outline-item-control-reveal,
+ .red-ui-info-outline-item-control-action {
+ display: none;
+ }
+ .red-ui-treeList-label:hover & {
+ .red-ui-info-outline-item-control-reveal,
+ .red-ui-info-outline-item-control-action {
+ display: inline-block;
+ }
+ }
+
+ .fa-ban {
+ display: none;
+ }
+ .red-ui-info-outline-item.red-ui-info-outline-item-disabled & {
+ .fa-ban {
+ display: inline-block;
+ }
+ .fa-circle-thin {
+ display: none;
+ }
+ }
+ button {
+ margin-right: 3px
+ }
+}
+.red-ui-info-outline-item-disabled {
+ .red-ui-search-result-node {
+ opacity: 0.4;
+ }
+ .red-ui-info-outline-item-label {
+ font-style: italic;
+ color: $secondary-text-color-disabled;
+ }
+ .red-ui-icons-flow {
+ opacity: 0.4;
+ }
+}
+
+
+
+.red-ui-icons {
+ display: inline-block;
+ width: 18px;
+ &:before {
+ white-space: pre;
+ content: ' '
+ }
+
+}
+
+.red-ui-icons-flow {
+ background-image: url('images/subflow_tab.svg');
+ background-repeat: no-repeat;
+ background-size: contain;
+ filter: brightness(2.5);
+}
+
+.red-ui-info-toolbar {
+ min-height: 39px;
+ height: 39px;
+ box-sizing: border-box;
+ text-align: left;
+ // padding-left: 9px;
+ // box-sizing: border-box;
+ // background: $palette-header-background;
+ // border-bottom: 1px solid $secondary-border-color;
+
+ .red-ui-searchBox-container {
+ position: absolute;
+ top: 6px;
+ right: 8px;
+ width: calc(100% - 150px);
+ max-width: 250px;
+ background: $palette-header-background;
+ input.red-ui-searchBox-input {
+ border: 1px solid $secondary-border-color;
+ border-radius: 3px;
+ font-size: 12px;
+ height: 26px;
+ }
+ input:focus.red-ui-searchBox-input {
+ border: 1px solid $secondary-border-color;
+ }
+ i.fa-search, i.fa-times {
+ top: 7px;
+ }
+ }
+
+}
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/searchBox.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/searchBox.scss
index 2f0740d61..d0cd631fc 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/searchBox.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/searchBox.scss
@@ -32,6 +32,9 @@
right: 5px;
top: 9px;
}
+ form.red-ui-searchBox-form {
+ margin: 0;
+ }
input.red-ui-searchBox-input {
border-radius: 0;
border: none;
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss
index be2d50674..ec865b116 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss
@@ -18,7 +18,7 @@
border: 1px solid $form-input-border-color;
border-radius: 4px;
height: 34px;
- display: inline-block;
+ display: inline-flex;
padding: 0;
margin: 0;
vertical-align: middle;
@@ -26,12 +26,7 @@
overflow:visible;
position: relative;
.red-ui-typedInput-input-wrap {
- position: absolute;
- left:0;
- right:0;
- top:0;
- bottom:0;
- outline: red;
+ flex-grow: 1;
}
input.red-ui-typedInput-input {
width: 100%;
@@ -49,14 +44,17 @@
border-color: $form-input-focus-color !important;
}
.red-ui-typedInput-value-label {
- position: absolute;
+ flex-grow: 1;
display: inline-block;
height: 32px;
box-sizing: border-box;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
-
+ .red-ui-typedInput-value-label-inactive {
+ background: $secondary-background-disabled;
+ color: $secondary-text-color-disabled;
+ }
}
}
.red-ui-typedInput-options {
@@ -104,18 +102,17 @@ button.red-ui-typedInput-option-trigger
{
text-align: left;
border: none;
- position: absolute;
+ flex-basis: auto;
box-sizing: border-box;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
padding: 0 1px 0 5px;
- display:inline-block;
background: $form-button-background;
height: 32px;
line-height: 30px;
- min-width: 23px;
vertical-align: middle;
color: $form-text-color;
+ white-space: nowrap;
i.red-ui-typedInput-icon {
margin-left: 1px;
margin-right: 2px;
@@ -123,7 +120,7 @@ button.red-ui-typedInput-option-trigger
}
&.disabled {
cursor: default;
- i.red-ui-typedInput-icon {
+ > i.red-ui-typedInput-icon {
color: $secondary-text-color-disabled;
}
}
@@ -151,7 +148,7 @@ button.red-ui-typedInput-option-trigger
text-decoration: none;
}
&.red-ui-typedInput-full-width {
- width: 100%;
+ flex-grow: 1;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
@@ -167,7 +164,6 @@ button.red-ui-typedInput-option-expand {
border-bottom-right-radius: 4px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
- right: 0;
}
button.red-ui-typedInput-option-trigger {
@@ -176,27 +172,23 @@ button.red-ui-typedInput-option-trigger {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
padding: 0 0 0 0;
- position:absolute;
- right: 0;
+ position:relative;
+ flex-grow: 1;
+ line-height: 32px;
+ display: inline-flex;
.red-ui-typedInput-option-label {
background:$form-button-background;
color: $form-text-color;
- position:absolute;
- left:0;
- right:23px;
- top: 0;
- padding: 0 5px 0 8px;
- i.red-ui-typedInput-icon {
- margin-right: 4px;
- }
+ flex-grow: 1;
+ padding: 0 0 0 8px;
+ display:inline-block;
}
.red-ui-typedInput-option-caret {
- top: 0;
- position: absolute;
- right: 0;
- bottom: 0;
- width: 17px;
- padding-left: 5px;
+ flex-grow: 0;
+ display:inline-block;
+ width: 23px;
+ text-align: center;
+ height: 100%;
&:before {
content:'';
display: inline-block;
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss b/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss
index f6255eacf..2adfb89ba 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss
@@ -112,7 +112,7 @@
position: absolute;
bottom: 0;
right:0;
- zIndex: 101;
+ z-index: 101;
border-left: 1px solid $primary-border-color;
border-top: 1px solid $primary-border-color;
background: $view-navigator-background;
@@ -122,7 +122,7 @@
stroke-dasharray: 5,5;
pointer-events: none;
stroke: $secondary-border-color;
- strokeWidth: 1;
+ stroke-width: 1;
fill: $view-background;
}
@@ -172,3 +172,44 @@ button.red-ui-footer-button-toggle {
margin-right: 0;
}
}
+
+
+#red-ui-loading-progress {
+ position: absolute;
+ background: $primary-background;
+ top: 0;
+ bottom: 0;
+ right: 0;
+ left: 0;
+ z-index: 200;
+ & > div {
+ position: absolute;
+ top: 30%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 300px;
+ height:80px;
+ text-align: center;
+ color: $secondary-text-color;
+
+ }
+}
+.red-ui-loading-bar {
+ box-sizing: border-box;
+ width: 300px;
+ height: 30px;
+ border: 2px solid $primary-border-color;
+ border-radius: 4px;
+
+ > span {
+ display: block;
+ height: 100%;
+ background: $secondary-border-color;
+ transition: width 0.2s;
+ width: 10%;
+ }
+}
+.red-ui-loading-bar-label {
+ font-size: 13px;
+ margin-bottom: 2px;
+}
diff --git a/packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-3.4.1.min.js b/packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-3.4.1.min.js
deleted file mode 100644
index a1c07fd80..000000000
--- a/packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-3.4.1.min.js
+++ /dev/null
@@ -1,2 +0,0 @@
-/*! jQuery v3.4.1 | (c) JS Foundation and other contributors | jquery.org/license */
-!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],E=C.document,r=Object.getPrototypeOf,s=t.slice,g=t.concat,u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.4.1",k=function(e,t){return new k.fn.init(e,t)},p=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;function d(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp($),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+$),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ne=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(m.childNodes),m.childNodes),t[m.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&((e?e.ownerDocument||e:m)!==C&&T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!A[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&U.test(t)){(s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=k),o=(l=h(t)).length;while(o--)l[o]="#"+s+" "+xe(l[o]);c=l.join(","),f=ee.test(t)&&ye(e.parentNode)||e}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){A(t,!0)}finally{s===k&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[k]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:m;return r!==C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),m!==C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=k,!C.getElementsByName||!C.getElementsByName(k).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){a.appendChild(e).innerHTML=" ",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+k+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+k+"+*").length||v.push(".#.+[+~]")}),ce(function(e){e.innerHTML=" ";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",$)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e===C||e.ownerDocument===m&&y(m,e)?-1:t===C||t.ownerDocument===m&&y(m,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===C?-1:t===C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]===m?-1:s[r]===m?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if((e.ownerDocument||e)!==C&&T(e),d.matchesSelector&&E&&!A[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){A(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=p[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&p(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?k.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?k.grep(e,function(e){return e===n!==r}):"string"!=typeof n?k.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(k.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:L.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof k?t[0]:t,k.merge(this,k.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),D.test(r[1])&&k.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(k):k.makeArray(e,this)}).prototype=k.fn,q=k(E);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}k.fn.extend({has:function(e){var t=k(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""," "],thead:[1,""],col:[2,""],tr:[2,""],td:[3,""],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?k.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;nx",y.noCloneChecked=!!me.cloneNode(!0).lastChild.defaultValue;var Te=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ee=/^([^.]*)(?:\.(.+)|)/;function ke(){return!0}function Se(){return!1}function Ne(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function Ae(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Ae(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Se;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return k().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=k.guid++)),e.each(function(){k.event.add(this,t,i,r,n)})}function De(e,i,o){o?(Q.set(e,i,!1),k.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Q.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(k.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Q.set(this,i,r),t=o(this,i),this[i](),r!==(n=Q.get(this,i))||t?Q.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(Q.set(this,i,{value:k.event.trigger(k.extend(r[0],k.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Q.get(e,i)&&k.event.add(e,i,ke)}k.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.get(t);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&k.find.matchesSelector(ie,i),n.guid||(n.guid=k.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof k&&k.event.triggered!==e.type?k.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(R)||[""]).length;while(l--)d=g=(s=Ee.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=k.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=k.event.special[d]||{},c=k.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&k.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),k.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.hasData(e)&&Q.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(R)||[""]).length;while(l--)if(d=g=(s=Ee.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=k.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||k.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)k.event.remove(e,d+t[l],n,r,!0);k.isEmptyObject(u)&&Q.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=k.event.fix(e),u=new Array(arguments.length),l=(Q.get(this,"events")||{})[s.type]||[],c=k.event.special[s.type]||{};for(u[0]=s,t=1;t\x20\t\r\n\f]*)[^>]*)\/>/gi,qe=/
diff --git a/packages/node_modules/@node-red/nodes/core/common/20-inject.js b/packages/node_modules/@node-red/nodes/core/common/20-inject.js
index c0d9e0c2f..54715e131 100644
--- a/packages/node_modules/@node-red/nodes/core/common/20-inject.js
+++ b/packages/node_modules/@node-red/nodes/core/common/20-inject.js
@@ -20,9 +20,32 @@ module.exports = function(RED) {
function InjectNode(n) {
RED.nodes.createNode(this,n);
- this.topic = n.topic;
- this.payload = n.payload;
- this.payloadType = n.payloadType;
+
+ /* Handle legacy */
+ if(!Array.isArray(n.props)){
+ n.props = [];
+ n.props.push({
+ p:'payload',
+ v:n.payload,
+ vt:n.payloadType
+ });
+ n.props.push({
+ p:'topic',
+ v:n.topic,
+ vt:'str'
+ });
+ } else {
+ for (var i=0,l=n.props.length; i 2147483) {
node.error(RED._("inject.errors.toolong", this));
delete node.repeat;
}
node.repeaterSetup = function () {
- if (this.repeat && !isNaN(this.repeat) && this.repeat > 0) {
- this.repeat = this.repeat * 1000;
- if (RED.settings.verbose) {
- this.log(RED._("inject.repeat", this));
+ if (this.repeat && !isNaN(this.repeat) && this.repeat > 0) {
+ this.repeat = this.repeat * 1000;
+ if (RED.settings.verbose) {
+ this.log(RED._("inject.repeat", this));
+ }
+ this.interval_id = setInterval(function() {
+ node.emit("input", {});
+ }, this.repeat);
+ } else if (this.crontab) {
+ if (RED.settings.verbose) {
+ this.log(RED._("inject.crontab", this));
+ }
+ this.cronjob = new cron.CronJob(this.crontab, function() { node.emit("input", {}); }, null, true);
}
- this.interval_id = setInterval(function() {
- node.emit("input", {});
- }, this.repeat);
- } else if (this.crontab) {
- if (RED.settings.verbose) {
- this.log(RED._("inject.crontab", this));
- }
- this.cronjob = new cron.CronJob(this.crontab, function() { node.emit("input", {}); }, null, true);
- }
};
if (this.once) {
this.onceTimeout = setTimeout( function() {
- node.emit("input",{});
- node.repeaterSetup();
+ node.emit("input",{});
+ node.repeaterSetup();
}, this.onceDelay);
} else {
- node.repeaterSetup();
+ node.repeaterSetup();
}
- this.on("input",function(msg) {
- msg.topic = this.topic;
- if (this.payloadType !== 'flow' && this.payloadType !== 'global') {
- try {
- if ( (this.payloadType == null && this.payload === "") || this.payloadType === "date") {
- msg.payload = Date.now();
- } else if (this.payloadType == null) {
- msg.payload = this.payload;
- } else if (this.payloadType === 'none') {
- msg.payload = "";
- } else {
- msg.payload = RED.util.evaluateNodeProperty(this.payload,this.payloadType,this,msg);
- }
- this.send(msg);
- msg = null;
- } catch(err) {
- this.error(err,msg);
- }
- } else {
- RED.util.evaluateNodeProperty(this.payload,this.payloadType,this,msg, function(err,res) {
- if (err) {
- node.error(err,msg);
- } else {
- msg.payload = res;
- node.send(msg);
- }
+ this.on("input", function(msg) {
+ var errors = [];
- });
+ this.props.forEach(p => {
+ var property = p.p;
+ var value = p.v ? p.v : '';
+ var valueType = p.vt ? p.vt : 'str';
+
+ if (!property) return;
+
+ if (valueType === "jsonata") {
+ if (p.exp) {
+ try {
+ var val = RED.util.evaluateJSONataExpression(p.exp, msg);
+ RED.util.setMessageProperty(msg, property, val, true);
+ }
+ catch (err) {
+ errors.push(err.message);
+ }
+ }
+ return;
+ }
+ try {
+ RED.util.setMessageProperty(msg,property,RED.util.evaluateNodeProperty(value, valueType, this, msg),true);
+ } catch (err) {
+ errors.push(err.toString());
+ }
+ });
+
+ if (errors.length) {
+ node.error(errors.join('; '), msg);
+ } else {
+ node.send(msg);
}
});
}
diff --git a/packages/node_modules/@node-red/nodes/core/common/21-debug.html b/packages/node_modules/@node-red/nodes/core/common/21-debug.html
index 5e2f3ba49..d12279c74 100644
--- a/packages/node_modules/@node-red/nodes/core/common/21-debug.html
+++ b/packages/node_modules/@node-red/nodes/core/common/21-debug.html
@@ -1,30 +1,35 @@
-
-
diff --git a/packages/node_modules/@node-red/nodes/core/common/lib/debug/debug-utils.js b/packages/node_modules/@node-red/nodes/core/common/lib/debug/debug-utils.js
index ddab5bbe2..64068ea86 100644
--- a/packages/node_modules/@node-red/nodes/core/common/lib/debug/debug-utils.js
+++ b/packages/node_modules/@node-red/nodes/core/common/lib/debug/debug-utils.js
@@ -457,7 +457,7 @@ RED.debug = (function() {
var metaRow = $('
').appendTo(msg);
$(''+ getTimestamp()+' ').appendTo(metaRow);
if (sourceNode) {
- $('',{href:"#",class:"red-ui-debug-msg-name"}).text('node: '+sanitize(o.name||sourceNode.name||sourceNode.id))
+ $(' ',{href:"#",class:"red-ui-debug-msg-name"}).text('node: '+(o.name||sourceNode.name||sourceNode.id))
.appendTo(metaRow)
.on("click", function(evt) {
evt.preventDefault();
diff --git a/packages/node_modules/@node-red/nodes/core/function/10-function.html b/packages/node_modules/@node-red/nodes/core/function/10-function.html
index 9202d96ba..78e7f2d63 100644
--- a/packages/node_modules/@node-red/nodes/core/function/10-function.html
+++ b/packages/node_modules/@node-red/nodes/core/function/10-function.html
@@ -1,22 +1,57 @@
-
diff --git a/packages/node_modules/@node-red/nodes/core/function/10-function.js b/packages/node_modules/@node-red/nodes/core/function/10-function.js
index 65a1b4a61..a6573a787 100644
--- a/packages/node_modules/@node-red/nodes/core/function/10-function.js
+++ b/packages/node_modules/@node-red/nodes/core/function/10-function.js
@@ -57,22 +57,55 @@ module.exports = function(RED) {
}
}
+ function createVMOpt(node, kind) {
+ var opt = {
+ filename: 'Function node'+kind+':'+node.id+(node.name?' ['+node.name+']':''), // filename for stack traces
+ displayErrors: true
+ // Using the following options causes node 4/6 to not include the line number
+ // in the stack output. So don't use them.
+ // lineOffset: -11, // line number offset to be used for stack traces
+ // columnOffset: 0, // column number offset to be used for stack traces
+ };
+ return opt;
+ }
+
+ function updateErrorInfo(err) {
+ if (err.stack) {
+ var stack = err.stack.toString();
+ var m = /^([^:]+):([^:]+):(\d+).*/.exec(stack);
+ if (m) {
+ var line = parseInt(m[3]) -1;
+ var kind = "body:";
+ if (/setup/.exec(m[1])) {
+ kind = "setup:";
+ }
+ if (/cleanup/.exec(m[1])) {
+ kind = "cleanup:";
+ }
+ err.message += " ("+kind+"line "+line+")";
+ }
+ }
+ }
+
function FunctionNode(n) {
RED.nodes.createNode(this,n);
var node = this;
- this.name = n.name;
- this.func = n.func;
+ node.name = n.name;
+ node.func = n.func;
+ node.ini = n.initialize ? n.initialize : "";
+ node.fin = n.finalize ? n.finalize : "";
var handleNodeDoneCall = true;
+
// Check to see if the Function appears to call `node.done()`. If so,
// we will assume it is well written and does actually call node.done().
// Otherwise, we will call node.done() after the function returns regardless.
- if (/node\.done\s*\(\s*\)/.test(this.func)) {
+ if (/node\.done\s*\(\s*\)/.test(node.func)) {
handleNodeDoneCall = false;
}
var functionText = "var results = null;"+
- "results = (function(msg,__send__,__done__){ "+
+ "results = (async function(msg,__send__,__done__){ "+
"var __msgid__ = msg._msgid;"+
"var node = {"+
"id:__node__.id,"+
@@ -87,11 +120,13 @@ module.exports = function(RED) {
"send:function(msgs,cloneMsg){ __node__.send(__send__,__msgid__,msgs,cloneMsg);},"+
"done:__done__"+
"};\n"+
- this.func+"\n"+
+ node.func+"\n"+
"})(msg,send,done);";
- this.topic = n.topic;
- this.outstandingTimers = [];
- this.outstandingIntervals = [];
+ var finScript = null;
+ var finOpt = null;
+ node.topic = n.topic;
+ node.outstandingTimers = [];
+ node.outstandingIntervals = [];
var sandbox = {
console:console,
util:util,
@@ -182,12 +217,12 @@ module.exports = function(RED) {
arguments[0] = function() {
sandbox.clearTimeout(timerId);
try {
- func.apply(this,arguments);
+ func.apply(node,arguments);
} catch(err) {
node.error(err,{});
}
};
- timerId = setTimeout.apply(this,arguments);
+ timerId = setTimeout.apply(node,arguments);
node.outstandingTimers.push(timerId);
return timerId;
},
@@ -203,12 +238,12 @@ module.exports = function(RED) {
var timerId;
arguments[0] = function() {
try {
- func.apply(this,arguments);
+ func.apply(node,arguments);
} catch(err) {
node.error(err,{});
}
};
- timerId = setInterval.apply(this,arguments);
+ timerId = setInterval.apply(node,arguments);
node.outstandingIntervals.push(timerId);
return timerId;
},
@@ -226,37 +261,48 @@ module.exports = function(RED) {
sandbox.setTimeout(function(){ resolve(value); }, after);
});
};
+ sandbox.promisify = util.promisify;
}
var context = vm.createContext(sandbox);
try {
- this.script = vm.createScript(functionText, {
- filename: 'Function node:'+this.id+(this.name?' ['+this.name+']':''), // filename for stack traces
- displayErrors: true
- // Using the following options causes node 4/6 to not include the line number
- // in the stack output. So don't use them.
- // lineOffset: -11, // line number offset to be used for stack traces
- // columnOffset: 0, // column number offset to be used for stack traces
- });
- this.on("input", function(msg,send,done) {
- try {
- var start = process.hrtime();
- context.msg = msg;
- context.send = send;
- context.done = done;
+ var iniScript = null;
+ var iniOpt = null;
+ if (node.ini && (node.ini !== "")) {
+ var iniText = "(async function () {\n"+node.ini +"\n})();";
+ iniOpt = createVMOpt(node, " setup");
+ iniScript = new vm.Script(iniText, iniOpt);
+ }
+ node.script = vm.createScript(functionText, createVMOpt(node, ""));
+ if (node.fin && (node.fin !== "")) {
+ var finText = "(function () {\n"+node.fin +"\n})();";
+ finOpt = createVMOpt(node, " cleanup");
+ finScript = new vm.Script(finText, finOpt);
+ }
+ var promise = Promise.resolve();
+ if (iniScript) {
+ promise = iniScript.runInContext(context, iniOpt);
+ }
- this.script.runInContext(context);
- sendResults(this,send,msg._msgid,context.results,false);
+ function processMessage(msg, send, done) {
+ var start = process.hrtime();
+ context.msg = msg;
+ context.send = send;
+ context.done = done;
+
+ node.script.runInContext(context);
+ context.results.then(function(results) {
+ sendResults(node,send,msg._msgid,results,false);
if (handleNodeDoneCall) {
done();
}
var duration = process.hrtime(start);
var converted = Math.floor((duration[0] * 1e9 + duration[1])/10000)/100;
- this.metric("duration", msg, converted);
+ node.metric("duration", msg, converted);
if (process.env.NODE_RED_FUNCTION_TIME) {
- this.status({fill:"yellow",shape:"dot",text:""+converted});
+ node.status({fill:"yellow",shape:"dot",text:""+converted});
}
- } catch(err) {
+ }).catch(err => {
if ((typeof err === "object") && err.hasOwnProperty("stack")) {
//remove unwanted part
var index = err.stack.search(/\n\s*at ContextifyScript.Script.runInContext/);
@@ -294,23 +340,67 @@ module.exports = function(RED) {
else {
done(JSON.stringify(err));
}
+ });
+ }
+
+ const RESOLVING = 0;
+ const RESOLVED = 1;
+ const ERROR = 2;
+ var state = RESOLVING;
+ var messages = [];
+
+ node.on("input", function(msg,send,done) {
+ if(state === RESOLVING) {
+ messages.push({msg:msg, send:send, done:done});
+ }
+ else if(state === RESOLVED) {
+ processMessage(msg, send, done);
}
});
- this.on("close", function() {
+ node.on("close", function() {
+ if (finScript) {
+ try {
+ finScript.runInContext(context, finOpt);
+ }
+ catch (err) {
+ node.error(err);
+ }
+ }
while (node.outstandingTimers.length > 0) {
clearTimeout(node.outstandingTimers.pop());
}
while (node.outstandingIntervals.length > 0) {
clearInterval(node.outstandingIntervals.pop());
}
- this.status({});
+ node.status({});
});
- } catch(err) {
+
+ promise.then(function (v) {
+ var msgs = messages;
+ messages = [];
+ while (msgs.length > 0) {
+ msgs.forEach(function (s) {
+ processMessage(s.msg, s.send, s.done);
+ });
+ msgs = messages;
+ messages = [];
+ }
+ state = RESOLVED;
+ }).catch((error) => {
+ messages = [];
+ state = ERROR;
+ node.error(error);
+ });
+
+ }
+ catch(err) {
// eg SyntaxError - which v8 doesn't include line number information
// so we can't do better than this
- this.error(err);
+ updateErrorInfo(err);
+ node.error(err);
}
}
RED.nodes.registerType("function",FunctionNode);
RED.library.register("functions");
};
+
diff --git a/packages/node_modules/@node-red/nodes/core/function/15-change.html b/packages/node_modules/@node-red/nodes/core/function/15-change.html
index fc9fa8d84..59d4337c5 100644
--- a/packages/node_modules/@node-red/nodes/core/function/15-change.html
+++ b/packages/node_modules/@node-red/nodes/core/function/15-change.html
@@ -1,5 +1,10 @@
diff --git a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js
index 48a595ccc..cda1afadf 100644
--- a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js
+++ b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js
@@ -24,6 +24,8 @@ module.exports = function(RED) {
this.op2 = n.op2 || "0";
this.op1type = n.op1type || "str";
this.op2type = n.op2type || "str";
+ this.second = (n.outputs == 2) ? true : false;
+ this.topic = n.topic || "topic";
if (this.op1type === 'val') {
if (this.op1 === 'true' || this.op1 === 'false') {
@@ -76,6 +78,7 @@ module.exports = function(RED) {
var node = this;
node.topics = {};
+ var npay = {};
var pendingMessages = [];
var activeMessagePromise = null;
var processMessageQueue = function(msg) {
@@ -110,8 +113,15 @@ module.exports = function(RED) {
processMessageQueue(msg);
});
+ var stat = function() {
+ var l = Object.keys(node.topics).length;
+ if (l === 0) { return {} }
+ else if (l === 1) { return {fill:"blue",shape:"dot"} }
+ else return {fill:"blue",shape:"dot",text:l};
+ }
+
var processMessage = function(msg) {
- var topic = msg.topic || "_none";
+ var topic = RED.util.getMessageProperty(msg,node.topic) || "_none";
var promise;
if (node.bytopic === "all") { topic = "_none"; }
node.topics[topic] = node.topics[topic] || {};
@@ -119,12 +129,13 @@ module.exports = function(RED) {
if (node.loop === true) { clearInterval(node.topics[topic].tout); }
else { clearTimeout(node.topics[topic].tout); }
delete node.topics[topic];
- node.status({});
+ node.status(stat());
}
else {
+ if (node.op2type === "payl") { npay[topic] = RED.util.cloneMessage(msg); }
if (((!node.topics[topic].tout) && (node.topics[topic].tout !== 0)) || (node.loop === true)) {
promise = Promise.resolve();
- if (node.op2type === "pay" || node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); }
+ if (node.op2type === "pay") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); }
else if (node.op2Templated) { node.topics[topic].m2 = mustache.render(node.op2,msg); }
else if (node.op2type !== "nul") {
promise = new Promise((resolve,reject) => {
@@ -186,22 +197,30 @@ module.exports = function(RED) {
});
}
promise.then(() => {
- msg2.payload = node.topics[topic].m2;
+ if (node.op2type === "payl") {
+ if (node.second === true) { node.send([null,npay[topic]]); }
+ else { node.send(npay[topic]); }
+ delete npay[topic];
+ }
+ else {
+ msg2.payload = node.topics[topic].m2;
+ if (node.second === true) { node.send([null,msg2]); }
+ else { node.send(msg2); }
+ }
delete node.topics[topic];
- node.send(msg2);
- node.status({});
+ node.status(stat());
}).catch(err => {
node.error(err);
});
} else {
delete node.topics[topic];
- node.status({});
+ node.status(stat());
}
}, node.duration);
}
}
- node.status({fill:"blue",shape:"dot",text:" "});
+ node.status(stat());
if (node.op1type !== "nul") { node.send(RED.util.cloneMessage(msg)); }
});
});
@@ -237,16 +256,17 @@ module.exports = function(RED) {
}
}
delete node.topics[topic];
- node.status({});
- node.send(msg2);
+ node.status(stat());
+ if (node.second === true) { node.send([null,msg2]); }
+ else { node.send(msg2); }
}).catch(err => {
node.error(err);
});
}, node.duration);
}
- else {
- if (node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); }
- }
+ // else {
+ // if (node.op2type === "payl") {node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); }
+ // }
}
return Promise.resolve();
}
@@ -259,7 +279,7 @@ module.exports = function(RED) {
delete node.topics[t];
}
}
- node.status({});
+ node.status(stat());
});
}
RED.nodes.registerType("trigger",TriggerNode);
diff --git a/packages/node_modules/@node-red/nodes/core/function/90-exec.html b/packages/node_modules/@node-red/nodes/core/function/90-exec.html
index c6102d397..4a4e8942c 100644
--- a/packages/node_modules/@node-red/nodes/core/function/90-exec.html
+++ b/packages/node_modules/@node-red/nodes/core/function/90-exec.html
@@ -14,7 +14,7 @@
limitations under the License.
-->
-
-
-
-
-
-
-
-
-
diff --git a/packages/node_modules/@node-red/nodes/core/sequence/19-batch.html b/packages/node_modules/@node-red/nodes/core/sequence/19-batch.html
index f3cb47a54..418ac605b 100644
--- a/packages/node_modules/@node-red/nodes/core/sequence/19-batch.html
+++ b/packages/node_modules/@node-red/nodes/core/sequence/19-batch.html
@@ -60,8 +60,8 @@
-
-
+
+
diff --git a/packages/node_modules/@node-red/nodes/core/sequence/19-batch.js b/packages/node_modules/@node-red/nodes/core/sequence/19-batch.js
index 495862af8..77574f222 100644
--- a/packages/node_modules/@node-red/nodes/core/sequence/19-batch.js
+++ b/packages/node_modules/@node-red/nodes/core/sequence/19-batch.js
@@ -179,6 +179,11 @@ module.exports = function(RED) {
}
node.pending = [];
this.on("input", function(msg) {
+ if (msg.hasOwnProperty("reset")) {
+ node.pending = [];
+ node.pending_count = 0;
+ return;
+ }
var queue = node.pending;
queue.push(msg);
node.pending_count++;
@@ -204,11 +209,26 @@ module.exports = function(RED) {
var interval = Number(n.interval || "0") *1000;
var allow_empty_seq = n.allowEmptySequence;
node.pending = []
- var timer = setInterval(function() {
+ function msgHandler() {
send_interval(node, allow_empty_seq);
node.pending_count = 0;
- }, interval);
+ }
+ var timer = undefined;
+ if (interval > 0) {
+ timer = setInterval(msgHandler, interval);
+ }
this.on("input", function(msg) {
+ if (msg.hasOwnProperty("reset")) {
+ if (timer !== undefined) {
+ clearInterval(timer);
+ }
+ node.pending = [];
+ node.pending_count = 0;
+ if (interval > 0) {
+ timer = setInterval(msgHandler, interval);
+ }
+ return;
+ }
node.pending.push(msg);
node.pending_count++;
var max_msgs = max_kept_msgs_count(node);
@@ -219,7 +239,9 @@ module.exports = function(RED) {
}
});
this.on("close", function() {
- clearInterval(timer);
+ if (timer !== undefined) {
+ clearInterval(timer);
+ }
node.pending = [];
node.pending_count = 0;
});
@@ -230,6 +252,11 @@ module.exports = function(RED) {
});
node.pending = {};
this.on("input", function(msg) {
+ if (msg.hasOwnProperty("reset")) {
+ node.pending = {};
+ node.pending_count = 0;
+ return;
+ }
concat_msg(node, msg);
});
this.on("close", function() {
diff --git a/packages/node_modules/@node-red/nodes/core/storage/10-file.html b/packages/node_modules/@node-red/nodes/core/storage/10-file.html
index d20ad75c5..5487920b8 100755
--- a/packages/node_modules/@node-red/nodes/core/storage/10-file.html
+++ b/packages/node_modules/@node-red/nodes/core/storage/10-file.html
@@ -1,5 +1,5 @@
-
-
-
diff --git a/packages/node_modules/@node-red/nodes/locales/de/network/06-httpproxy.html b/packages/node_modules/@node-red/nodes/locales/de/network/06-httpproxy.html
index 228da487b..1ece7efe0 100755
--- a/packages/node_modules/@node-red/nodes/locales/de/network/06-httpproxy.html
+++ b/packages/node_modules/@node-red/nodes/locales/de/network/06-httpproxy.html
@@ -14,7 +14,7 @@
limitations under the License.
-->
-
-
-
-
-
-
-
-
-
-
diff --git a/packages/node_modules/@node-red/nodes/locales/de/network/31-tcpin.html b/packages/node_modules/@node-red/nodes/locales/de/network/31-tcpin.html
index 2b22418dc..51ec15c6f 100755
--- a/packages/node_modules/@node-red/nodes/locales/de/network/31-tcpin.html
+++ b/packages/node_modules/@node-red/nodes/locales/de/network/31-tcpin.html
@@ -14,13 +14,13 @@
limitations under the License.
-->
-
-
-
-
-
-
-
diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/function/90-exec.html b/packages/node_modules/@node-red/nodes/locales/en-US/function/90-exec.html
index 69447bc60..ff515e781 100644
--- a/packages/node_modules/@node-red/nodes/locales/en-US/function/90-exec.html
+++ b/packages/node_modules/@node-red/nodes/locales/en-US/function/90-exec.html
@@ -14,7 +14,7 @@
limitations under the License.
-->
-
diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/network/06-httpproxy.html b/packages/node_modules/@node-red/nodes/locales/en-US/network/06-httpproxy.html
index b17012e72..12bb3684c 100644
--- a/packages/node_modules/@node-red/nodes/locales/en-US/network/06-httpproxy.html
+++ b/packages/node_modules/@node-red/nodes/locales/en-US/network/06-httpproxy.html
@@ -14,7 +14,7 @@
limitations under the License.
-->
-
-
-
-
-
-
-
diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/network/31-tcpin.html b/packages/node_modules/@node-red/nodes/locales/en-US/network/31-tcpin.html
index e8cb29ffc..173f003f7 100644
--- a/packages/node_modules/@node-red/nodes/locales/en-US/network/31-tcpin.html
+++ b/packages/node_modules/@node-red/nodes/locales/en-US/network/31-tcpin.html
@@ -14,14 +14,14 @@
limitations under the License.
-->
-
-
-
diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/network/32-udp.html b/packages/node_modules/@node-red/nodes/locales/en-US/network/32-udp.html
index 09e6aef34..666889be6 100644
--- a/packages/node_modules/@node-red/nodes/locales/en-US/network/32-udp.html
+++ b/packages/node_modules/@node-red/nodes/locales/en-US/network/32-udp.html
@@ -14,7 +14,7 @@
limitations under the License.
-->
-
-
diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-HTML.html b/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-HTML.html
index b9231663d..7b081976c 100644
--- a/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-HTML.html
+++ b/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-HTML.html
@@ -14,7 +14,7 @@
limitations under the License.
-->
-
-
diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/storage/10-file.html b/packages/node_modules/@node-red/nodes/locales/en-US/storage/10-file.html
index 26701d706..fe2bbc324 100644
--- a/packages/node_modules/@node-red/nodes/locales/en-US/storage/10-file.html
+++ b/packages/node_modules/@node-red/nodes/locales/en-US/storage/10-file.html
@@ -14,7 +14,7 @@
limitations under the License.
-->
-
-
diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/storage/23-watch.html b/packages/node_modules/@node-red/nodes/locales/en-US/storage/23-watch.html
index 4a28cfcab..bc6e4ad31 100644
--- a/packages/node_modules/@node-red/nodes/locales/en-US/storage/23-watch.html
+++ b/packages/node_modules/@node-red/nodes/locales/en-US/storage/23-watch.html
@@ -14,7 +14,7 @@
limitations under the License.
-->
-
-
diff --git a/packages/node_modules/@node-red/nodes/locales/ja/function/90-exec.html b/packages/node_modules/@node-red/nodes/locales/ja/function/90-exec.html
index 7eb75b10a..d034f71e1 100644
--- a/packages/node_modules/@node-red/nodes/locales/ja/function/90-exec.html
+++ b/packages/node_modules/@node-red/nodes/locales/ja/function/90-exec.html
@@ -14,7 +14,7 @@
limitations under the License.
-->
-
diff --git a/packages/node_modules/@node-red/nodes/locales/ja/network/06-httpproxy.html b/packages/node_modules/@node-red/nodes/locales/ja/network/06-httpproxy.html
index 03beddb8d..24b15aa29 100644
--- a/packages/node_modules/@node-red/nodes/locales/ja/network/06-httpproxy.html
+++ b/packages/node_modules/@node-red/nodes/locales/ja/network/06-httpproxy.html
@@ -14,7 +14,7 @@
limitations under the License.
-->
-
-
-
-
-
-
-
diff --git a/packages/node_modules/@node-red/nodes/locales/ja/network/31-tcpin.html b/packages/node_modules/@node-red/nodes/locales/ja/network/31-tcpin.html
index efe82cbb1..e63c93ae5 100644
--- a/packages/node_modules/@node-red/nodes/locales/ja/network/31-tcpin.html
+++ b/packages/node_modules/@node-red/nodes/locales/ja/network/31-tcpin.html
@@ -14,12 +14,12 @@
limitations under the License.
-->
-
-
-
diff --git a/packages/node_modules/@node-red/nodes/locales/ja/network/32-udp.html b/packages/node_modules/@node-red/nodes/locales/ja/network/32-udp.html
index ec07a865e..fffcbcec5 100644
--- a/packages/node_modules/@node-red/nodes/locales/ja/network/32-udp.html
+++ b/packages/node_modules/@node-red/nodes/locales/ja/network/32-udp.html
@@ -14,13 +14,13 @@
limitations under the License.
-->
-
-
diff --git a/packages/node_modules/@node-red/nodes/locales/ja/parsers/70-HTML.html b/packages/node_modules/@node-red/nodes/locales/ja/parsers/70-HTML.html
index eb7b00dff..19be36aed 100644
--- a/packages/node_modules/@node-red/nodes/locales/ja/parsers/70-HTML.html
+++ b/packages/node_modules/@node-red/nodes/locales/ja/parsers/70-HTML.html
@@ -14,7 +14,7 @@
limitations under the License.
-->
-
-
diff --git a/packages/node_modules/@node-red/nodes/locales/ja/storage/10-file.html b/packages/node_modules/@node-red/nodes/locales/ja/storage/10-file.html
index 2df174104..21d4af9be 100644
--- a/packages/node_modules/@node-red/nodes/locales/ja/storage/10-file.html
+++ b/packages/node_modules/@node-red/nodes/locales/ja/storage/10-file.html
@@ -14,7 +14,7 @@
limitations under the License.
-->
-
-
diff --git a/packages/node_modules/@node-red/nodes/locales/ja/storage/23-watch.html b/packages/node_modules/@node-red/nodes/locales/ja/storage/23-watch.html
index 45f658c05..3a4594ccc 100644
--- a/packages/node_modules/@node-red/nodes/locales/ja/storage/23-watch.html
+++ b/packages/node_modules/@node-red/nodes/locales/ja/storage/23-watch.html
@@ -14,7 +14,7 @@
limitations under the License.
-->
-
diff --git a/packages/node_modules/@node-red/nodes/locales/ko/network/06-httpproxy.html b/packages/node_modules/@node-red/nodes/locales/ko/network/06-httpproxy.html
index 4357c7f2b..cdfccd799 100644
--- a/packages/node_modules/@node-red/nodes/locales/ko/network/06-httpproxy.html
+++ b/packages/node_modules/@node-red/nodes/locales/ko/network/06-httpproxy.html
@@ -14,7 +14,7 @@
limitations under the License.
-->
-
-
-
-
-
-
-
diff --git a/packages/node_modules/@node-red/nodes/locales/ko/network/31-tcpin.html b/packages/node_modules/@node-red/nodes/locales/ko/network/31-tcpin.html
index 1c665bb8d..53c61e679 100644
--- a/packages/node_modules/@node-red/nodes/locales/ko/network/31-tcpin.html
+++ b/packages/node_modules/@node-red/nodes/locales/ko/network/31-tcpin.html
@@ -14,12 +14,12 @@
limitations under the License.
-->
-
-
-
-
-
-
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/common/21-debug.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/21-debug.html
new file mode 100644
index 000000000..e3137c50d
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/21-debug.html
@@ -0,0 +1,25 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/common/24-complete.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/24-complete.html
new file mode 100644
index 000000000..e69ebc6a0
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/24-complete.html
@@ -0,0 +1,24 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/common/25-catch.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/25-catch.html
new file mode 100644
index 000000000..5b2b4c3b2
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/25-catch.html
@@ -0,0 +1,36 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/common/25-status.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/25-status.html
new file mode 100644
index 000000000..7d9504c9f
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/25-status.html
@@ -0,0 +1,33 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/common/60-link.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/60-link.html
new file mode 100644
index 000000000..6f2bc5f99
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/60-link.html
@@ -0,0 +1,31 @@
+
+
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/common/90-comment.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/90-comment.html
new file mode 100644
index 000000000..f98577ff4
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/90-comment.html
@@ -0,0 +1,21 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/common/98-unknown.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/98-unknown.html
new file mode 100644
index 000000000..108e19228
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/98-unknown.html
@@ -0,0 +1,24 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/function/10-function.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/10-function.html
new file mode 100644
index 000000000..035ccc81f
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/10-function.html
@@ -0,0 +1,51 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/function/10-switch.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/10-switch.html
new file mode 100644
index 000000000..7ad25c24f
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/10-switch.html
@@ -0,0 +1,37 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/function/15-change.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/15-change.html
new file mode 100644
index 000000000..fcba3fed4
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/15-change.html
@@ -0,0 +1,33 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/function/16-range.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/16-range.html
new file mode 100644
index 000000000..b5d2d033f
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/16-range.html
@@ -0,0 +1,40 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/function/80-template.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/80-template.html
new file mode 100644
index 000000000..938a77818
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/80-template.html
@@ -0,0 +1,46 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/function/89-delay.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/89-delay.html
new file mode 100644
index 000000000..690bddaea
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/89-delay.html
@@ -0,0 +1,32 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/function/89-trigger.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/89-trigger.html
new file mode 100644
index 000000000..5f27a5002
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/89-trigger.html
@@ -0,0 +1,33 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/function/90-exec.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/90-exec.html
new file mode 100644
index 000000000..27c421160
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/90-exec.html
@@ -0,0 +1,74 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json b/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json
index 9202bb3a7..934cc734d 100644
--- a/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json
@@ -6,7 +6,9 @@
"name": "名称",
"username": "用户名",
"password": "密码",
- "property": "属性"
+ "property": "属性",
+ "selectNodes": "选择节点...",
+ "expand": "扩展"
},
"status": {
"connected": "已连接",
@@ -35,7 +37,22 @@
"stopped": "停止",
"failed": "注入失败: __error__",
"label": {
- "repeat": "重复"
+ "repeat": "重复",
+ "flow": "流上下午",
+ "global": "全局上下文",
+ "str": "字符串",
+ "num": "数值",
+ "bool": "布尔值",
+ "json": "JSON对象",
+ "bin": "buffer",
+ "date": "时间戳",
+ "env": "环境变量",
+ "object": "对象",
+ "string": "字符串",
+ "boolean": "布尔值",
+ "number": "数值",
+ "Array": "数组",
+ "invalid": "无效的JSON对象"
},
"timestamp": "时间戳",
"none": "无",
@@ -70,15 +87,13 @@
}
},
"catch": {
- "catch": "监测所有节点",
- "catchNodes": "监测__number__个节点",
+ "catch": "捕获:所有节点",
+ "catchNodes": "捕获:__number__个节点",
+ "catchUncaught": "捕获:未捕获",
"label": {
- "source": "监测范围",
- "node": "节点",
- "type": "类型",
+ "source": "捕获范围",
"selectAll": "全选",
- "sortByLabel": "按名称排序",
- "sortByType": "按类型排序"
+ "uncaught": "忽略其他捕获节点处理的错误"
},
"scope": {
"all": "所有节点",
@@ -90,10 +105,6 @@
"statusNodes": "报告__number__个节点状态",
"label": {
"source": "报告状态范围",
- "node": "节点",
- "type": "类型",
- "selectAll": "全选",
- "sortByLabel": "按名称排序",
"sortByType": "按类型排序"
},
"scope": {
@@ -101,8 +112,13 @@
"selected": "指定节点"
}
},
+ "complete": {
+ "completeNodes": "完成: __number__个节点"
+ },
"debug": {
"output": "输出",
+ "none": "None",
+ "invalid-exp": "无效的JSONata表达式: __error__",
"msgprop": "信息属性",
"msgobj": "完整信息",
"to": "目标",
@@ -124,7 +140,11 @@
"filterCurrent": "当前流程",
"debugNodes": "调试节点",
"clearLog": "清空日志",
- "openWindow": "在新窗口打开"
+ "filterLog": "过滤日志",
+ "openWindow": "在新窗口打开",
+ "copyPath": "复制路径",
+ "copyPayload": "复制值",
+ "pinPath": "固定展开"
},
"messageMenu": {
"collapseAll": "折叠所有路径",
@@ -146,26 +166,33 @@
"key": "私钥",
"passphrase": "密码",
"ca": "CA证书",
- "verify-server-cert":"验证服务器证书"
+ "verify-server-cert":"验证服务器证书",
+ "servername": "服务器名"
},
"placeholder": {
"cert":"证书路径 (PEM 格式)",
"key":"私钥路径 (PEM 格式)",
"ca":"CA证书路径 (PEM 格式)",
- "passphrase":"私钥密码 (可选)"
+ "passphrase":"私钥密码 (可选)",
+ "servername":"用于SNI"
},
"error": {
"missing-file": "未提供证书/密钥文件"
}
},
"exec": {
+ "exec": "exec",
+ "spawn": "spawn",
"label": {
"command": "命令",
"append": "追加",
"timeout": "超时",
"timeoutplace": "可选填",
"return": "输出",
- "seconds": "秒"
+ "seconds": "秒",
+ "stdout": "标准输出",
+ "stderr": "标准错误输出",
+ "retcode": "返回码"
},
"placeholder": {
"extraparams": "额外的输入参数"
@@ -177,6 +204,7 @@
"oldrc": "使用旧式输出 (兼容模式)"
},
"function": {
+ "function": "函数",
"label": {
"function": "函数",
"outputs": "输出"
@@ -187,6 +215,7 @@
}
},
"template": {
+ "template": "模板",
"label": {
"template": "模版",
"property": "属性",
@@ -199,7 +228,7 @@
"yaml": "YAML",
"none": "无"
},
- "templatevalue": "This is the payload: {{payload}} !"
+ "templatevalue": "这是有效载荷: {{payload}} !"
},
"delay": {
"action": "行为设置",
@@ -272,6 +301,9 @@
"wait-reset": "等待被重置",
"wait-for": "等待",
"wait-loop": "周期性重发",
+ "for": "处理",
+ "bytopics": "每个msg.topic",
+ "alltopics": "所有消息",
"duration": {
"ms": "毫秒",
"s": "秒",
@@ -290,6 +322,7 @@
}
},
"comment": {
+ "comment": "注释"
},
"unknown": {
"label": {
@@ -300,9 +333,10 @@
"mqtt": {
"label": {
"broker": "服务端",
- "example": "e.g. localhost",
+ "example": "比如:本地主机",
"output": "输出",
"qos": "QoS",
+ "retain": "保持",
"clientid": "客户端ID",
"port": "端口",
"keepalive": "Keepalive计时(秒)",
@@ -312,17 +346,22 @@
"verify-server-cert":"验证服务器证书",
"compatmode": "使用旧式MQTT 3.1支持"
},
+ "sections-label":{
+ "birth-message": "连接时发送的消息(出生消息)",
+ "will-message":"意外断开连接时的发送消息(Will消息)",
+ "close-message":"断开连接前发送的消息(关闭消息)"
+ },
"tabs-label": {
"connection": "连接",
"security": "安全",
- "will": "Will信息",
- "birth": "Birth信息"
+ "messages": "消息"
},
"placeholder": {
"clientid": "留白则自动生成",
"clientid-nonclean":"如非新会话,必须设置客户端ID",
"will-topic": "留白将禁止Will信息",
- "birth-topic": "留白将禁止Birth信息"
+ "birth-topic": "留白将禁止Birth信息",
+ "close-topic": "留白以禁用关闭消息"
},
"state": {
"connected": "已连接到服务端: __broker__",
@@ -333,7 +372,9 @@
"output": {
"buffer": "Buffer",
"string": "字符串",
- "base64": "Base64编码字符串"
+ "base64": "Base64编码字符串",
+ "auto": "自动检测 (字符串或buffer)",
+ "json": "解析的JSON对象"
},
"true": "是",
"false": "否",
@@ -342,7 +383,9 @@
"not-defined": "主题未设置",
"missing-config": "未设置服务端",
"invalid-topic": "主题无效",
- "nonclean-missingclientid": "客户端ID未设定,使用新会话"
+ "nonclean-missingclientid": "客户端ID未设定,使用新会话",
+ "invalid-json-string": "无效的JSON字符串",
+ "invalid-json-parse": "无法解析JSON字符串"
}
},
"httpin": {
@@ -353,13 +396,27 @@
"return": "返回",
"upload": "接受文件上传?",
"status": "状态码",
- "headers": "Header",
- "other": "其他"
+ "headers": "头",
+ "other": "其他",
+ "paytoqs" : "将msg.payload附加为查询字符串参数",
+ "utf8String": "UTF8格式的字符串",
+ "binaryBuffer": "二进制buffer",
+ "jsonObject": "解析的JSON对象",
+ "authType": "类型",
+ "bearerToken": "Token"
},
"setby": "- 用 msg.method 设定 -",
"basicauth": "基本认证",
"use-tls": "使用安全连接 (SSL/TLS) ",
"tls-config":"TLS 设置",
+ "basic": "基本认证",
+ "digest": "摘要认证",
+ "bearer": "bearer认证",
+ "use-proxy": "使用代理服务器",
+ "persist": "对连接启用keep-alive",
+ "proxy-config": "代理服务器设置",
+ "use-proxyauth": "使用代理身份验证",
+ "noproxy-hosts": "代理例外",
"utf8": "UTF-8 字符串",
"binary": "二进制数据",
"json": "JSON对象",
@@ -376,7 +433,10 @@
"json-error": "JSON 解析错误",
"no-url": "未设定 URL",
"deprecated-call":"__method__方法已弃用",
- "invalid-transport":"非HTTP传输请求"
+ "invalid-transport":"非HTTP传输请求",
+ "timeout-isnan": "超时值不是有效数字,忽略",
+ "timeout-isnegative": "超时值为负,忽略",
+ "invalid-payload": "无效的有效载荷"
},
"status": {
"requesting": "请求中"
@@ -395,17 +455,23 @@
"message": "完整信息",
"tip": {
"path1": "默认情况下,
payload
将包含要发送或从Websocket接收的数据。侦听器可以配置为以JSON格式的字符串发送或接收整个消息对象.",
- "path2": "这条路径将相对于 ",
+ "path2": "这条路径将相对于
__path__
.",
"url1": "URL 应该使用ws://或者wss://方案并指向现有的websocket侦听器.",
"url2": "默认情况下,
payload
将包含要发送或从Websocket接收的数据。可以将客户端配置为以JSON格式的字符串发送或接收整个消息对象."
},
+ "status": {
+ "connected": "连接数 __count__",
+ "connected_plural": "连接数 __count__"
+ },
"errors": {
"connect-error": "ws连接发生了错误: ",
"send-error": "发送时发生了错误: ",
- "missing-conf": "未设置服务器"
+ "missing-conf": "未设置服务器",
+ "duplicate-path": "同一路径上不能有两个WebSocket侦听器: __path__"
}
},
"watch": {
+ "watch": "watch",
"label": {
"files": "文件",
"recursive": "递归所有子文件夹"
@@ -421,7 +487,7 @@
"output": "输出",
"port": "端口",
"host": "主机地址",
- "payload": "的有效载荷",
+ "payload": "有效载荷",
"delimited": "分隔符号",
"close-connection": "是否在成功发送每条信息后断开连接?",
"decode-base64": "用 Base64 解码信息?",
@@ -480,7 +546,6 @@
"output": "输出",
"group": "组",
"interface": "本地IP",
- "interfaceprompt": "(可选)本地 IP 绑定到",
"send": "发送一个",
"toport": "到端口",
"address": "地址",
@@ -488,6 +553,7 @@
},
"placeholder": {
"interface": "(可选)eth0的IP地址",
+ "interfaceprompt": "(可选) 要绑定的本地接口或地址",
"address": "目标IP地址"
},
"udpmsgs": "udp信息",
@@ -529,15 +595,18 @@
"ip-notset": "udp: IP地址未设定",
"port-notset": "udp: 端口未设定",
"port-invalid": "udp: 无效端口号码",
- "alreadyused": "udp: 端口已被占用"
+ "alreadyused": "udp: 端口已被占用",
+ "ifnotfound": "udp: 接口 __iface__ 未发现"
}
},
"switch": {
+ "switch": "switch",
"label": {
"property": "属性",
"rule": "规则",
"repair" : "重建信息队列"
},
+ "previous": "先前值",
"and": "与",
"checkall": "全选所有规则",
"stopfirst": "接受第一条匹配信息后停止",
@@ -550,11 +619,15 @@
"false":"为假",
"null":"为空",
"nnull":"非空",
- "head":"head",
- "tail":"tail",
- "index":"index between",
+ "istype": "类型是",
+ "empty": "为空",
+ "nempty": "非空",
+ "head":"头",
+ "tail":"尾",
+ "index":"索引在..中间",
"exp":"JSONata表达式",
- "else":"除此以外"
+ "else":"除此以外",
+ "hask": "拥有键"
},
"errors": {
"invalid-expr": "无效的JSONata表达式: __error__",
@@ -588,6 +661,7 @@
}
},
"range": {
+ "range": "range",
"label": {
"action": "操作",
"inputrange": "映射输入数据",
@@ -623,7 +697,8 @@
"firstrow": "第一行包含列名",
"output": "输出",
"includerow": "包含列名行",
- "newline": "换行符"
+ "newline": "换行符",
+ "usestrings": "解析数值"
},
"placeholder": {
"columns": "用逗号分割列名"
@@ -654,7 +729,8 @@
"html": {
"label": {
"select": "选取项",
- "output": "输出"
+ "output": "输出",
+ "in": "in"
},
"output": {
"html": "选定元素的html内容",
@@ -670,7 +746,9 @@
"errors": {
"dropped-object": "忽略非对象格式的有效负载",
"dropped": "忽略不支持格式的有效负载类型",
- "dropped-error": "转换有效负载失败"
+ "dropped-error": "转换有效负载失败",
+ "schema-error": "JSON架构错误",
+ "schema-error-compile": "JSON架构错误: 未能编译架构"
},
"label": {
"o2j": "对象至JSON",
@@ -713,7 +791,10 @@
"breaklines": "分拆成行",
"filelabel": "文件",
"sendError": "发生错误时发送消息(传统模式)",
- "deletelabel": "删除 __file__"
+ "deletelabel": "删除 __file__",
+ "encoding": "编码",
+ "utf8String": "UTF8字符串",
+ "binaryBuffer": "二进制buffer"
},
"action": {
"append": "追加至文件",
@@ -731,6 +812,21 @@
"deletedfile": "删除文件: __file__",
"appendedfile": "追加至文件: __file__"
},
+ "encoding": {
+ "none": "默认",
+ "native": "Native",
+ "unicode": "Unicode",
+ "japanese": "日本",
+ "chinese": "中国",
+ "korean": "韩国",
+ "taiwan": "台湾/香港",
+ "windows": "Windows代码页",
+ "iso": "ISO代码页",
+ "ibm": "IBM代码页",
+ "mac": "Mac代码页",
+ "koi8": "KOI8代码页",
+ "misc": "其它"
+ },
"errors": {
"nofilename": "未指定文件名",
"invaliddelete": "警告:无效删除。请在配置对话框中使用特定的删除选项",
@@ -742,6 +838,7 @@
"tip": "提示: 文件名应该是绝对路径,否则它将相对于Node-RED进程的工作目录。"
},
"split": {
+ "split": "split",
"intro":"基于以下类型拆分
msg.payload
:",
"object":"
对象 ",
"objectSend":"每个键值对作为单个消息发送",
@@ -753,6 +850,7 @@
"addname":" 复制键到 "
},
"join":{
+ "join": "join",
"mode":{
"mode":"模式",
"auto":"自动",
@@ -761,6 +859,7 @@
"custom":"手动"
},
"combine":"合并每个",
+ "completeMessage": "完整的消息",
"create":"输出为",
"type":{
"string":"字符串",
@@ -799,6 +898,7 @@
}
},
"sort" : {
+ "sort": "排序",
"target" : "排序属性",
"seq" : "信息队列",
"key" : "键值",
@@ -807,11 +907,12 @@
"ascending" : "升序",
"descending" : "降序",
"as-number" : "作为数值",
- "invalid-exp" : "sort节点中存在无效的JSONata表达式",
- "too-many" : "sort节点中有太多待定信息",
- "clear" : "清空sort节点中的待定信息"
+ "invalid-exp" : "排序节点中存在无效的JSONata表达式",
+ "too-many" : "排序节点中有太多待定信息",
+ "clear" : "清空排序节点中的待定信息"
},
"batch" : {
+ "batch": "batch",
"mode": {
"label" : "模式",
"num-msgs" : "按指定数量分组",
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/network/05-tls.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/05-tls.html
new file mode 100644
index 000000000..5a9603946
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/05-tls.html
@@ -0,0 +1,19 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/network/06-httpproxy.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/06-httpproxy.html
new file mode 100644
index 000000000..e84971973
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/06-httpproxy.html
@@ -0,0 +1,22 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/network/10-mqtt.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/10-mqtt.html
new file mode 100644
index 000000000..520d7f4ef
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/10-mqtt.html
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/network/21-httpin.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/21-httpin.html
new file mode 100644
index 000000000..0fb52efd1
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/21-httpin.html
@@ -0,0 +1,81 @@
+
+
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/network/21-httprequest.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/21-httprequest.html
new file mode 100644
index 000000000..3c7b163e2
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/21-httprequest.html
@@ -0,0 +1,78 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/network/22-websocket.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/22-websocket.html
new file mode 100644
index 000000000..d2ee29dfa
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/22-websocket.html
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/network/31-tcpin.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/31-tcpin.html
new file mode 100644
index 000000000..2f00ed5f5
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/31-tcpin.html
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/network/32-udp.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/32-udp.html
new file mode 100644
index 000000000..1e01aa0a3
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/32-udp.html
@@ -0,0 +1,28 @@
+
+
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-CSV.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-CSV.html
new file mode 100644
index 000000000..5657f4cd3
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-CSV.html
@@ -0,0 +1,43 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-HTML.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-HTML.html
new file mode 100644
index 000000000..73b365600
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-HTML.html
@@ -0,0 +1,33 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-JSON.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-JSON.html
new file mode 100644
index 000000000..2e574a0bf
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-JSON.html
@@ -0,0 +1,43 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-XML.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-XML.html
new file mode 100644
index 000000000..969f410a0
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-XML.html
@@ -0,0 +1,48 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-YAML.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-YAML.html
new file mode 100644
index 000000000..e65d1b87d
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-YAML.html
@@ -0,0 +1,34 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/sequence/17-split.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/sequence/17-split.html
new file mode 100644
index 000000000..22f01832a
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/sequence/17-split.html
@@ -0,0 +1,133 @@
+
+
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/sequence/18-sort.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/sequence/18-sort.html
new file mode 100644
index 000000000..226355a8c
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/sequence/18-sort.html
@@ -0,0 +1,41 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/sequence/19-batch.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/sequence/19-batch.html
new file mode 100644
index 000000000..012f20816
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/sequence/19-batch.html
@@ -0,0 +1,34 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/storage/10-file.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/storage/10-file.html
new file mode 100644
index 000000000..eb38a5235
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/storage/10-file.html
@@ -0,0 +1,55 @@
+
+
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/storage/23-watch.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/storage/23-watch.html
new file mode 100644
index 000000000..eec611429
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/storage/23-watch.html
@@ -0,0 +1,25 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/20-inject.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/20-inject.html
new file mode 100644
index 000000000..046eddd7e
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/20-inject.html
@@ -0,0 +1,35 @@
+
+
+
+
\ No newline at end of file
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/21-debug.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/21-debug.html
new file mode 100644
index 000000000..31f78e907
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/21-debug.html
@@ -0,0 +1,25 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/24-complete.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/24-complete.html
new file mode 100644
index 000000000..862745310
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/24-complete.html
@@ -0,0 +1,24 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/25-catch.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/25-catch.html
new file mode 100644
index 000000000..4e3db015d
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/25-catch.html
@@ -0,0 +1,36 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/25-status.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/25-status.html
new file mode 100644
index 000000000..d961c8e52
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/25-status.html
@@ -0,0 +1,33 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/60-link.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/60-link.html
new file mode 100644
index 000000000..e7723c499
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/60-link.html
@@ -0,0 +1,31 @@
+
+
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/90-comment.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/90-comment.html
new file mode 100644
index 000000000..d044f28db
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/90-comment.html
@@ -0,0 +1,21 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/98-unknown.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/98-unknown.html
new file mode 100644
index 000000000..c3588def1
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/98-unknown.html
@@ -0,0 +1,24 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/10-function.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/10-function.html
new file mode 100644
index 000000000..9f8ddb43f
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/10-function.html
@@ -0,0 +1,51 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/10-switch.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/10-switch.html
new file mode 100644
index 000000000..5a65eff93
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/10-switch.html
@@ -0,0 +1,37 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/15-change.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/15-change.html
new file mode 100644
index 000000000..91a320945
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/15-change.html
@@ -0,0 +1,33 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/16-range.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/16-range.html
new file mode 100644
index 000000000..62eb63d0b
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/16-range.html
@@ -0,0 +1,40 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/80-template.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/80-template.html
new file mode 100644
index 000000000..874ae3801
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/80-template.html
@@ -0,0 +1,46 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/89-delay.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/89-delay.html
new file mode 100644
index 000000000..28c291de8
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/89-delay.html
@@ -0,0 +1,32 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/89-trigger.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/89-trigger.html
new file mode 100644
index 000000000..6bbe72f4d
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/89-trigger.html
@@ -0,0 +1,33 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/90-exec.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/90-exec.html
new file mode 100644
index 000000000..27be34e00
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/90-exec.html
@@ -0,0 +1,74 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json b/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json
new file mode 100644
index 000000000..5368bb993
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json
@@ -0,0 +1,940 @@
+{
+ "common": {
+ "label": {
+ "payload": "內容",
+ "topic": "主題",
+ "name": "名稱",
+ "username": "使用者名稱",
+ "password": "密碼",
+ "property": "屬性",
+ "selectNodes": "選擇節點...",
+ "expand": "擴展"
+ },
+ "status": {
+ "connected": "已連接",
+ "not-connected": "未連接",
+ "disconnected": "已斷開",
+ "connecting": "連接中",
+ "error": "錯誤",
+ "ok": "確定"
+ },
+ "notification": {
+ "error": "
錯誤 : __message__",
+ "errors": {
+ "not-deployed": "節點未部署",
+ "no-response": "伺服器無反應",
+ "unexpected": "發生意外錯誤 (__status__) __message__"
+ }
+ },
+ "errors": {
+ "nooverride": "警告: 資訊的屬性已經不可以改寫節點的屬性. 詳情參考 bit.ly/nr-override-msg-props"
+ }
+ },
+ "inject": {
+ "inject": "注入",
+ "repeat": "重複 = __repeat__",
+ "crontab": "crontab = __crontab__",
+ "stopped": "停止",
+ "failed": "注入失敗: __error__",
+ "label": {
+ "repeat": "重複",
+ "flow": "流上下午",
+ "global": "全局上下文",
+ "str": "字符串",
+ "num": "數值",
+ "bool": "布爾值",
+ "json": "JSON對象",
+ "bin": "buffer",
+ "date": "時間戳",
+ "env": "環境變量",
+ "object": "對象",
+ "string": "字符串",
+ "boolean": "布爾值",
+ "number": "數值",
+ "Array": "數組",
+ "invalid": "無效的JSON對象"
+ },
+ "timestamp": "時間戳記",
+ "none": "無",
+ "interval": "週期性執行",
+ "interval-time": "指定時間段並週期性執行",
+ "time": "指定時間",
+ "seconds": "秒",
+ "minutes": "分鐘",
+ "hours": "小時",
+ "between": "介於",
+ "previous": "之前數值",
+ "at": "在",
+ "and": "至",
+ "every": "每隔",
+ "days": [
+ "星期一",
+ "星期二",
+ "星期三",
+ "星期四",
+ "星期五",
+ "星期六",
+ "星期天"
+ ],
+ "on": "在",
+ "onstart": "立刻執行於",
+ "onceDelay": "秒後, 此後",
+ "tip": "
注意: \"指定時間段並週期性執行\" 和 \"指定時間\" 會使用cron系統.
詳情查看信息頁.",
+ "success": "成功注入: __label__",
+ "errors": {
+ "failed": "注入失敗, 請查看日誌",
+ "toolong": "週期過長"
+ }
+ },
+ "catch": {
+ "catch": "監測所有節點",
+ "catchNodes": "監測__number__個節點",
+ "catchUncaught": "捕獲:未捕獲",
+ "label": {
+ "source": "監測範圍",
+ "selectAll": "全選",
+ "uncaught": "忽略其他捕獲節點處理的錯誤"
+ },
+ "scope": {
+ "all": "所有節點",
+ "selected": "指定節點"
+ }
+ },
+ "status": {
+ "status": "報告所有節點狀態",
+ "statusNodes": "報告__number__個節點狀態",
+ "label": {
+ "source": "報告狀態範圍",
+ "sortByType": "按類型排序"
+ },
+ "scope": {
+ "all": "所有節點",
+ "selected": "指定節點"
+ }
+ },
+ "complete": {
+ "completeNodes": "完成: __number__個節點"
+ },
+ "debug": {
+ "output": "輸出",
+ "none": "None",
+ "invalid-exp": "無效的JSONata表達式: __error__",
+ "msgprop": "資訊屬性",
+ "msgobj": "完整資訊",
+ "to": "目標",
+ "debtab": "除錯窗口",
+ "tabcon": "除錯窗口及Console",
+ "toSidebar": "除錯窗口",
+ "toConsole": "Console",
+ "toStatus": "節點狀態 (32位元字元)",
+ "severity": "級別",
+ "notification": {
+ "activated": "成功啟動: __label__",
+ "deactivated": "成功取消: __label__"
+ },
+ "sidebar": {
+ "label": "除錯窗口",
+ "name": "名稱",
+ "filterAll": "所有節點",
+ "filterSelected": "已選節點",
+ "filterCurrent": "當前流程",
+ "debugNodes": "除錯節點",
+ "clearLog": "清空日誌",
+ "filterLog": "過濾日誌",
+ "openWindow": "在新視窗打開",
+ "copyPath": "復制路徑",
+ "copyPayload": "復制值",
+ "pinPath": "固定展開"
+ },
+ "messageMenu": {
+ "collapseAll": "折疊所有路徑",
+ "clearPinned": "清空已固定路徑",
+ "filterNode": "過濾此節點",
+ "clearFilter": "清空過濾條件"
+ }
+ },
+ "link": {
+ "linkIn": "輸入",
+ "linkOut": "輸出"
+ },
+ "tls": {
+ "tls": "TLS設置",
+ "label": {
+ "use-local-files": "使用本地密鑰及證書檔",
+ "upload": "上傳",
+ "cert": "證書",
+ "key": "私密金鑰",
+ "passphrase": "密碼",
+ "ca": "CA證書",
+ "verify-server-cert": "驗證伺服器憑證",
+ "servername": "服務器名"
+ },
+ "placeholder": {
+ "cert": "憑證路徑 (PEM 格式)",
+ "key": "私密金鑰路徑 (PEM 格式)",
+ "ca": "CA憑證路徑 (PEM 格式)",
+ "passphrase": "私密金鑰密碼 (可選)",
+ "servername": "用於SNI"
+ },
+ "error": {
+ "missing-file": "未提供證書/金鑰檔案"
+ }
+ },
+ "exec": {
+ "exec": "exec",
+ "spawn": "spawn",
+ "label": {
+ "command": "命令",
+ "append": "追加",
+ "timeout": "超時",
+ "timeoutplace": "可選填",
+ "return": "輸出",
+ "seconds": "秒",
+ "stdout": "標準輸出",
+ "stderr": "標準錯誤輸出",
+ "retcode": "返回碼"
+ },
+ "placeholder": {
+ "extraparams": "額外的輸入參數"
+ },
+ "opt": {
+ "exec": "當命令完成時 - exec模式",
+ "spawn": "當命令進行時 - spawn模式"
+ },
+ "oldrc": "使用舊式輸出 (相容模式)"
+ },
+ "function": {
+ "function": "函數",
+ "label": {
+ "function": "函數",
+ "outputs": "輸出"
+ },
+ "error": {
+ "inputListener": "無法在函數中監聽對'注入'事件",
+ "non-message-returned": "函數節點嘗試返回類型為 __type__ 的資訊"
+ }
+ },
+ "template": {
+ "template": "模板",
+ "label": {
+ "template": "模版",
+ "property": "屬性",
+ "format": "語法高亮",
+ "syntax": "格式",
+ "output": "輸出為",
+ "mustache": "Mustache 模版",
+ "plain": "純文字",
+ "json": "JSON",
+ "yaml": "YAML",
+ "none": "無"
+ },
+ "templatevalue": "This is the payload: {{payload}} !"
+ },
+ "delay": {
+ "action": "行為設置",
+ "for": "時長",
+ "delaymsg": "延遲每一條資訊",
+ "delayfixed": "固定延遲時間",
+ "delayvarmsg": "允許msg.delay複寫延遲時長",
+ "randomdelay": "隨機延遲",
+ "limitrate": "限制資訊頻率",
+ "limitall": "所有資訊",
+ "limittopic": "每一個msg.topic",
+ "fairqueue": "依次發送每一個topic",
+ "timedqueue": "發所有topic",
+ "milisecs": "毫秒",
+ "secs": "秒",
+ "sec": "秒",
+ "mins": "分",
+ "min": "分",
+ "hours": "小時",
+ "hour": "小時",
+ "days": "天",
+ "day": "天",
+ "between": "介於",
+ "and": "至",
+ "rate": "速度",
+ "msgper": "信息 每",
+ "dropmsg": "不傳輸中間資訊",
+ "label": {
+ "delay": "延遲",
+ "variable": "變數",
+ "limit": "限制",
+ "limitTopic": "限制主題",
+ "random": "隨機",
+ "units": {
+ "second": {
+ "plural": "秒",
+ "singular": "秒"
+ },
+ "minute": {
+ "plural": "分鐘",
+ "singular": "分鐘"
+ },
+ "hour": {
+ "plural": "小時",
+ "singular": "小時"
+ },
+ "day": {
+ "plural": "天",
+ "singular": "天"
+ }
+ }
+ },
+ "error": {
+ "buffer": "緩衝了超過 1000 條資訊",
+ "buffer1": "緩衝了超過 10000 條資訊"
+ }
+ },
+ "trigger": {
+ "send": "發送",
+ "then": "然後",
+ "then-send": "然後發送",
+ "output": {
+ "string": "字串",
+ "number": "數字",
+ "existing": "現有資訊物件",
+ "original": "原本資訊物件",
+ "latest": "最新資訊物件",
+ "nothing": "無"
+ },
+ "wait-reset": "等待被重置",
+ "wait-for": "等待",
+ "wait-loop": "週期性重發",
+ "for": "處理",
+ "bytopics": "每個msg.topic",
+ "alltopics": "所有消息",
+ "duration": {
+ "ms": "毫秒",
+ "s": "秒",
+ "m": "分鐘",
+ "h": "小時"
+ },
+ "extend": " 如有新資訊,延長延遲",
+ "label": {
+ "trigger": "觸發",
+ "trigger-block": "觸發並阻止",
+ "trigger-loop": "週期性重發",
+ "reset": "重置觸發節點條件 如果:",
+ "resetMessage": "msg.reset已設置",
+ "resetPayload": "msg.payload等於",
+ "resetprompt": "可選填"
+ }
+ },
+ "comment": {
+ "comment": "注釋"
+ },
+ "unknown": {
+ "label": {
+ "unknown": "未知"
+ },
+ "tip": "
此節點是您安裝,但Node-RED所不知道的類型。
如果在此狀態下部署節點,那麼它的配置將被保留,但是流程將不會啟動,直到安裝缺少的節點。
有關更多説明,請參閱資訊側欄
"
+ },
+ "mqtt": {
+ "label": {
+ "broker": "服務端",
+ "example": "e.g. localhost",
+ "output": "輸出",
+ "qos": "QoS",
+ "retain": "保持",
+ "clientid": "使用者端ID",
+ "port": "埠",
+ "keepalive": "Keepalive計時(秒)",
+ "cleansession": "使用新的會話",
+ "use-tls": "使用安全連接 (SSL/TLS)",
+ "tls-config": "TLS 設置",
+ "verify-server-cert": "驗證伺服器憑證",
+ "compatmode": "使用舊式MQTT 3.1支援"
+ },
+ "sections-label": {
+ "birth-message": "連接時發送的消息(出生消息)",
+ "will-message": "意外斷開連接時的發送消息(Will消息)",
+ "close-message": "斷開連接前發送的消息(關閉消息)"
+ },
+ "tabs-label": {
+ "connection": "連接",
+ "security": "安全",
+ "messages": "消息"
+ },
+ "placeholder": {
+ "clientid": "留白則自動隨機生成",
+ "clientid-nonclean": "如非新會話,必須設置使用者端ID",
+ "will-topic": "留白將禁止Will資訊",
+ "birth-topic": "留白將禁止Birth資訊",
+ "close-topic": "留白以禁用關閉消息"
+ },
+ "state": {
+ "connected": "已連接到服務端: __broker__",
+ "disconnected": "已斷開與服務端 __broker__ 的連結",
+ "connect-failed": "與服務端 __broker__ 的連接失敗"
+ },
+ "retain": "保留",
+ "output": {
+ "buffer": "Buffer",
+ "string": "字串",
+ "base64": "Base64編碼字串",
+ "auto": "自動檢測 (字符串或buffer)",
+ "json": "解析的JSON對象"
+ },
+ "true": "是",
+ "false": "否",
+ "tip": "提示: 若希望通過msg屬性對topic(資訊), qos及retain(保留)進行設置, 則將上述項留白",
+ "errors": {
+ "not-defined": "主題未設置",
+ "missing-config": "未設置服務端",
+ "invalid-topic": "主題無效",
+ "nonclean-missingclientid": "使用者端ID未設定,使用新會話",
+ "invalid-json-string": "無效的JSON字符串",
+ "invalid-json-parse": "無法解析JSON字符串"
+ }
+ },
+ "httpin": {
+ "label": {
+ "method": "請求方式",
+ "url": "URL",
+ "doc": "文字檔",
+ "return": "返回",
+ "upload": "接受檔案上傳?",
+ "status": "狀態碼",
+ "headers": "Header",
+ "other": "其他",
+ "paytoqs": "將msg.payload附加為查詢字符串參數",
+ "utf8String": "UTF8格式的字符串",
+ "binaryBuffer": "二進制buffer",
+ "jsonObject": "解析的JSON對象",
+ "authType": "類型",
+ "bearerToken": "Token"
+ },
+ "setby": "- 用 msg.method 設定 -",
+ "basicauth": "基本認證",
+ "use-tls": "使用安全連接 (SSL/TLS) ",
+ "tls-config": "TLS 設置",
+ "basic": "基本認證",
+ "digest": "摘要認證",
+ "bearer": "bearer認證",
+ "use-proxy": "使用代理服務器",
+ "persist": "對連接啟用keep-alive",
+ "proxy-config": "代理服務器設置",
+ "use-proxyauth": "使用代理身份驗證",
+ "noproxy-hosts": "代理例外",
+ "utf8": "UTF-8 字串",
+ "binary": "二進位資料",
+ "json": "JSON對象",
+ "tip": {
+ "in": "相對URL",
+ "res": "發送到此節點的消息
必須 來自
http input 節點",
+ "req": "提示:如果JSON解析失敗,則獲取的字串將按原樣返回."
+ },
+ "httpreq": "http 請求",
+ "errors": {
+ "not-created": "當httpNodeRoot為否時,無法創建http-in節點",
+ "missing-path": "無路徑",
+ "no-response": "無響應物件",
+ "json-error": "JSON 解析錯誤",
+ "no-url": "未設定 URL",
+ "deprecated-call": "__method__方法已棄用",
+ "invalid-transport": "非HTTP傳輸請求",
+ "timeout-isnan": "超時值不是有效數字,忽略",
+ "timeout-isnegative": "超時值為負,忽略",
+ "invalid-payload": "無效的有效載荷"
+ },
+ "status": {
+ "requesting": "請求中"
+ }
+ },
+ "websocket": {
+ "label": {
+ "type": "類型",
+ "path": "路徑",
+ "url": "URL"
+ },
+ "listenon": "監聽",
+ "connectto": "連接",
+ "sendrec": "發送/接受",
+ "payload": "有效載荷",
+ "message": "完整資訊",
+ "tip": {
+ "path1": "預設情況下,
payload
將包含要發送或從Websocket接收的資料。偵聽器可以配置為以JSON格式的字串發送或接收整個消息物件.",
+ "path2": "這條路徑將相對於
__path__
.",
+ "url1": "URL 應該使用ws://或者wss://方案並指向現有的websocket監聽器.",
+ "url2": "預設情況下,
payload
將包含要發送或從Websocket接收的資料。可以將使用者端配置為以JSON格式的字串發送或接收整個消息物件."
+ },
+ "status": {
+ "connected": "連接數 __count__",
+ "connected_plural": "連接數 __count__"
+ },
+ "errors": {
+ "connect-error": "ws連接發生了錯誤: ",
+ "send-error": "發送時發生了錯誤: ",
+ "missing-conf": "未設置伺服器",
+ "duplicate-path": "同一路徑上不能有兩個WebSocket偵聽器: __path__"
+ }
+ },
+ "watch": {
+ "watch": "watch",
+ "label": {
+ "files": "文件",
+ "recursive": "遞迴所有子資料夾"
+ },
+ "placeholder": {
+ "files": "逗號分開文件或資料夾"
+ },
+ "tip": "在Windows上,請務必使用雙斜杠 \\\\ 來隔開資料夾名字"
+ },
+ "tcpin": {
+ "label": {
+ "type": "類型",
+ "output": "輸出",
+ "port": "埠",
+ "host": "主機位址",
+ "payload": "的有效載荷",
+ "delimited": "分隔符號號",
+ "close-connection": "是否在成功發送每條資訊後斷開連接?",
+ "decode-base64": "用 Base64 解碼信息?",
+ "server": "伺服器",
+ "return": "返回",
+ "ms": "毫秒",
+ "chars": "字元"
+ },
+ "type": {
+ "listen": "監聽",
+ "connect": "連接",
+ "reply": "回應 TCP"
+ },
+ "output": {
+ "stream": "字串流",
+ "single": "單一",
+ "buffer": "Buffer",
+ "string": "字串",
+ "base64": "Base64 字串"
+ },
+ "return": {
+ "timeout": "指定時間後",
+ "character": "當收到某個字元為",
+ "number": "指定字元數",
+ "never": "永不 - 保持連接",
+ "immed": "馬上 - 不需要等待回復"
+ },
+ "status": {
+ "connecting": "正在連接到 __host__:__port__",
+ "connected": "已經連接到 __host__:__port__",
+ "listening-port": "監聽埠 __port__",
+ "stopped-listening": "已停止監聽埠",
+ "connection-from": "連接來自 __host__:__port__",
+ "connection-closed": "連接已關閉 __host__:__port__",
+ "connections": "__count__ 個連接",
+ "connections_plural": "__count__ 個連接"
+ },
+ "errors": {
+ "connection-lost": "連接中斷 __host__:__port__",
+ "timeout": "超時關閉通訊端連接,埠 __port__",
+ "cannot-listen": "無法監聽埠 __port__, 錯誤: __error__",
+ "error": "錯誤: __error__",
+ "socket-error": "通訊端連接錯誤來自 __host__:__port__",
+ "no-host": "主機位址或埠未設定",
+ "connect-timeout": "連接逾時",
+ "connect-fail": "連接失敗"
+ }
+ },
+ "udp": {
+ "label": {
+ "listen": "監聽",
+ "onport": "埠",
+ "using": "使用",
+ "output": "輸出",
+ "group": "組",
+ "interface": "本地IP",
+ "send": "發送一個",
+ "toport": "到埠",
+ "address": "地址",
+ "decode-base64": "是否解碼Base64編碼的資訊?",
+ "interfaceprompt": "(可選)本地 IP 綁定到"
+ },
+ "placeholder": {
+ "interface": "(可選)eth0的IP地址",
+ "interfaceprompt": "(可選) 要綁定的本地接口或地址",
+ "address": "目標IP位址"
+ },
+ "udpmsgs": "udp信息",
+ "mcmsgs": "群播信息",
+ "udpmsg": "udp信息",
+ "bcmsg": "廣播資訊",
+ "mcmsg": "群播信息",
+ "output": {
+ "buffer": "Buffer",
+ "string": "字串",
+ "base64": "Base64編碼字串"
+ },
+ "bind": {
+ "random": "綁定到任意本地埠",
+ "local": "綁定到本地埠",
+ "target": "綁定到目標埠"
+ },
+ "tip": {
+ "in": "提示:確保您的防火牆將允許資料進入",
+ "out": "提示:如果要使用
msg.ip
和
msg.port
設置,請將位址和埠留空",
+ "port": "正在使用埠: "
+ },
+ "status": {
+ "listener-at": "udp 監聽器正在監聽 __host__:__port__",
+ "mc-group": "udp 群播到 __group__",
+ "listener-stopped": "udp 監聽器已停止",
+ "output-stopped": "udp 輸出已停止",
+ "mc-ready": "udp 群播已準備好: __outport__ -> __host__:__port__",
+ "bc-ready": "udp 廣播已準備好: __outport__ -> __host__:__port__",
+ "ready": "udp 已準備好: __outport__ -> __host__:__port__",
+ "ready-nolocal": "udp 已準備好: __host__:__port__",
+ "re-use": "udp 重用通訊端: __outport__ -> __host__:__port__"
+ },
+ "errors": {
+ "access-error": "UDP 訪問錯誤, 你可能需要root許可權才能接入1024以下的埠",
+ "error": "錯誤: __error__",
+ "bad-mcaddress": "無效的群播地址",
+ "interface": "必須是指定介面的IP位址",
+ "ip-notset": "udp: IP地址未設定",
+ "port-notset": "udp: 埠未設定",
+ "port-invalid": "udp: 無效埠號碼",
+ "alreadyused": "udp: 埠已被佔用",
+ "ifnotfound": "udp: 接口 __iface__ 未發現"
+ }
+ },
+ "switch": {
+ "switch": "switch",
+ "label": {
+ "property": "屬性",
+ "rule": "規則",
+ "repair": "重建資訊佇列"
+ },
+ "previous": "先前值",
+ "and": "與",
+ "checkall": "全選所有規則",
+ "stopfirst": "接受第一條匹配資訊後停止",
+ "ignorecase": "忽略大小寫",
+ "rules": {
+ "btwn": "在之間",
+ "cont": "包含",
+ "regex": "匹配規則運算式",
+ "true": "為真",
+ "false": "為假",
+ "null": "為空",
+ "nnull": "非空",
+ "istype": "類型是",
+ "empty": "為空",
+ "nempty": "非空",
+ "head": "head",
+ "tail": "tail",
+ "index": "index between",
+ "exp": "JSONata運算式",
+ "else": "除此以外",
+ "hask": "擁有鍵"
+ },
+ "errors": {
+ "invalid-expr": "無效的JSONata運算式: __error__",
+ "too-many": "Switch節點中有太多待定信息"
+ }
+ },
+ "change": {
+ "label": {
+ "rules": "規則",
+ "rule": "規則",
+ "set": "設定 __property__",
+ "change": "修改 __property__",
+ "delete": "刪除 __property__",
+ "move": "移動 __property__",
+ "changeCount": "修改: __count__條規矩",
+ "regex": "使用規則運算式"
+ },
+ "action": {
+ "set": "設定",
+ "change": "修改",
+ "delete": "刪除",
+ "move": "轉移",
+ "to": "到",
+ "search": "搜索",
+ "replace": "替代為"
+ },
+ "errors": {
+ "invalid-from": "無效的'from'屬性: __error__",
+ "invalid-json": "無效的'to'JSON 屬性",
+ "invalid-expr": "無效的JSONata運算式: __error__"
+ }
+ },
+ "range": {
+ "range": "range",
+ "label": {
+ "action": "操作",
+ "inputrange": "映射輸入資料",
+ "resultrange": "至目標範圍",
+ "from": "從",
+ "to": "到",
+ "roundresult": "取最接近整數?"
+ },
+ "placeholder": {
+ "min": "e.g. 0",
+ "maxin": "e.g. 99",
+ "maxout": "e.g. 255"
+ },
+ "scale": {
+ "payload": "按比例msg.payload",
+ "limit": "按比例並設定界限至目標範圍",
+ "wrap": "按比例並包含在目標範圍內"
+ },
+ "tip": "提示: 此節點僅對數字有效",
+ "errors": {
+ "notnumber": "不是一個數字"
+ }
+ },
+ "csv": {
+ "label": {
+ "columns": "列",
+ "separator": "分隔符號",
+ "c2o": "CSV至對象",
+ "o2c": "對象至CSV",
+ "input": "輸入",
+ "skip-s": "忽略前",
+ "skip-e": "行",
+ "firstrow": "第一行包含列名",
+ "output": "輸出",
+ "includerow": "包含列名行",
+ "newline": "分行符號",
+ "usestrings": "解析數值"
+ },
+ "placeholder": {
+ "columns": "用逗號分割列名"
+ },
+ "separator": {
+ "comma": "逗號",
+ "tab": "Tab",
+ "space": "空格",
+ "semicolon": "分號",
+ "colon": "冒號",
+ "hashtag": "井號",
+ "other": "其他..."
+ },
+ "output": {
+ "row": "每行一條信息",
+ "array": "僅一條資訊 [陣列]"
+ },
+ "newline": {
+ "linux": "Linux (\\n)",
+ "mac": "Mac (\\r)",
+ "windows": "Windows (\\r\\n)"
+ },
+ "errors": {
+ "csv_js": "此節點僅處理CSV字串或JS物件",
+ "obj_csv": "對象->CSV轉換未設定列模版"
+ }
+ },
+ "html": {
+ "label": {
+ "select": "選取項",
+ "output": "輸出",
+ "in": "in"
+ },
+ "output": {
+ "html": "選定元素的html內容",
+ "text": "選定元素的純文字內容",
+ "attr": "包含選定元素的所有屬性的物件"
+ },
+ "format": {
+ "single": "一條資訊 [陣列]",
+ "multi": "多條資訊,每條一個元素"
+ }
+ },
+ "json": {
+ "errors": {
+ "dropped-object": "忽略非物件格式的有效負載",
+ "dropped": "忽略不支援格式的有效負載類型",
+ "dropped-error": "轉換有效負載失敗",
+ "schema-error": "JSON架構錯誤",
+ "schema-error-compile": "JSON架構錯誤: 未能編譯架構"
+ },
+ "label": {
+ "o2j": "對象至JSON",
+ "pretty": "格式化JSON字串",
+ "action": "操作",
+ "property": "屬性",
+ "actions": {
+ "toggle": "JSON字串與物件互轉",
+ "str": "總是轉為JSON字串",
+ "obj": "總是轉為JS對象"
+ }
+ }
+ },
+ "yaml": {
+ "errors": {
+ "dropped-object": "忽略非物件格式的有效負載",
+ "dropped": "忽略不支援格式的有效負載類型",
+ "dropped-error": "轉換有效負載失敗"
+ }
+ },
+ "xml": {
+ "label": {
+ "represent": "XML標籤屬性的屬性名稱",
+ "prefix": "標籤文本內容的屬性名稱",
+ "advanced": "高級選項",
+ "x2o": "XML到物件選項"
+ },
+ "errors": {
+ "xml_js": "此節點僅處理XML字串或JS物件."
+ }
+ },
+ "file": {
+ "label": {
+ "filename": "檔案名",
+ "action": "行為",
+ "addnewline": "向每個有效載荷添加分行符號(\\n)?",
+ "createdir": "創建目錄(如果不存在)?",
+ "outputas": "輸出",
+ "breakchunks": "分拆成塊",
+ "breaklines": "分拆成行",
+ "filelabel": "文件",
+ "sendError": "發生錯誤時發送消息(傳統模式)",
+ "deletelabel": "刪除 __file__",
+ "encoding": "編碼",
+ "utf8String": "UTF8字符串",
+ "binaryBuffer": "二進制buffer"
+ },
+ "action": {
+ "append": "追加至文件",
+ "overwrite": "複寫文件",
+ "delete": "刪除檔"
+ },
+ "output": {
+ "utf8": "一個utf8字串",
+ "buffer": "一個Buffer物件",
+ "lines": "每行一條信息",
+ "stream": "一個Buffer流"
+ },
+ "status": {
+ "wrotefile": "寫入至文件: __file__",
+ "deletedfile": "刪除檔: __file__",
+ "appendedfile": "追加至文件: __file__"
+ },
+ "encoding": {
+ "none": "默認",
+ "native": "Native",
+ "unicode": "Unicode",
+ "japanese": "日本",
+ "chinese": "中國",
+ "korean": "韓國",
+ "taiwan": "臺灣/香港",
+ "windows": "Windows代碼頁",
+ "iso": "ISO代碼頁",
+ "ibm": "IBM代碼頁",
+ "mac": "Mac代碼頁",
+ "koi8": "KOI8代碼頁",
+ "misc": "其它"
+ },
+ "errors": {
+ "nofilename": "未指定檔案名",
+ "invaliddelete": "警告:無效刪除。請在配置對話方塊中使用特定的刪除選項",
+ "deletefail": "無法刪除檔: __error__",
+ "writefail": "無法寫入文件: __error__",
+ "appendfail": "無法追加到文件: __error__",
+ "createfail": "檔創建失敗: __error__"
+ },
+ "tip": "提示: 檔案名應該是絕對路徑,否則它將相對於Node-RED進程的工作目錄。"
+ },
+ "split": {
+ "split": "split",
+ "intro": "基於以下類型拆分
msg.payload
:",
+ "object": "
對象 ",
+ "objectSend": "每個鍵值對作為單個消息發送",
+ "strBuff": "
字串 /
Buffer ",
+ "array": "
陣列 ",
+ "splitUsing": "拆分使用",
+ "splitLength": "固定長度",
+ "stream": "作為消息流處理",
+ "addname": " 複製鍵到 "
+ },
+ "join": {
+ "join": "join",
+ "mode": {
+ "mode": "模式",
+ "auto": "自動",
+ "merge": "合併序列",
+ "reduce": "縮減序列",
+ "custom": "手動"
+ },
+ "combine": "合併每個",
+ "completeMessage": "完整的消息",
+ "create": "輸出為",
+ "type": {
+ "string": "字串",
+ "array": "陣列",
+ "buffer": "Buffer",
+ "object": "鍵值對對象",
+ "merged": "合併對象"
+ },
+ "using": "使用此值",
+ "key": "作為鍵",
+ "joinedUsing": "合併符號",
+ "send": "發送資訊:",
+ "afterCount": "達到一定數量的資訊時",
+ "count": "數量",
+ "subsequent": "和每個後續的消息",
+ "afterTimeout": "第一條消息的若幹時間後",
+ "seconds": "秒",
+ "complete": "在收到存在
msg.complete
的消息後",
+ "tip": "此模式假定此節點與
split 相連, 或者接收到的消息有正確配置的
msg.parts
屬性.",
+ "too-many": "join節點中有太多待定信息",
+ "merge": {
+ "topics-label": "合併主題",
+ "topics": "主題",
+ "topic": "主題",
+ "on-change": "當收到一個新主題時發送已合併資訊"
+ },
+ "reduce": {
+ "exp": "Reduce運算式",
+ "exp-value": "exp",
+ "init": "初始值",
+ "right": "反向求值(從後往前)",
+ "fixup": "Fix-up exp"
+ },
+ "errors": {
+ "invalid-expr": "無效的JSONata運算式: __error__"
+ }
+ },
+ "sort": {
+ "sort": "排序",
+ "target": "排序屬性",
+ "seq": "資訊佇列",
+ "key": "鍵值",
+ "elem": "元素值",
+ "order": "順序",
+ "ascending": "昇冪",
+ "descending": "降冪",
+ "as-number": "作為數值",
+ "invalid-exp": "排序節點中存在無效的JSONata運算式",
+ "too-many": "排序節點中有太多待定信息",
+ "clear": "清空排序節點中的待定資訊"
+ },
+ "batch": {
+ "batch": "batch",
+ "mode": {
+ "label": "模式",
+ "num-msgs": "按指定數量分組",
+ "interval": "按時間間隔分組",
+ "concat": "按主題分組"
+ },
+ "count": {
+ "label": "分組數量",
+ "overlap": "隊末隊首重疊數量",
+ "count": "數量",
+ "invalid": "無效的分組數量或重疊數量"
+ },
+ "interval": {
+ "label": "時間間隔",
+ "seconds": "秒",
+ "empty": "無數據到達時發送空資訊"
+ },
+ "concat": {
+ "topics-label": "主題",
+ "topic": "主題"
+ },
+ "too-many": "batch節點中有太多待定信息",
+ "unexpected": "未知模式",
+ "no-parts": "資訊中沒有parts屬性"
+ }
+}
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/05-tls.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/05-tls.html
new file mode 100644
index 000000000..ee4b3bd95
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/05-tls.html
@@ -0,0 +1,19 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/06-httpproxy.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/06-httpproxy.html
new file mode 100644
index 000000000..23f899627
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/06-httpproxy.html
@@ -0,0 +1,22 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/10-mqtt.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/10-mqtt.html
new file mode 100644
index 000000000..825bf218e
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/10-mqtt.html
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/21-httpin.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/21-httpin.html
new file mode 100644
index 000000000..0d44ce3b1
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/21-httpin.html
@@ -0,0 +1,81 @@
+
+
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/21-httprequest.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/21-httprequest.html
new file mode 100644
index 000000000..71ed96087
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/21-httprequest.html
@@ -0,0 +1,78 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/22-websocket.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/22-websocket.html
new file mode 100644
index 000000000..4bb2c7f4d
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/22-websocket.html
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/31-tcpin.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/31-tcpin.html
new file mode 100644
index 000000000..2898ca718
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/31-tcpin.html
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/32-udp.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/32-udp.html
new file mode 100644
index 000000000..401af48e3
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/32-udp.html
@@ -0,0 +1,28 @@
+
+
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-CSV.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-CSV.html
new file mode 100644
index 000000000..9a8638614
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-CSV.html
@@ -0,0 +1,43 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-HTML.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-HTML.html
new file mode 100644
index 000000000..b1559455f
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-HTML.html
@@ -0,0 +1,33 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-JSON.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-JSON.html
new file mode 100644
index 000000000..1a46c3690
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-JSON.html
@@ -0,0 +1,43 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-XML.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-XML.html
new file mode 100644
index 000000000..e4e433fdb
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-XML.html
@@ -0,0 +1,48 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-YAML.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-YAML.html
new file mode 100644
index 000000000..8ea4f87f8
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-YAML.html
@@ -0,0 +1,34 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/17-split.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/17-split.html
new file mode 100644
index 000000000..fd97b497a
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/17-split.html
@@ -0,0 +1,133 @@
+
+
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/18-sort.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/18-sort.html
new file mode 100644
index 000000000..cb8e7fa21
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/18-sort.html
@@ -0,0 +1,41 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/19-batch.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/19-batch.html
new file mode 100644
index 000000000..79879076e
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/19-batch.html
@@ -0,0 +1,34 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/storage/10-file.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/storage/10-file.html
new file mode 100644
index 000000000..ce2531137
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/storage/10-file.html
@@ -0,0 +1,55 @@
+
+
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/storage/23-watch.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/storage/23-watch.html
new file mode 100644
index 000000000..d8e1b5807
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/storage/23-watch.html
@@ -0,0 +1,25 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json
index d2b3060ee..96e829eaf 100644
--- a/packages/node_modules/@node-red/nodes/package.json
+++ b/packages/node_modules/@node-red/nodes/package.json
@@ -1,6 +1,6 @@
{
"name": "@node-red/nodes",
- "version": "1.0.3",
+ "version": "1.1.0",
"license": "Apache-2.0",
"repository": {
"type": "git",
@@ -15,11 +15,11 @@
}
],
"dependencies": {
- "ajv": "6.10.2",
+ "ajv": "6.12.0",
"body-parser": "1.19.0",
"cheerio": "0.22.0",
"content-type": "1.0.4",
- "cookie-parser": "1.4.4",
+ "cookie-parser": "1.4.5",
"cookie": "0.4.0",
"cors": "2.8.5",
"cron": "1.7.2",
@@ -27,18 +27,18 @@
"fs-extra": "8.1.0",
"fs.notify": "0.0.4",
"hash-sum": "2.0.0",
- "https-proxy-agent": "2.2.4",
+ "https-proxy-agent": "5.0.0",
"is-utf8": "0.2.1",
"js-yaml": "3.13.1",
"media-typer": "1.1.0",
"mqtt": "2.18.8",
"multer": "1.4.2",
- "mustache": "3.0.2",
+ "mustache": "4.0.1",
"on-headers": "1.0.2",
"raw-body": "2.4.1",
"request": "2.88.0",
"ws": "6.2.1",
- "xml2js": "0.4.22",
- "iconv-lite": "0.5.0"
+ "xml2js": "0.4.23",
+ "iconv-lite": "0.5.1"
}
}
diff --git a/packages/node_modules/@node-red/registry/lib/installer.js b/packages/node_modules/@node-red/registry/lib/installer.js
index 3562dfb47..dac100613 100644
--- a/packages/node_modules/@node-red/registry/lib/installer.js
+++ b/packages/node_modules/@node-red/registry/lib/installer.js
@@ -32,6 +32,7 @@ var paletteEditorEnabled = false;
var settings;
var moduleRe = /^(@[^/]+?[/])?[^/]+?$/;
var slashRe = process.platform === "win32" ? /\\|[/]/ : /[/]/;
+var pkgurlRe = /^(https?|git(|\+https?|\+ssh|\+file)):\/\//;
function init(runtime) {
events = runtime.events;
@@ -76,14 +77,17 @@ function checkExistingModule(module,version) {
}
return false;
}
-function installModule(module,version) {
+function installModule(module,version,url) {
activePromise = activePromise.then(() => {
//TODO: ensure module is 'safe'
return new Promise((resolve,reject) => {
var installName = module;
var isUpgrade = false;
try {
- if (moduleRe.test(module)) {
+ if (url && pkgurlRe.test(url)) {
+ // Git remote url or Tarball url - check the valid package url
+ installName = url;
+ } else if (moduleRe.test(module)) {
// Simple module name - assume it can be npm installed
if (version) {
installName += "@"+version;
@@ -104,7 +108,7 @@ function installModule(module,version) {
}
var installDir = settings.userDir || process.env.NODE_RED_HOME || ".";
- var args = ['install','--no-audit','--no-update-notifier','--save','--save-prefix="~"','--production',installName];
+ var args = ['install','--no-audit','--no-update-notifier','--no-fund','--save','--save-prefix="~"','--production',installName];
log.trace(npmCommand + JSON.stringify(args));
exec.run(npmCommand,args,{
cwd: installDir
@@ -197,7 +201,7 @@ function uninstallModule(module) {
var list = registry.removeModule(module);
log.info(log._("server.install.uninstalling",{name:module}));
- var args = ['remove','--no-audit','--no-update-notifier','--save',module];
+ var args = ['remove','--no-audit','--no-update-notifier','--no-fund','--save',module];
log.trace(npmCommand + JSON.stringify(args));
exec.run(npmCommand,args,{
diff --git a/packages/node_modules/@node-red/registry/lib/localfilesystem.js b/packages/node_modules/@node-red/registry/lib/localfilesystem.js
index 947972031..c66d7d080 100644
--- a/packages/node_modules/@node-red/registry/lib/localfilesystem.js
+++ b/packages/node_modules/@node-red/registry/lib/localfilesystem.js
@@ -286,6 +286,10 @@ function getNodeFiles(disableNodePathScan) {
nodeFiles.forEach(function(node) {
nodeList["node-red"].nodes[node.name] = node;
});
+ if (settings.coreNodesDir) {
+ var examplesDir = path.join(settings.coreNodesDir,"examples");
+ nodeList["node-red"].examples = {path: examplesDir};
+ }
if (!disableNodePathScan) {
var moduleFiles = scanTreeForNodesModules();
diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json
index fefb24360..1ac6ce528 100644
--- a/packages/node_modules/@node-red/registry/package.json
+++ b/packages/node_modules/@node-red/registry/package.json
@@ -1,6 +1,6 @@
{
"name": "@node-red/registry",
- "version": "1.0.3",
+ "version": "1.1.0",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,9 +16,9 @@
}
],
"dependencies": {
- "@node-red/util": "1.0.3",
+ "@node-red/util": "1.1.0",
"semver": "6.3.0",
- "uglify-js": "3.6.9",
+ "uglify-js": "3.8.1",
"when": "3.7.8"
}
}
diff --git a/packages/node_modules/@node-red/runtime/lib/api/flows.js b/packages/node_modules/@node-red/runtime/lib/api/flows.js
index 1e6e72dc7..277099ecc 100644
--- a/packages/node_modules/@node-red/runtime/lib/api/flows.js
+++ b/packages/node_modules/@node-red/runtime/lib/api/flows.js
@@ -57,6 +57,8 @@ var api = module.exports = {
* Sets the current flow configuration
* @param {Object} opts
* @param {User} opts.user - the user calling the api
+ * @param {Object} opts.flows - the flow configuration: `{flows: [..], credentials: {}}`
+ * @param {Object} opts.deploymentType - the type of deployment - "full", "nodes", "flows", "reload"
* @param {Object} opts.req - the request to log (optional)
* @return {Promise
} - the active flow configuration
* @memberof @node-red/runtime_flows
@@ -83,7 +85,7 @@ var api = module.exports = {
return reject(err);
}
}
- apiPromise = runtime.nodes.setFlows(flows.flows,deploymentType);
+ apiPromise = runtime.nodes.setFlows(flows.flows,flows.credentials,deploymentType);
}
apiPromise.then(function(flowId) {
return resolve({rev:flowId});
@@ -238,17 +240,25 @@ var api = module.exports = {
if (!credentials) {
return resolve({});
}
- var definition = runtime.nodes.getCredentialDefinition(opts.type) || {};
-
var sendCredentials = {};
- for (var cred in definition) {
- if (definition.hasOwnProperty(cred)) {
- if (definition[cred].type == "password") {
- var key = 'has_' + cred;
- sendCredentials[key] = credentials[cred] != null && credentials[cred] !== '';
- continue;
+ var cred;
+ if (/^subflow(:|$)/.test(opts.type)) {
+ for (cred in credentials) {
+ if (credentials.hasOwnProperty(cred)) {
+ sendCredentials['has_'+cred] = credentials[cred] != null && credentials[cred] !== '';
+ }
+ }
+ } else {
+ var definition = runtime.nodes.getCredentialDefinition(opts.type) || {};
+ for (cred in definition) {
+ if (definition.hasOwnProperty(cred)) {
+ if (definition[cred].type == "password") {
+ var key = 'has_' + cred;
+ sendCredentials[key] = credentials[cred] != null && credentials[cred] !== '';
+ continue;
+ }
+ sendCredentials[cred] = credentials[cred] || '';
}
- sendCredentials[cred] = credentials[cred] || '';
}
}
resolve(sendCredentials);
diff --git a/packages/node_modules/@node-red/runtime/lib/api/nodes.js b/packages/node_modules/@node-red/runtime/lib/api/nodes.js
index ee4d3bc1a..fb16cc631 100644
--- a/packages/node_modules/@node-red/runtime/lib/api/nodes.js
+++ b/packages/node_modules/@node-red/runtime/lib/api/nodes.js
@@ -159,6 +159,7 @@ var api = module.exports = {
* @param {User} opts.user - the user calling the api
* @param {String} opts.module - the id of the module to install
* @param {String} opts.version - (optional) the version of the module to install
+ * @param {String} opts.url - (optional) url to install
* @param {Object} opts.req - the request to log (optional)
* @return {Promise} - the node module info
* @memberof @node-red/runtime_nodes
@@ -183,20 +184,20 @@ var api = module.exports = {
return reject(err);
}
}
- runtime.nodes.installModule(opts.module,opts.version).then(function(info) {
- runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version}, opts.req);
+ runtime.nodes.installModule(opts.module,opts.version,opts.url).then(function(info) {
+ runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url}, opts.req);
return resolve(info);
}).catch(function(err) {
if (err.code === 404) {
- runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,error:"not_found"}, opts.req);
+ runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url,error:"not_found"}, opts.req);
// TODO: code/status
err.status = 404;
} else if (err.code) {
err.status = 400;
- runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,error:err.code}, opts.req);
+ runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url,error:err.code}, opts.req);
} else {
err.status = 400;
- runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,error:err.code||"unexpected_error",message:err.toString()}, opts.req);
+ runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url,error:err.code||"unexpected_error",message:err.toString()}, opts.req);
}
return reject(err);
})
diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/Node.js b/packages/node_modules/@node-red/runtime/lib/nodes/Node.js
index c62895f7f..a834c8c34 100644
--- a/packages/node_modules/@node-red/runtime/lib/nodes/Node.js
+++ b/packages/node_modules/@node-red/runtime/lib/nodes/Node.js
@@ -193,6 +193,7 @@ Node.prototype.emit = function(event, ...args) {
*/
Node.prototype._emitInput = function(arg) {
var node = this;
+ this.metric("receive", arg);
if (node._inputCallback) {
// Just one callback registered.
try {
@@ -448,7 +449,6 @@ Node.prototype.receive = function(msg) {
if (!msg._msgid) {
msg._msgid = redUtil.generateId();
}
- this.metric("receive",msg);
this.emit("input",msg);
};
diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/context/index.js b/packages/node_modules/@node-red/runtime/lib/nodes/context/index.js
index 322fa2868..c395c0615 100644
--- a/packages/node_modules/@node-red/runtime/lib/nodes/context/index.js
+++ b/packages/node_modules/@node-red/runtime/lib/nodes/context/index.js
@@ -420,15 +420,39 @@ function createRootContext() {
Object.defineProperties(obj, {
get: {
value: function(key, storage, callback) {
+ if (!callback && typeof storage === 'function') {
+ callback = storage;
+ storage = undefined;
+ }
+ if (callback) {
+ callback()
+ return;
+ }
return undefined;
}
},
set: {
value: function(key, value, storage, callback) {
+ if (!callback && typeof storage === 'function') {
+ callback = storage;
+ storage = undefined;
+ }
+ if (callback) {
+ callback()
+ return
+ }
}
},
keys: {
value: function(storage, callback) {
+ if (!callback && typeof storage === 'function') {
+ callback = storage;
+ storage = undefined;
+ }
+ if (callback) {
+ callback();
+ return;
+ }
return undefined;
}
}
@@ -436,25 +460,48 @@ function createRootContext() {
return obj;
}
-function getContext(localId,flowId,parent) {
- var contextId = localId;
+/**
+ * Get a flow-level context object.
+ * @param {string} flowId [description]
+ * @param {string} parentFlowId the id of the parent flow. undefined
+ * @return {object}} the context object
+ */
+function getFlowContext(flowId,parentFlowId) {
+ if (contexts.hasOwnProperty(flowId)) {
+ return contexts[flowId];
+ }
+ var parentContext = contexts[parentFlowId];
+ if (!parentContext) {
+ parentContext = createRootContext();
+ contexts[parentFlowId] = parentContext;
+ // throw new Error("Flow "+flowId+" is missing parent context "+parentFlowId);
+ }
+ var newContext = createContext(flowId,undefined,parentContext);
+ contexts[flowId] = newContext;
+ return newContext;
+
+}
+
+function getContext(nodeId, flowId) {
+ var contextId = nodeId;
if (flowId) {
- contextId = localId+":"+flowId;
+ contextId = nodeId+":"+flowId;
}
if (contexts.hasOwnProperty(contextId)) {
return contexts[contextId];
}
- var newContext = createContext(contextId,undefined,parent);
+ var newContext = createContext(contextId);
+
if (flowId) {
- var node = flows.get(flowId);
- var parent = undefined;
- if (node && node.type.startsWith("subflow:")) {
- parent = node.context().flow;
+ var flowContext = contexts[flowId];
+ if (!flowContext) {
+ // This is most likely due to a unit test for a node which doesn't
+ // initialise the flow properly.
+ // To keep things working, initialise the missing context.
+ // This *does not happen* in normal node-red operation
+ flowContext = createContext(flowId,undefined,createRootContext());
+ contexts[flowId] = flowContext;
}
- else {
- parent = createRootContext();
- }
- var flowContext = getContext(flowId,undefined,parent);
Object.defineProperty(newContext, 'flow', {
value: flowContext
});
@@ -466,6 +513,39 @@ function getContext(localId,flowId,parent) {
return newContext;
}
+//
+// function getContext(localId,flowId,parent) {
+// var contextId = localId;
+// if (flowId) {
+// contextId = localId+":"+flowId;
+// }
+// console.log("getContext",localId,flowId,"known?",contexts.hasOwnProperty(contextId));
+// if (contexts.hasOwnProperty(contextId)) {
+// return contexts[contextId];
+// }
+// var newContext = createContext(contextId,undefined,parent);
+// if (flowId) {
+// var node = flows.get(flowId);
+// console.log("flows,get",flowId,node&&node.type)
+// var parent = undefined;
+// if (node && node.type.startsWith("subflow:")) {
+// parent = node.context().flow;
+// }
+// else {
+// parent = createRootContext();
+// }
+// var flowContext = getContext(flowId,undefined,parent);
+// Object.defineProperty(newContext, 'flow', {
+// value: flowContext
+// });
+// }
+// Object.defineProperty(newContext, 'global', {
+// value: contexts['global']
+// })
+// contexts[contextId] = newContext;
+// return newContext;
+// }
+
function deleteContext(id,flowId) {
if(!hasConfiguredStore){
// only delete context if there's no configured storage.
@@ -517,6 +597,7 @@ module.exports = {
load: load,
listStores: listStores,
get: getContext,
+ getFlowContext:getFlowContext,
delete: deleteContext,
clean: clean,
close: close
diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/context/localfilesystem.js b/packages/node_modules/@node-red/runtime/lib/nodes/context/localfilesystem.js
index 0f83573e8..9755681d8 100644
--- a/packages/node_modules/@node-red/runtime/lib/nodes/context/localfilesystem.js
+++ b/packages/node_modules/@node-red/runtime/lib/nodes/context/localfilesystem.js
@@ -203,10 +203,10 @@ LocalFileSystem.prototype.open = function(){
var newContext = self.cache._export();
scopes.forEach(function(scope) {
var storagePath = getStoragePath(self.storageBaseDir,scope);
- var context = newContext[scope];
+ var context = newContext[scope] || {};
var stringifiedContext = stringify(context);
if (stringifiedContext.circular && !self.knownCircularRefs[scope]) {
- log.warn(log._("error-circular",{scope:scope}));
+ log.warn(log._("context.localfilesystem.error-circular",{scope:scope}));
self.knownCircularRefs[scope] = true;
} else {
delete self.knownCircularRefs[scope];
@@ -245,7 +245,7 @@ LocalFileSystem.prototype.get = function(scope, key, callback) {
return this.cache.get(scope,key,callback);
}
if(typeof callback !== "function"){
- throw new Error("Callback must be a function");
+ throw new Error("File Store cache disabled - only asynchronous access supported");
}
var storagePath = getStoragePath(this.storageBaseDir ,scope);
loadFile(storagePath + ".json").then(function(data){
@@ -304,7 +304,7 @@ LocalFileSystem.prototype.set = function(scope, key, value, callback) {
}, this.flushInterval);
}
} else if (callback && typeof callback !== 'function') {
- throw new Error("Callback must be a function");
+ throw new Error("File Store cache disabled - only asynchronous access supported");
} else {
self.writePromise = self.writePromise.then(function() { return loadFile(storagePath + ".json") }).then(function(data){
var obj = data ? JSON.parse(data) : {}
@@ -324,7 +324,7 @@ LocalFileSystem.prototype.set = function(scope, key, value, callback) {
}
var stringifiedContext = stringify(obj);
if (stringifiedContext.circular && !self.knownCircularRefs[scope]) {
- log.warn(log._("error-circular",{scope:scope}));
+ log.warn(log._("context.localfilesystem.error-circular",{scope:scope}));
self.knownCircularRefs[scope] = true;
} else {
delete self.knownCircularRefs[scope];
diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js b/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js
index 355bd9bc5..153ef4b06 100644
--- a/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js
+++ b/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js
@@ -341,33 +341,62 @@ var api = module.exports = {
extract: function(node) {
var nodeID = node.id;
var nodeType = node.type;
+ var cred;
var newCreds = node.credentials;
if (newCreds) {
delete node.credentials;
var savedCredentials = credentialCache[nodeID] || {};
- var dashedType = nodeType.replace(/\s+/g, '-');
- var definition = credentialsDef[dashedType];
- if (!definition) {
- log.warn(log._("nodes.credentials.not-registered",{type:nodeType}));
- return;
- }
+ if (/^subflow(:|$)/.test(nodeType)) {
+ for (cred in newCreds) {
+ if (newCreds.hasOwnProperty(cred)) {
+ if (newCreds[cred] === "__PWRD__") {
+ continue;
+ }
+ if (0 === newCreds[cred].length || /^\s*$/.test(newCreds[cred])) {
+ delete savedCredentials[cred];
+ dirty = true;
+ continue;
+ }
+ if (!savedCredentials.hasOwnProperty(cred) || JSON.stringify(savedCredentials[cred]) !== JSON.stringify(newCreds[cred])) {
+ savedCredentials[cred] = newCreds[cred];
+ dirty = true;
+ }
- for (var cred in definition) {
- if (definition.hasOwnProperty(cred)) {
- if (newCreds[cred] === undefined) {
- continue;
}
- if (definition[cred].type == "password" && newCreds[cred] == '__PWRD__') {
- continue;
+ }
+ for (cred in savedCredentials) {
+ if (savedCredentials.hasOwnProperty(cred)) {
+ if (!newCreds.hasOwnProperty(cred)) {
+ delete savedCredentials[cred];
+ dirty = true;
+ }
}
- if (0 === newCreds[cred].length || /^\s*$/.test(newCreds[cred])) {
- delete savedCredentials[cred];
- dirty = true;
- continue;
- }
- if (!savedCredentials.hasOwnProperty(cred) || JSON.stringify(savedCredentials[cred]) !== JSON.stringify(newCreds[cred])) {
- savedCredentials[cred] = newCreds[cred];
- dirty = true;
+ }
+ } else {
+ var dashedType = nodeType.replace(/\s+/g, '-');
+ var definition = credentialsDef[dashedType];
+ if (!definition) {
+ log.warn(log._("nodes.credentials.not-registered",{type:nodeType}));
+ return;
+ }
+
+ for (cred in definition) {
+ if (definition.hasOwnProperty(cred)) {
+ if (newCreds[cred] === undefined) {
+ continue;
+ }
+ if (definition[cred].type == "password" && newCreds[cred] == '__PWRD__') {
+ continue;
+ }
+ if (0 === newCreds[cred].length || /^\s*$/.test(newCreds[cred])) {
+ delete savedCredentials[cred];
+ dirty = true;
+ continue;
+ }
+ if (!savedCredentials.hasOwnProperty(cred) || JSON.stringify(savedCredentials[cred]) !== JSON.stringify(newCreds[cred])) {
+ savedCredentials[cred] = newCreds[cred];
+ dirty = true;
+ }
}
}
}
diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js
index db15e8eb5..df4e810fa 100644
--- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js
+++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js
@@ -18,6 +18,7 @@ var clone = require("clone");
var redUtil = require("@node-red/util").util;
var flowUtil = require("./util");
var events = require("../../events");
+const context = require('../context');
var Subflow;
var Log;
@@ -54,6 +55,8 @@ class Flow {
this.catchNodes = [];
this.statusNodes = [];
this.path = this.id;
+ // Ensure a context exists for this flow
+ this.context = context.getFlowContext(this.id,this.parent.id);
}
/**
@@ -568,15 +571,18 @@ function stopNode(node,removed) {
Log.trace("Stopping node "+node.type+":"+node.id+(removed?" removed":""));
const start = Date.now();
const closePromise = node.close(removed);
+ let closeTimer = null;
const closeTimeout = new Promise((resolve,reject) => {
- setTimeout(() => {
+ closeTimer = setTimeout(() => {
reject("Close timed out");
}, nodeCloseTimeout);
});
return Promise.race([closePromise,closeTimeout]).then(() => {
+ clearTimeout(closeTimer);
var delta = Date.now() - start;
Log.trace("Stopped node "+node.type+":"+node.id+" ("+delta+"ms)" );
}).catch(err => {
+ clearTimeout(closeTimer);
node.error(Log._("nodes.flows.stopping-error",{message:err}));
Log.debug(err.stack);
})
diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js
index a7bec1234..cca230d79 100644
--- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js
+++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js
@@ -16,12 +16,15 @@
const clone = require("clone");
const Flow = require('./Flow').Flow;
-
+const context = require('../context');
const util = require("util");
const redUtil = require("@node-red/util").util;
const flowUtil = require("./util");
+
+const credentials = require("../credentials");
+
var Log;
/**
@@ -38,41 +41,12 @@ function evaluateInputValue(value, type, node) {
if (type === "bool") {
return (value === "true") || (value === true);
}
+ if (type === "cred") {
+ return value;
+ }
return redUtil.evaluateNodeProperty(value, type, node, null, null);
}
-/**
- * Compose information object for env var
- */
-function composeInfo(info, val) {
- var result = {
- name: info.name,
- type: info.type,
- value: val,
- };
- if (info.ui) {
- var ui = info.ui;
- result.ui = {
- hasUI: ui.hasUI,
- icon: ui.icon,
- labels: ui.labels,
- type: ui.type
- };
- var retUI = result.ui;
- if (ui.type === "input") {
- retUI.inputTypes = ui.inputTypes;
- }
- if (ui.type === "select") {
- retUI.menu = ui.menu;
- }
- if (ui.type === "spinner") {
- retUI.spinner = ui.spinner;
- }
- }
- return result;
-}
-
-
/**
* This class represents a subflow - which is handled as a special type of Flow
*/
@@ -142,10 +116,16 @@ class Subflow extends Flow {
this.node_map = node_map;
this.path = parent.path+"/"+(subflowInstance._alias||subflowInstance.id);
+ this.templateCredentials = credentials.get(subflowDef.id);
+ this.instanceCredentials = credentials.get(this.id);
+
var env = [];
if (this.subflowDef.env) {
this.subflowDef.env.forEach(e => {
env[e.name] = e;
+ if (e.type === "cred") {
+ e.value = this.templateCredentials[e.name];
+ }
});
}
if (this.subflowInstance.env) {
@@ -154,7 +134,14 @@ class Subflow extends Flow {
var ui = old ? old.ui : null;
env[e.name] = e;
if (ui) {
- env[e.name].ui = ui;
+ env[e.name].ui = ui;
+ }
+ if (e.type === "cred") {
+ if (!old || this.instanceCredentials.hasOwnProperty(e.name) ) {
+ e.value = this.instanceCredentials[e.name];
+ } else if (old) {
+ e.value = this.templateCredentials[e.name];
+ }
}
});
}
@@ -227,6 +214,10 @@ class Subflow extends Flow {
this.node.on("input", function(msg) { this.send(msg);});
this.node.on("close", function() { this.status({}); })
this.node.status = status => this.parent.handleStatus(this.node,status);
+ // Create a context instance
+ // console.log("Node.context",this.type,"id:",this._alias||this.id,"z:",this.z)
+ this._context = context.get(this._alias||this.id,this.z);
+
this.node._updateWires = this.node.updateWires;
@@ -324,19 +315,10 @@ class Subflow extends Flow {
* @return {Object} val value of env var
*/
getSetting(name) {
- this.trace("getSetting:"+name);
if (!/^\$parent\./.test(name)) {
var env = this.env;
- var is_info = name.endsWith("_info");
- var is_type = name.endsWith("_type");
- var ename = (is_info || is_type) ? name.substring(0, name.length -5) : name; // 5 = length of "_info"/"_type"
-
- if (env && env.hasOwnProperty(ename)) {
- var val = env[ename];
-
- if (is_type) {
- return val ? val.type : undefined;
- }
+ if (env && env.hasOwnProperty(name)) {
+ var val = env[name];
// If this is an env type property we need to be careful not
// to get into lookup loops.
// 1. if the value to lookup is the same as this one, go straight to parent
@@ -350,11 +332,7 @@ class Subflow extends Flow {
value = value.replace(new RegExp("\\${"+name+"}","g"),"${$parent."+name+"}");
}
try {
- var ret = evaluateInputValue(value, type, this.node);
- if (is_info) {
- return composeInfo(val, ret);
- }
- return ret;
+ return evaluateInputValue(value, type, this.node);
}
catch (e) {
this.error(e);
@@ -466,7 +444,7 @@ function createNodeInSubflow(subflowInstanceId, def) {
* properties in the nodes object to reference the new node ids.
* This handles:
* - node.wires,
- * - node.scope of Catch and Status nodes,
+ * - node.scope of Complete, Catch and Status nodes,
* - node.XYZ for any property where XYZ is recognised as an old property
* @param {[type]} nodes [description]
* @param {[type]} nodeMap [description]
@@ -489,7 +467,7 @@ function remapSubflowNodes(nodes,nodeMap) {
}
}
}
- if ((node.type === 'catch' || node.type === 'status') && node.scope) {
+ if ((node.type === 'complete' || node.type === 'catch' || node.type === 'status') && node.scope) {
node.scope = node.scope.map(function(id) {
return nodeMap[id]?nodeMap[id].id:""
})
diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/index.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/index.js
index 981ed7173..1a350ce7c 100644
--- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/index.js
+++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/index.js
@@ -106,15 +106,20 @@ function load(forceStart) {
// This is a force reload from the API - disable safeMode
delete settings.safeMode;
}
- return setFlows(null,"load",false,forceStart);
+ return setFlows(null,null,"load",false,forceStart);
}
/*
* _config - new node array configuration
+ * _credentials - new credentials configuration (optional)
* type - full/nodes/flows/load (default full)
* muteLog - don't emit the standard log messages (used for individual flow api)
*/
-function setFlows(_config,type,muteLog,forceStart) {
+function setFlows(_config,_credentials,type,muteLog,forceStart) {
+ if (typeof _credentials === "string") {
+ type = _credentials;
+ _credentials = null;
+ }
type = type||"full";
if (settings.safeMode) {
if (type !== "load") {
@@ -155,16 +160,27 @@ function setFlows(_config,type,muteLog,forceStart) {
delete newFlowConfig.allNodes[id].credentials;
}
}
+ var credsDirty;
- // Allow the credential store to remove anything no longer needed
- credentials.clean(config);
+ if (_credentials) {
+ // A full set of credentials have been provided. Use those instead
+ configSavePromise = credentials.load(_credentials);
+ credsDirty = true;
+ } else {
+ // Allow the credential store to remove anything no longer needed
+ credentials.clean(config);
- // Remember whether credentials need saving or not
- var credsDirty = credentials.dirty();
+ // Remember whether credentials need saving or not
+ var credsDirty = credentials.dirty();
+
+ configSavePromise = Promise.resolve();
+ }
// Get the latest credentials and ask storage to save them (if needed)
// as well as the new flow configuration.
- configSavePromise = credentials.export().then(function(creds) {
+ configSavePromise = configSavePromise.then(function() {
+ return credentials.export()
+ }).then(function(creds) {
var saveConfig = {
flows: config,
credentialsDirty:credsDirty,
@@ -515,7 +531,7 @@ function addFlow(flow) {
var newConfig = clone(activeConfig.flows);
newConfig = newConfig.concat(nodes);
- return setFlows(newConfig,'flows',true).then(function() {
+ return setFlows(newConfig,null,'flows',true).then(function() {
log.info(log._("nodes.flows.added-flow",{label:(flow.label?flow.label+" ":"")+"["+flow.id+"]"}));
return flow.id;
});
@@ -537,7 +553,7 @@ function getFlow(id) {
if (flow.label) {
result.label = flow.label;
}
- if (flow.disabled) {
+ if (flow.hasOwnProperty('disabled')) {
result.disabled = flow.disabled;
}
if (flow.hasOwnProperty('info')) {
@@ -646,7 +662,7 @@ function updateFlow(id,newFlow) {
}
newConfig = newConfig.concat(nodes);
- return setFlows(newConfig,'flows',true).then(function() {
+ return setFlows(newConfig,null,'flows',true).then(function() {
log.info(log._("nodes.flows.updated-flow",{label:(label?label+" ":"")+"["+id+"]"}));
})
}
@@ -668,7 +684,7 @@ function removeFlow(id) {
return node.z !== id && node.id !== id;
});
- return setFlows(newConfig,'flows',true).then(function() {
+ return setFlows(newConfig,null,'flows',true).then(function() {
log.info(log._("nodes.flows.removed-flow",{label:(flow.label?flow.label+" ":"")+"["+flow.id+"]"}));
});
}
diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/util.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/util.js
index b6074d792..e06cc5ede 100644
--- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/util.js
+++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/util.js
@@ -85,6 +85,7 @@ module.exports = {
flow.subflows = {};
flow.configs = {};
flow.flows = {};
+ flow.groups = {};
flow.missingTypes = [];
config.forEach(function(n) {
@@ -95,8 +96,12 @@ module.exports = {
flow.flows[n.id].configs = {};
flow.flows[n.id].nodes = {};
}
+ if (n.type === 'group') {
+ flow.groups[n.id] = n;
+ }
});
+ // TODO: why a separate forEach? this can be merged with above
config.forEach(function(n) {
if (n.type === 'subflow') {
flow.subflows[n.id] = n;
@@ -108,7 +113,7 @@ module.exports = {
var linkWires = {};
var linkOutNodes = [];
config.forEach(function(n) {
- if (n.type !== 'subflow' && n.type !== 'tab') {
+ if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') {
var subflowDetails = subflowInstanceRE.exec(n.type);
if ( (subflowDetails && !flow.subflows[subflowDetails[1]]) || (!subflowDetails && !typeRegistry.get(n.type)) ) {
@@ -163,7 +168,7 @@ module.exports = {
var addedTabs = {};
config.forEach(function(n) {
- if (n.type !== 'subflow' && n.type !== 'tab') {
+ if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') {
for (var prop in n) {
if (n.hasOwnProperty(prop) && prop !== 'id' && prop !== 'wires' && prop !== 'type' && prop !== '_users' && flow.configs.hasOwnProperty(n[prop])) {
// This property references a global config node
diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/index.js b/packages/node_modules/@node-red/runtime/lib/nodes/index.js
index fcb153fe1..09aebe6eb 100644
--- a/packages/node_modules/@node-red/runtime/lib/nodes/index.js
+++ b/packages/node_modules/@node-red/runtime/lib/nodes/index.js
@@ -150,10 +150,10 @@ function reportNodeStateChange(info,enabled) {
}
}
-function installModule(module,version) {
+function installModule(module,version,url) {
var existingModule = registry.getModuleInfo(module);
var isUpgrade = !!existingModule;
- return registry.installModule(module,version).then(function(info) {
+ return registry.installModule(module,version,url).then(function(info) {
if (isUpgrade) {
events.emit("runtime-event",{id:"node/upgraded",retain:false,payload:{module:module,version:version}});
} else {
diff --git a/packages/node_modules/@node-red/runtime/lib/settings.js b/packages/node_modules/@node-red/runtime/lib/settings.js
index 35417a497..e22a90686 100644
--- a/packages/node_modules/@node-red/runtime/lib/settings.js
+++ b/packages/node_modules/@node-red/runtime/lib/settings.js
@@ -87,7 +87,7 @@ var persistentSettings = {
throw new Error(log._("settings.not-available"));
}
var current = globalSettings[prop];
- globalSettings[prop] = value;
+ globalSettings[prop] = clone(value);
try {
assert.deepEqual(current,value);
return when.resolve();
diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/library.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/library.js
index da0e1f94f..04701321f 100644
--- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/library.js
+++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/library.js
@@ -25,73 +25,66 @@ var settings;
var libDir;
var libFlowsDir;
+function toSingleLine(text) {
+ var result = text.replace(/\\/g, "\\\\").replace(/\n/g, "\\n");
+ return result;
+}
-function getFileMeta(root,path) {
- var fn = fspath.join(root,path);
- var fd = fs.openSync(fn,"r");
+function fromSingleLine(text) {
+ var result = text.replace(/\\[\\n]/g, function(s) {
+ return ((s === "\\\\") ? "\\" : "\n");
+ });
+ return result;
+}
+
+function getFileMeta(root, path) {
+ var fn = fspath.join(root, path);
+ var fd = fs.openSync(fn, 'r');
var size = fs.fstatSync(fd).size;
var meta = {};
var read = 0;
var length = 10;
- var remaining = "";
+ var remaining = Buffer.alloc(0);
var buffer = Buffer.alloc(length);
- while(read < size) {
- read+=fs.readSync(fd,buffer,0,length);
- var data = remaining+buffer.toString();
- var parts = data.split("\n");
- remaining = parts.splice(-1);
- for (var i=0;i 0?"\n":"")+parts[i];
- }
- }
- if (!scanning) {
- body += remaining;
- }
- } else {
- read += thisRead;
- body += buffer.slice(0,thisRead).toString();
+ for (var i = 0; i < parts.length; i++) {
+ if (! /^\/\/ \w+: /.test(parts[i]) || !scanning) {
+ body += (body.length > 0 ? '\n' : '') + parts[i];
+ scanning = false;
}
}
- fs.closeSync(fd);
return body;
}
+
function getLibraryEntry(type,path) {
var root = fspath.join(libDir,type);
var rootPath = fspath.join(libDir,type,path);
@@ -172,7 +165,7 @@ module.exports = {
var headers = "";
for (var i in meta) {
if (meta.hasOwnProperty(i)) {
- headers += "// "+i+": "+meta[i]+"\n";
+ headers += "// "+i+": "+toSingleLine(meta[i])+"\n";
}
}
if (type === "flows" && settings.flowFilePretty) {
diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/Project.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/Project.js
index 7ba3bd2f3..ad3c0bf26 100644
--- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/Project.js
+++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/Project.js
@@ -706,7 +706,9 @@ Project.prototype.fetch = function(user,remoteName) {
promise = promise.then(function() {
return gitTools.fetch(project.path,remote,authCache.get(project.name,project.remotes[remote].fetch,username))
}).catch(function(err) {
- err.remote = remote;
+ if (!err.remote) {
+ err.remote = remote;
+ }
throw err;
})
});
diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js
index b9f114698..d812c05f2 100644
--- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js
+++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js
@@ -41,6 +41,9 @@ function runGitCommand(args,cwd,env,emit) {
err.code = "git_connection_failed";
} else if (/Connection timed out/i.test(stderr)) {
err.code = "git_connection_failed";
+ } else if(/Host key verification failed/i.test(stderr)) {
+ // TODO: handle host key verification errors separately
+ err.code = "git_host_key_verification_failed";
} else if (/fatal: could not read/i.test(stderr)) {
// Username/Password
err.code = "git_auth_failed";
@@ -48,9 +51,6 @@ function runGitCommand(args,cwd,env,emit) {
err.code = "git_auth_failed";
} else if(/Permission denied \(publickey\)/i.test(stderr)) {
err.code = "git_auth_failed";
- } else if(/Host key verification failed/i.test(stderr)) {
- // TODO: handle host key verification errors separately
- err.code = "git_auth_failed";
} else if (/commit your changes or stash/i.test(stderr)) {
err.code = "git_local_overwrite";
} else if (/CONFLICT/.test(err.stdout)) {
diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/node-red-ask-pass.sh b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/node-red-ask-pass.sh
index 695b2a19e..b6fbf80f4 100755
--- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/node-red-ask-pass.sh
+++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/node-red-ask-pass.sh
@@ -1 +1,2 @@
+#!/bin/sh
"$NODE_RED_GIT_NODE_PATH" "$NODE_RED_GIT_ASKPASS_PATH" "$NODE_RED_GIT_SOCK_PATH" $@
diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/node-red-ssh.sh b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/node-red-ssh.sh
index 26a232f83..b879b7384 100755
--- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/node-red-ssh.sh
+++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/node-red-ssh.sh
@@ -1 +1,2 @@
+#!/bin/sh
ssh -i "$NODE_RED_KEY_FILE" -F /dev/null $@
diff --git a/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json b/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json
index d56031d2f..fea4d5591 100644
--- a/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json
+++ b/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json
@@ -47,7 +47,14 @@
"now-running": "Server now running at __listenpath__",
"failed-to-start": "Failed to start server:",
"headless-mode": "Running in headless mode",
- "httpadminauth-deprecated": "use of httpAdminAuth is deprecated. Use adminAuth instead"
+ "httpadminauth-deprecated": "use of httpAdminAuth is deprecated. Use adminAuth instead",
+ "https": {
+ "refresh-interval": "Refreshing https settings every __interval__ hours",
+ "settings-refreshed": "Server https settings have been refreshed",
+ "refresh-failed": "Failed to refresh https settings: __message__",
+ "nodejs-version": "httpsRefreshInterval requires Node.js 11 or later",
+ "function-required": "httpsRefreshInterval requires https property to be a function"
+ }
},
"api": {
diff --git a/packages/node_modules/@node-red/runtime/locales/zh-CN/runtime.json b/packages/node_modules/@node-red/runtime/locales/zh-CN/runtime.json
new file mode 100644
index 000000000..d0813f476
--- /dev/null
+++ b/packages/node_modules/@node-red/runtime/locales/zh-CN/runtime.json
@@ -0,0 +1,174 @@
+{
+ "runtime": {
+ "welcome": "欢迎使用Node-RED",
+ "version": "__component__ 版本: __version__",
+ "unsupported_version": "__component__的不受支持的版本。要求: __requires__ 找到: __version__",
+ "paths": {
+ "settings": "设置文件 : __path__",
+ "httpStatic": "HTTP Static : __path__"
+ }
+ },
+
+ "server": {
+ "loading": "加载控制板节点",
+ "palette-editor": {
+ "disabled": "控制板编辑器已禁用:用户设置",
+ "npm-not-found": "控制板编辑器已禁用:找不到npm命令",
+ "npm-too-old": "控制板编辑器已禁用: npm版本太旧。需要版本npm >= 3.x"
+ },
+ "errors": "无法注册__count__节点类型",
+ "errors_plural": "无法注册__count__个节点类型",
+ "errors-help": "使用-v运行以获取详细信息",
+ "missing-modules": "缺少节点模块:",
+ "node-version-mismatch": "无法在此版本上加载节点模块。要求:__ version__",
+ "type-already-registered": "'__type__'已由模块__module__注册",
+ "removing-modules": "从配置中删除模块",
+ "added-types": "添加的节点类型:",
+ "removed-types": "删除的节点类型:",
+ "install": {
+ "invalid": "无效的模块名称",
+ "installing": "安装模块:__ name__,版本:__ version__",
+ "installed": "已安装的模块:__ name__",
+ "install-failed": "安装失败",
+ "install-failed-long": "模块__name__安装失败:",
+ "install-failed-not-found": "$t(server.install.install-failed-long) 模块未发现",
+ "upgrading": "更新模块: __name__ 至版本: __version__",
+ "upgraded": "更新模块: __name__。 重新启动Node-RED以使用新版本",
+ "upgrade-failed-not-found": "$t(server.install.install-failed-long) 未发现版本",
+ "uninstalling": "卸载模块: __name__",
+ "uninstall-failed": "卸载失败",
+ "uninstall-failed-long": "卸载模块__name__失败:",
+ "uninstalled": "卸载模块: __name__"
+ },
+ "unable-to-listen": "无法在__listenpath__上收听",
+ "port-in-use": "错误: 端口正在使用中",
+ "uncaught-exception": "未捕获的异常:",
+ "admin-ui-disabled": "管理员界面已禁用",
+ "now-running": "服务器现在在__listenpath__上运行",
+ "failed-to-start": "无法启动服务器:",
+ "headless-mode": "在headless模式下运行",
+ "httpadminauth-deprecated": "不建议使用httpAdminAuth。请改用adminAuth"
+ },
+
+ "api": {
+ "flows": {
+ "error-save": "保存流程错误: __message__",
+ "error-reload": "重载流程错误: __message__"
+ },
+ "library": {
+ "error-load-entry": "加载库条目'__path__'时出错:__message__",
+ "error-save-entry": "保存库条目'__path__'时出错:__ message__",
+ "error-load-flow": "加载流程'__path__'时出错:__ message__",
+ "error-save-flow": "保存流'__path__'时出错:__ message__"
+ },
+ "nodes": {
+ "enabled": "启用的节点类型:",
+ "disabled": "禁用的节点类型:",
+ "error-enable": "无法启用节点:"
+ }
+ },
+
+ "comms": {
+ "error": "通讯渠道错误:__ message__",
+ "error-server": "通信服务器错误:__ message__",
+ "error-send": "通讯发送错误:__ message__"
+ },
+
+ "settings": {
+ "user-not-available": "无法保存用户设置:__ message__",
+ "not-available": "设置不可用",
+ "property-read-only": "属性“ __prop__”是只读的"
+ },
+
+ "nodes": {
+ "credentials": {
+ "error":"加载证书时出错:__ message__",
+ "error-saving":"保存证书时出错:__ message__",
+ "not-registered": "证书类型'__type__'未注册",
+ "system-key-warning": "\n\n---------------------------------------------------------------------\n您的流程证书文件是使用系统生成的密钥加密的。\n\n如果系统生成的密钥由于任何原因丢失,则您的证书文件将无法恢复,您将必须删除它并重新输入您的证书。\n\n您应该使用您的设置文件中的'credentialSecret'选项设置自己的密钥。然后,下次部署更改时,Node-RED将使用选择的密钥重新加密您的证书文件。\n---------------------------------------------------------------------\n"
+ },
+ "flows": {
+ "safe-mode": "流程在安全模式下停止。部署开始。",
+ "registered-missing": "缺少注册的类型:__ type__",
+ "error": "错误加载流程:__ message__",
+ "starting-modified-nodes": "启动修改的节点",
+ "starting-modified-flows": "启动修改的流程",
+ "starting-flows": "启动流程",
+ "started-modified-nodes": "修改的节点已启动",
+ "started-modified-flows": "修改的流程已启动",
+ "started-flows": "流程已启动",
+ "stopping-modified-nodes": "停止修改的节点",
+ "stopping-modified-flows": "停止修改的流程",
+ "stopping-flows": "停止流程",
+ "stopped-modified-nodes": "修改的节点已停止",
+ "stopped-modified-flows": "修改的流程已停止",
+ "stopped-flows": "流程已停止",
+ "stopped": "已停止",
+ "stopping-error": "错误停止节点:__ message__",
+ "added-flow": "流程已添加: __label__",
+ "updated-flow": "流程已更新: __label__",
+ "removed-flow": "流程已移除: __label__",
+ "missing-types": "等待缺少的类型被注册:",
+ "missing-type-provided": " - __type__ (由npm模块__module__提供)",
+ "missing-type-install-1": "要安装所有缺少的模块,请运行:",
+ "missing-type-install-2": "在目录中:"
+ },
+ "flow": {
+ "unknown-type": "未知类型: __type__",
+ "missing-types": "缺少类型",
+ "error-loop": "邮件已超过最大捕获数"
+ },
+ "index": {
+ "unrecognised-id": "无法识别的ID: __id__",
+ "type-in-use": "使用中的类型: __msg__",
+ "unrecognised-module": "无法识别的模块: __module__"
+ },
+ "registry": {
+ "localfilesystem": {
+ "module-not-found": "找不到模块:'__module__'"
+ }
+ }
+ },
+
+ "storage": {
+ "index": {
+ "forbidden-flow-name": "禁止流程名称"
+ },
+ "localfilesystem": {
+ "user-dir": "用户目录: __path__",
+ "flows-file": "流程文件: __path__",
+ "create": "创建新__type__文件",
+ "empty": "现有__type__文件为空",
+ "invalid": "现有__type__文件为无效json",
+ "restore": "恢复__type__文件备份:__path__",
+ "restore-fail": "恢复__type__文件备份失败:__ message__",
+ "fsync-fail": "将文件__path__刷新到磁盘失败:__message__",
+ "projects": {
+ "changing-project": "设置活动项目:__ project__",
+ "active-project": "活动项目:__ project__",
+ "project-not-found": "找不到项目:__ project__",
+ "no-active-project": "没有活动的项目:使用默认流文件",
+ "disabled": "项目已禁用:editorTheme.projects.enabled = false",
+ "disabledNoFlag": "项目已禁用:设置editorTheme.projects.enabled = true来启用",
+ "git-not-found": "项目已禁用:找不到git命令",
+ "git-version-old": "项目已禁用:不支持的git __version__。 需要的git版本为2.x",
+ "summary": "一个Node-RED项目",
+ "readme": "### 关于\n\n这是您项目的README.md文件。它可以帮助用户了解您的项目,如何使用它以及他们可能需要知道的其他任何信息。"
+ }
+ }
+ },
+
+ "context": {
+ "log-store-init": "上下文储存: '__name__' [__info__]",
+ "error-loading-module": "加载上下文存储时出错: __message__",
+ "error-loading-module2": "加载上下文存储时出错 '__module__': __message__",
+ "error-module-not-defined": "上下文存储库'__storage__'缺少“模块”选项",
+ "error-invalid-module-name": "无效的上下文存储名称:'__ name__'",
+ "error-invalid-default-module": "无效的默认的上下文存储库: '__storage__'",
+ "unknown-store": "指定了未知的上下文存储库'__name__'。 使用默认存储库。",
+ "localfilesystem": {
+ "error-circular": "上下文__scope__包含无法保留的循环引用",
+ "error-write": "编写上下文时出错:__ message__"
+ }
+ }
+}
diff --git a/packages/node_modules/@node-red/runtime/locales/zh-TW/runtime.json b/packages/node_modules/@node-red/runtime/locales/zh-TW/runtime.json
new file mode 100644
index 000000000..b96d87a6e
--- /dev/null
+++ b/packages/node_modules/@node-red/runtime/locales/zh-TW/runtime.json
@@ -0,0 +1,174 @@
+{
+ "runtime": {
+ "welcome": "歡迎使用Node-RED",
+ "version": "__component__ 版本: __version__",
+ "unsupported_version": "__component__的不受支持的版本。要求: __requires__ 找到: __version__",
+ "paths": {
+ "settings": "設置文件 : __path__",
+ "httpStatic": "HTTP Static : __path__"
+ }
+ },
+
+ "server": {
+ "loading": "加載控制板節點",
+ "palette-editor": {
+ "disabled": "控制板編輯器已禁用:用戶設置",
+ "npm-not-found": "控制板編輯器已禁用:找不到npm命令",
+ "npm-too-old": "控制板編輯器已禁用: npm版本太舊。需要版本npm >= 3.x"
+ },
+ "errors": "無法註冊__count__節點類型",
+ "errors_plural": "無法註冊__count__個節點類型",
+ "errors-help": "使用-v運行以獲取詳細信息",
+ "missing-modules": "缺少節點模組:",
+ "node-version-mismatch": "無法在此版本上加載節點模組。要求:__ version__",
+ "type-already-registered": "'__type__'已由模組__module__註冊",
+ "removing-modules": "從配置中刪除模組",
+ "added-types": "添加的節點類型:",
+ "removed-types": "刪除的節點類型:",
+ "install": {
+ "invalid": "無效的模組名稱",
+ "installing": "安裝模組:__ name__,版本:__ version__",
+ "installed": "已安裝的模組:__ name__",
+ "install-failed": "安裝失敗",
+ "install-failed-long": "模組__name__安裝失敗:",
+ "install-failed-not-found": "$t(server.install.install-failed-long) 模組未發現",
+ "upgrading": "更新模組: __name__ 至版本: __version__",
+ "upgraded": "更新模組: __name__。 重新啟動Node-RED以使用新版本",
+ "upgrade-failed-not-found": "$t(server.install.install-failed-long) 未發現版本",
+ "uninstalling": "卸載模組: __name__",
+ "uninstall-failed": "卸載失敗",
+ "uninstall-failed-long": "卸載模組__name__失敗:",
+ "uninstalled": "卸載模組: __name__"
+ },
+ "unable-to-listen": "無法在__listenpath__上收聽",
+ "port-in-use": "錯誤: 端口正在使用中",
+ "uncaught-exception": "未捕獲的異常:",
+ "admin-ui-disabled": "管理員界面已禁用",
+ "now-running": "服務器現在在__listenpath__上運行",
+ "failed-to-start": "無法啟動服務器:",
+ "headless-mode": "在headless模式下運行",
+ "httpadminauth-deprecated": "不建議使用httpAdminAuth。請改用adminAuth"
+ },
+
+ "api": {
+ "flows": {
+ "error-save": "保存流程錯誤: __message__",
+ "error-reload": "重載流程錯誤: __message__"
+ },
+ "library": {
+ "error-load-entry": "加載庫條目'__path__'時出錯:__message__",
+ "error-save-entry": "保存庫條目'__path__'時出錯:__ message__",
+ "error-load-flow": "加載流程'__path__'時出錯:__ message__",
+ "error-save-flow": "保存流'__path__'時出錯:__ message__"
+ },
+ "nodes": {
+ "enabled": "啟用的節點類型:",
+ "disabled": "禁用的節點類型:",
+ "error-enable": "無法啟用節點:"
+ }
+ },
+
+ "comms": {
+ "error": "通訊渠道錯誤:__ message__",
+ "error-server": "通信服務器錯誤:__ message__",
+ "error-send": "通訊發送錯誤:__ message__"
+ },
+
+ "settings": {
+ "user-not-available": "無法保存用戶設置:__ message__",
+ "not-available": "設置不可用",
+ "property-read-only": "屬性“ __prop__”是只讀的"
+ },
+
+ "nodes": {
+ "credentials": {
+ "error":"加載證書時出錯:__ message__",
+ "error-saving":"保存證書時出錯:__ message__",
+ "not-registered": "證書類型'__type__'未註冊",
+ "system-key-warning": "\n\n---------------------------------------------------------------------\n您的流程證書文件是使用系統生成的密鑰加密的。\n\n如果系統生成的密鑰由於任何原因丟失,則您的證書文件將無法恢復,您將必須刪除它並重新輸入您的證書。\n\n您應該使用您的設置文件中的'credentialSecret'選項設置自己的密鑰。然後,下次部署更改時,Node-RED將使用選擇的密鑰重新加密您的證書文件。\n---------------------------------------------------------------------\n"
+ },
+ "flows": {
+ "safe-mode": "流程在安全模式下停止。部署開始。",
+ "registered-missing": "缺少註冊的類型:__ type__",
+ "error": "錯誤加載流程:__ message__",
+ "starting-modified-nodes": "啟動修改的節點",
+ "starting-modified-flows": "啟動修改的流程",
+ "starting-flows": "啟動流程",
+ "started-modified-nodes": "修改的節點已啟動",
+ "started-modified-flows": "修改的流程已啟動",
+ "started-flows": "流程已啟動",
+ "stopping-modified-nodes": "停止修改的節點",
+ "stopping-modified-flows": "停止修改的流程",
+ "stopping-flows": "停止流程",
+ "stopped-modified-nodes": "修改的節點已停止",
+ "stopped-modified-flows": "修改的流程已停止",
+ "stopped-flows": "流程已停止",
+ "stopped": "已停止",
+ "stopping-error": "錯誤停止節點:__ message__",
+ "added-flow": "流程已添加: __label__",
+ "updated-flow": "流程已更新: __label__",
+ "removed-flow": "流程已移除: __label__",
+ "missing-types": "等待缺少的類型被註冊:",
+ "missing-type-provided": " - __type__ (由npm模組__module__提供)",
+ "missing-type-install-1": "要安裝所有缺少的模組,請運行:",
+ "missing-type-install-2": "在目錄中:"
+ },
+ "flow": {
+ "unknown-type": "未知類型: __type__",
+ "missing-types": "缺少類型",
+ "error-loop": "郵件已超過最大捕獲數"
+ },
+ "index": {
+ "unrecognised-id": "無法識別的ID: __id__",
+ "type-in-use": "使用中的類型: __msg__",
+ "unrecognised-module": "無法識別的模組: __module__"
+ },
+ "registry": {
+ "localfilesystem": {
+ "module-not-found": "找不到模組:'__module__'"
+ }
+ }
+ },
+
+ "storage": {
+ "index": {
+ "forbidden-flow-name": "禁止流程名稱"
+ },
+ "localfilesystem": {
+ "user-dir": "用戶目錄: __path__",
+ "flows-file": "流程文件: __path__",
+ "create": "創建新__type__文件",
+ "empty": "現有__type__文件為空",
+ "invalid": "現有__type__文件為無效json",
+ "restore": "恢復__type__文件備份:__path__",
+ "restore-fail": "恢復__type__文件備份失敗:__ message__",
+ "fsync-fail": "將文件__path__刷新到磁盤失敗:__message__",
+ "projects": {
+ "changing-project": "設置活動項目:__ project__",
+ "active-project": "活動項目:__ project__",
+ "project-not-found": "找不到項目:__ project__",
+ "no-active-project": "沒有活動的項目:使用默認流文件",
+ "disabled": "項目已禁用:editorTheme.projects.enabled = false",
+ "disabledNoFlag": "項目已禁用:設置editorTheme.projects.enabled = true來啟用",
+ "git-not-found": "項目已禁用:找不到git命令",
+ "git-version-old": "項目已禁用:不支持的git __version__。 需要的git版本為2.x",
+ "summary": "一個Node-RED項目",
+ "readme": "### 關於\n\n這是您項目的README.md文件。它可以幫助用戶了解您的項目,如何使用它以及他們可能需要知道的其他任何信息。"
+ }
+ }
+ },
+
+ "context": {
+ "log-store-init": "上下文儲存: '__name__' [__info__]",
+ "error-loading-module": "加載上下文存儲時出錯: __message__",
+ "error-loading-module2": "加載上下文存儲時出錯 '__module__': __message__",
+ "error-module-not-defined": "上下文存儲庫'__storage__'缺少“模組”選項",
+ "error-invalid-module-name": "無效的上下文存儲名稱:'__ name__'",
+ "error-invalid-default-module": "無效的默認的上下文存儲庫: '__storage__'",
+ "unknown-store": "指定了未知的上下文存儲庫'__name__'。 使用默認存儲庫。",
+ "localfilesystem": {
+ "error-circular": "上下文__scope__包含無法保留的循環引用",
+ "error-write": "編寫上下文時出錯:__ message__"
+ }
+ }
+}
diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json
index 0d03b355b..93dc305f9 100644
--- a/packages/node_modules/@node-red/runtime/package.json
+++ b/packages/node_modules/@node-red/runtime/package.json
@@ -1,6 +1,6 @@
{
"name": "@node-red/runtime",
- "version": "1.0.3",
+ "version": "1.1.0",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,8 +16,8 @@
}
],
"dependencies": {
- "@node-red/registry": "1.0.3",
- "@node-red/util": "1.0.3",
+ "@node-red/registry": "1.1.0",
+ "@node-red/util": "1.1.0",
"clone": "2.1.2",
"express": "4.17.1",
"fs-extra": "8.1.0",
diff --git a/packages/node_modules/@node-red/util/lib/util.js b/packages/node_modules/@node-red/util/lib/util.js
index 9cdf2e0bb..e5e270723 100644
--- a/packages/node_modules/@node-red/util/lib/util.js
+++ b/packages/node_modules/@node-red/util/lib/util.js
@@ -19,9 +19,9 @@
* @mixin @node-red/util_util
*/
-
-const clone = require("clone");
+const clonedeep = require("lodash.clonedeep");
const jsonata = require("jsonata");
+const moment = require("moment-timezone");
const safeJSONStringify = require("json-stringify-safe");
const util = require("util");
@@ -81,22 +81,25 @@ function ensureBuffer(o) {
* @memberof @node-red/util_util
*/
function cloneMessage(msg) {
- // Temporary fix for #97
- // TODO: remove this http-node-specific fix somehow
- var req = msg.req;
- var res = msg.res;
- delete msg.req;
- delete msg.res;
- var m = clone(msg);
- if (req) {
- m.req = req;
- msg.req = req;
+ if (typeof msg !== "undefined" && msg !== null) {
+ // Temporary fix for #97
+ // TODO: remove this http-node-specific fix somehow
+ var req = msg.req;
+ var res = msg.res;
+ delete msg.req;
+ delete msg.res;
+ var m = clonedeep(msg);
+ if (req) {
+ m.req = req;
+ msg.req = req;
+ }
+ if (res) {
+ m.res = res;
+ msg.res = res;
+ }
+ return m;
}
- if (res) {
- m.res = res;
- msg.res = res;
- }
- return m;
+ return msg;
}
/**
@@ -566,16 +569,23 @@ function evaluateNodeProperty(value, type, node, msg, callback) {
*/
function prepareJSONataExpression(value,node) {
var expr = jsonata(value);
- expr.assign('flowContext',function(val) {
- return node.context().flow.get(val);
+ expr.assign('flowContext', function(val, store) {
+ return node.context().flow.get(val, store);
});
- expr.assign('globalContext',function(val) {
- return node.context().global.get(val);
+ expr.assign('globalContext', function(val, store) {
+ return node.context().global.get(val, store);
});
expr.assign('env', function(name) {
var val = getSetting(node, name);
- return (val ? val : "");
- })
+ if (typeof val !== 'undefined') {
+ return val;
+ } else {
+ return "";
+ }
+ });
+ expr.assign('moment', function(arg1, arg2, arg3, arg4) {
+ return moment(arg1, arg2, arg3, arg4);
+ });
expr.registerFunction('clone', cloneMessage, '<(oa)-:o>');
expr._legacyMode = /(^|[^a-zA-Z0-9_'"])msg([^a-zA-Z0-9_'"]|$)/.test(value);
expr._node = node;
diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json
index 174db5bfd..4ae8b5014 100644
--- a/packages/node_modules/@node-red/util/package.json
+++ b/packages/node_modules/@node-red/util/package.json
@@ -1,6 +1,6 @@
{
"name": "@node-red/util",
- "version": "1.0.3",
+ "version": "1.1.0",
"license": "Apache-2.0",
"repository": {
"type": "git",
@@ -18,7 +18,9 @@
"clone": "2.1.2",
"i18next": "15.1.2",
"json-stringify-safe": "5.0.1",
- "jsonata": "1.7.0",
+ "jsonata": "1.8.3",
+ "lodash.clonedeep": "^4.5.0",
+ "moment-timezone": "^0.5.31",
"when": "3.7.8"
}
}
diff --git a/packages/node_modules/node-red/package.json b/packages/node_modules/node-red/package.json
index 618480400..5c7192a20 100644
--- a/packages/node_modules/node-red/package.json
+++ b/packages/node_modules/node-red/package.json
@@ -1,6 +1,6 @@
{
"name": "node-red",
- "version": "1.0.3",
+ "version": "1.1.0",
"description": "Low-code programming for event-driven applications",
"homepage": "http://nodered.org",
"license": "Apache-2.0",
@@ -31,17 +31,17 @@
"flow"
],
"dependencies": {
- "@node-red/editor-api": "1.0.3",
- "@node-red/runtime": "1.0.3",
- "@node-red/util": "1.0.3",
- "@node-red/nodes": "1.0.3",
+ "@node-red/editor-api": "1.1.0",
+ "@node-red/runtime": "1.1.0",
+ "@node-red/util": "1.1.0",
+ "@node-red/nodes": "1.1.0",
"basic-auth": "2.0.1",
"bcryptjs": "2.4.3",
"express": "4.17.1",
"fs-extra": "8.1.0",
"node-red-node-rbe": "^0.2.6",
"node-red-node-tail": "^0.1.0",
- "nopt": "4.0.1",
+ "nopt": "4.0.3",
"semver": "6.3.0"
},
"optionalDependencies": {
diff --git a/packages/node_modules/node-red/red.js b/packages/node_modules/node-red/red.js
index 26b6d1fc8..05dc1a0bf 100755
--- a/packages/node_modules/node-red/red.js
+++ b/packages/node_modules/node-red/red.js
@@ -39,7 +39,8 @@ var knownOpts = {
"title": String,
"userDir": [path],
"verbose": Boolean,
- "safe": Boolean
+ "safe": Boolean,
+ "define": [String, Array]
};
var shortHands = {
"?":["--help"],
@@ -49,7 +50,8 @@ var shortHands = {
// doesn't get treated as --title
"t":["--help"],
"u":["--userDir"],
- "v":["--verbose"]
+ "v":["--verbose"],
+ "D":["--define"]
};
nopt.invalidHandler = function(k,v,t) {
// TODO: console.log(k,v,t);
@@ -69,6 +71,7 @@ if (parsedArgs.help) {
console.log(" -u, --userDir DIR use specified user directory");
console.log(" -v, --verbose enable verbose output");
console.log(" --safe enable safe mode");
+ console.log(" -D, --define X=Y overwrite value in settings file");
console.log(" -?, --help show this help");
console.log("");
console.log("Documentation can be found at http://nodered.org");
@@ -130,6 +133,36 @@ try {
process.exit();
}
+if (parsedArgs.define) {
+ var defs = parsedArgs.define;
+ try {
+ while (defs.length > 0) {
+ var def = defs.shift();
+ var match = /^(([^=]+)=(.+)|@(.*))$/.exec(def);
+ if (match) {
+ if (!match[4]) {
+ var val = JSON.parse(match[3]);
+ RED.util.setObjectProperty(settings, match[2], val, true);
+ } else {
+ var obj = fs.readJsonSync(match[4]);
+ for (var k in obj) {
+ if (obj.hasOwnProperty(k)) {
+ RED.util.setObjectProperty(settings, k, obj[k], true)
+ }
+ }
+ }
+ }
+ else {
+ throw new Error("Invalid syntax: '"+def+"'");
+ }
+
+ }
+ } catch (e) {
+ console.log("Error processing -D option: "+e.message);
+ process.exit();
+ }
+}
+
if (parsedArgs.verbose) {
settings.verbose = true;
}
@@ -142,208 +175,265 @@ if (process.env.NODE_RED_ENABLE_PROJECTS) {
settings.editorTheme.projects.enabled = !/^false$/i.test(process.env.NODE_RED_ENABLE_PROJECTS);
}
-if (settings.https) {
- server = https.createServer(settings.https,function(req,res) {app(req,res);});
-} else {
- server = http.createServer(function(req,res) {app(req,res);});
-}
-server.setMaxListeners(0);
+// Delay logging of (translated) messages until the RED object has been initialized
+var delayedLogItems = [];
-function formatRoot(root) {
- if (root[0] != "/") {
- root = "/" + root;
- }
- if (root.slice(-1) != "/") {
- root = root + "/";
- }
- return root;
+var startupHttps = settings.https;
+if (typeof startupHttps === "function") {
+ // Get the result of the function, because createServer doesn't accept functions as input
+ startupHttps = startupHttps();
}
+var httpsPromise = Promise.resolve(startupHttps);
-if (settings.httpRoot === false) {
- settings.httpAdminRoot = false;
- settings.httpNodeRoot = false;
-} else {
- settings.httpRoot = settings.httpRoot||"/";
- settings.disableEditor = settings.disableEditor||false;
-}
+httpsPromise.then(function(startupHttps) {
+ if (startupHttps) {
+ server = https.createServer(startupHttps,function(req,res) {app(req,res);});
-if (settings.httpAdminRoot !== false) {
- settings.httpAdminRoot = formatRoot(settings.httpAdminRoot || settings.httpRoot || "/");
- settings.httpAdminAuth = settings.httpAdminAuth || settings.httpAuth;
-} else {
- settings.disableEditor = true;
-}
-
-if (settings.httpNodeRoot !== false) {
- settings.httpNodeRoot = formatRoot(settings.httpNodeRoot || settings.httpRoot || "/");
- settings.httpNodeAuth = settings.httpNodeAuth || settings.httpAuth;
-}
-
-// if we got a port from command line, use it (even if 0)
-// replicate (settings.uiPort = parsedArgs.port||settings.uiPort||1880;) but allow zero
-if (parsedArgs.port !== undefined){
- settings.uiPort = parsedArgs.port;
-} else {
- if (settings.uiPort === undefined){
- settings.uiPort = 1880;
- }
-}
-
-settings.uiHost = settings.uiHost||"0.0.0.0";
-
-if (flowFile) {
- settings.flowFile = flowFile;
-}
-if (parsedArgs.userDir) {
- settings.userDir = parsedArgs.userDir;
-}
-
-try {
- RED.init(server,settings);
-} catch(err) {
- if (err.code == "unsupported_version") {
- console.log("Unsupported version of Node.js:",process.version);
- console.log("Node-RED requires Node.js v8.9.0 or later");
- } else {
- console.log("Failed to start server:");
- if (err.stack) {
- console.log(err.stack);
- } else {
- console.log(err);
- }
- }
- process.exit(1);
-}
-
-function basicAuthMiddleware(user,pass) {
- var basicAuth = require('basic-auth');
- var checkPassword;
- var localCachedPassword;
- if (pass.length == "32") {
- // Assume its a legacy md5 password
- checkPassword = function(p) {
- return crypto.createHash('md5').update(p,'utf8').digest('hex') === pass;
- }
- } else {
- checkPassword = function(p) {
- return bcrypt.compareSync(p,pass);
- }
- }
-
- var checkPasswordAndCache = function(p) {
- // For BasicAuth routes we know the password cannot change without
- // a restart of Node-RED. This means we can cache the provided crypted
- // version to save recalculating each time.
- if (localCachedPassword === p) {
- return true;
- }
- var result = checkPassword(p);
- if (result) {
- localCachedPassword = p;
- }
- return result;
- }
-
- return function(req,res,next) {
- if (req.method === 'OPTIONS') {
- return next();
- }
- var requestUser = basicAuth(req);
- if (!requestUser || requestUser.name !== user || !checkPasswordAndCache(requestUser.pass)) {
- res.set('WWW-Authenticate', 'Basic realm="Authorization Required"');
- return res.sendStatus(401);
- }
- next();
- }
-}
-
-if (settings.httpAdminRoot !== false && settings.httpAdminAuth) {
- RED.log.warn(RED.log._("server.httpadminauth-deprecated"));
- app.use(settings.httpAdminRoot, basicAuthMiddleware(settings.httpAdminAuth.user,settings.httpAdminAuth.pass));
-}
-
-if (settings.httpAdminRoot !== false) {
- app.use(settings.httpAdminRoot,RED.httpAdmin);
-}
-if (settings.httpNodeRoot !== false && settings.httpNodeAuth) {
- app.use(settings.httpNodeRoot,basicAuthMiddleware(settings.httpNodeAuth.user,settings.httpNodeAuth.pass));
-}
-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));
-}
-
-function getListenPath() {
- var port = settings.serverPort;
- if (port === undefined){
- port = settings.uiPort;
- }
-
- var listenPath = 'http'+(settings.https?'s':'')+'://'+
- (settings.uiHost == '::'?'localhost':(settings.uiHost == '0.0.0.0'?'127.0.0.1':settings.uiHost))+
- ':'+port;
- if (settings.httpAdminRoot !== false) {
- listenPath += settings.httpAdminRoot;
- } else if (settings.httpStatic) {
- listenPath += "/";
- }
- return listenPath;
-}
-
-RED.start().then(function() {
- if (settings.httpAdminRoot !== false || settings.httpNodeRoot !== false || settings.httpStatic) {
- server.on('error', function(err) {
- if (err.errno === "EADDRINUSE") {
- RED.log.error(RED.log._("server.unable-to-listen", {listenpath:getListenPath()}));
- RED.log.error(RED.log._("server.port-in-use"));
- } else {
- RED.log.error(RED.log._("server.uncaught-exception"));
- if (err.stack) {
- RED.log.error(err.stack);
+ if (settings.httpsRefreshInterval) {
+ var httpsRefreshInterval = parseFloat(settings.httpsRefreshInterval)||12;
+ if (httpsRefreshInterval > 596) {
+ // Max value based on (2^31-1)ms - the max that setInterval can accept
+ httpsRefreshInterval = 596;
+ }
+ // Check whether setSecureContext is available (Node.js 11+)
+ if (server.setSecureContext) {
+ // Check whether `http` is a callable function
+ if (typeof settings.https === "function") {
+ delayedLogItems.push({type:"info", id:"server.https.refresh-interval", params:{interval:httpsRefreshInterval}});
+ setInterval(function () {
+ try {
+ // Get the result of the function, because createServer doesn't accept functions as input
+ Promise.resolve(settings.https()).then(function(refreshedHttps) {
+ if (refreshedHttps) {
+ // Only update the credentials in the server when key or cert has changed
+ if(!server.key || !server.cert || !server.key.equals(refreshedHttps.key) || !server.cert.equals(refreshedHttps.cert)) {
+ server.setSecureContext(refreshedHttps);
+ RED.log.info(RED.log._("server.https.settings-refreshed"));
+ }
+ }
+ }).catch(function(err) {
+ RED.log.error(RED.log._("server.https.refresh-failed",{message:err}));
+ });
+ } catch(err) {
+ RED.log.error(RED.log._("server.https.refresh-failed",{message:err}));
+ }
+ }, httpsRefreshInterval*60*60*1000);
} else {
- RED.log.error(err);
+ delayedLogItems.push({type:"warn", id:"server.https.function-required"});
}
+ } else {
+ delayedLogItems.push({type:"warn", id:"server.https.nodejs-version"});
}
- process.exit(1);
- });
- server.listen(settings.uiPort,settings.uiHost,function() {
- if (settings.httpAdminRoot === false) {
- RED.log.info(RED.log._("server.admin-ui-disabled"));
+ }
+ } else {
+ server = http.createServer(function(req,res) {app(req,res);});
+ }
+ server.setMaxListeners(0);
+
+ function formatRoot(root) {
+ if (root[0] != "/") {
+ root = "/" + root;
+ }
+ if (root.slice(-1) != "/") {
+ root = root + "/";
+ }
+ return root;
+ }
+
+ if (settings.httpRoot === false) {
+ settings.httpAdminRoot = false;
+ settings.httpNodeRoot = false;
+ } else {
+ settings.httpRoot = settings.httpRoot||"/";
+ settings.disableEditor = settings.disableEditor||false;
+ }
+
+ if (settings.httpAdminRoot !== false) {
+ settings.httpAdminRoot = formatRoot(settings.httpAdminRoot || settings.httpRoot || "/");
+ settings.httpAdminAuth = settings.httpAdminAuth || settings.httpAuth;
+ } else {
+ settings.disableEditor = true;
+ }
+
+ if (settings.httpNodeRoot !== false) {
+ settings.httpNodeRoot = formatRoot(settings.httpNodeRoot || settings.httpRoot || "/");
+ settings.httpNodeAuth = settings.httpNodeAuth || settings.httpAuth;
+ }
+
+ // if we got a port from command line, use it (even if 0)
+ // replicate (settings.uiPort = parsedArgs.port||settings.uiPort||1880;) but allow zero
+ if (parsedArgs.port !== undefined){
+ settings.uiPort = parsedArgs.port;
+ } else {
+ if (settings.uiPort === undefined){
+ settings.uiPort = 1880;
+ }
+ }
+
+ settings.uiHost = settings.uiHost||"0.0.0.0";
+
+ if (flowFile) {
+ settings.flowFile = flowFile;
+ }
+ if (parsedArgs.userDir) {
+ settings.userDir = parsedArgs.userDir;
+ }
+
+ try {
+ RED.init(server,settings);
+ } catch(err) {
+ if (err.code == "unsupported_version") {
+ console.log("Unsupported version of Node.js:",process.version);
+ console.log("Node-RED requires Node.js v8.9.0 or later");
+ } else {
+ console.log("Failed to start server:");
+ if (err.stack) {
+ console.log(err.stack);
+ } else {
+ console.log(err);
}
- settings.serverPort = server.address().port;
- process.title = parsedArgs.title || 'node-red';
- RED.log.info(RED.log._("server.now-running", {listenpath:getListenPath()}));
- });
- } else {
- RED.log.info(RED.log._("server.headless-mode"));
+ }
+ process.exit(1);
}
-}).otherwise(function(err) {
- RED.log.error(RED.log._("server.failed-to-start"));
- if (err.stack) {
- RED.log.error(err.stack);
- } else {
- RED.log.error(err);
- }
-});
-process.on('uncaughtException',function(err) {
- util.log('[red] Uncaught Exception:');
- if (err.stack) {
- util.log(err.stack);
- } else {
- util.log(err);
- }
- process.exit(1);
-});
+ function basicAuthMiddleware(user,pass) {
+ var basicAuth = require('basic-auth');
+ var checkPassword;
+ var localCachedPassword;
+ if (pass.length == "32") {
+ // Assume its a legacy md5 password
+ checkPassword = function(p) {
+ return crypto.createHash('md5').update(p,'utf8').digest('hex') === pass;
+ }
+ } else {
+ checkPassword = function(p) {
+ return bcrypt.compareSync(p,pass);
+ }
+ }
-process.on('SIGINT', function () {
- RED.stop().then(function() {
- process.exit();
+ var checkPasswordAndCache = function(p) {
+ // For BasicAuth routes we know the password cannot change without
+ // a restart of Node-RED. This means we can cache the provided crypted
+ // version to save recalculating each time.
+ if (localCachedPassword === p) {
+ return true;
+ }
+ var result = checkPassword(p);
+ if (result) {
+ localCachedPassword = p;
+ }
+ return result;
+ }
+
+ return function(req,res,next) {
+ if (req.method === 'OPTIONS') {
+ return next();
+ }
+ var requestUser = basicAuth(req);
+ if (!requestUser || requestUser.name !== user || !checkPasswordAndCache(requestUser.pass)) {
+ res.set('WWW-Authenticate', 'Basic realm="Authorization Required"');
+ return res.sendStatus(401);
+ }
+ next();
+ }
+ }
+
+ if (settings.httpAdminRoot !== false && settings.httpAdminAuth) {
+ RED.log.warn(RED.log._("server.httpadminauth-deprecated"));
+ app.use(settings.httpAdminRoot, basicAuthMiddleware(settings.httpAdminAuth.user,settings.httpAdminAuth.pass));
+ }
+
+ if (settings.httpAdminRoot !== false) {
+ app.use(settings.httpAdminRoot,RED.httpAdmin);
+ }
+ if (settings.httpNodeRoot !== false && settings.httpNodeAuth) {
+ app.use(settings.httpNodeRoot,basicAuthMiddleware(settings.httpNodeAuth.user,settings.httpNodeAuth.pass));
+ }
+ 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));
+ }
+
+ function getListenPath() {
+ var port = settings.serverPort;
+ if (port === undefined){
+ port = settings.uiPort;
+ }
+
+ var listenPath = 'http'+(settings.https?'s':'')+'://'+
+ (settings.uiHost == '::'?'localhost':(settings.uiHost == '0.0.0.0'?'127.0.0.1':settings.uiHost))+
+ ':'+port;
+ if (settings.httpAdminRoot !== false) {
+ listenPath += settings.httpAdminRoot;
+ } else if (settings.httpStatic) {
+ listenPath += "/";
+ }
+ return listenPath;
+ }
+
+ RED.start().then(function() {
+ if (settings.httpAdminRoot !== false || settings.httpNodeRoot !== false || settings.httpStatic) {
+ server.on('error', function(err) {
+ if (err.errno === "EADDRINUSE") {
+ RED.log.error(RED.log._("server.unable-to-listen", {listenpath:getListenPath()}));
+ RED.log.error(RED.log._("server.port-in-use"));
+ } else {
+ RED.log.error(RED.log._("server.uncaught-exception"));
+ if (err.stack) {
+ RED.log.error(err.stack);
+ } else {
+ RED.log.error(err);
+ }
+ }
+ process.exit(1);
+ });
+
+ // Log all the delayed messages, since they can be translated at this point
+ delayedLogItems.forEach(function (delayedLogItem, index) {
+ RED.log[delayedLogItem.type](RED.log._(delayedLogItem.id, delayedLogItem.params||{}));
+ });
+
+ server.listen(settings.uiPort,settings.uiHost,function() {
+ if (settings.httpAdminRoot === false) {
+ RED.log.info(RED.log._("server.admin-ui-disabled"));
+ }
+ settings.serverPort = server.address().port;
+ process.title = parsedArgs.title || 'node-red';
+ RED.log.info(RED.log._("server.now-running", {listenpath:getListenPath()}));
+ });
+ } else {
+ RED.log.info(RED.log._("server.headless-mode"));
+ }
+ }).otherwise(function(err) {
+ RED.log.error(RED.log._("server.failed-to-start"));
+ if (err.stack) {
+ RED.log.error(err.stack);
+ } else {
+ RED.log.error(err);
+ }
});
+
+ process.on('uncaughtException',function(err) {
+ util.log('[red] Uncaught Exception:');
+ if (err.stack) {
+ util.log(err.stack);
+ } else {
+ util.log(err);
+ }
+ process.exit(1);
+ });
+
+ process.on('SIGINT', function () {
+ RED.stop().then(function() {
+ process.exit();
+ });
+ });
+}).catch(function(err) {
+ console.log("Failed to get https settings: " + err);
});
diff --git a/packages/node_modules/node-red/settings.js b/packages/node_modules/node-red/settings.js
index 960cb6d17..1941b8fdc 100644
--- a/packages/node_modules/node-red/settings.js
+++ b/packages/node_modules/node-red/settings.js
@@ -139,13 +139,36 @@ module.exports = {
// The following property can be used to enable HTTPS
// See http://nodejs.org/api/https.html#https_https_createserver_options_requestlistener
// for details on its contents.
- // See the comment at the top of this file on how to load the `fs` module used by
- // this setting.
- //
+ // See the comment at the top of this file on how to load the `fs` module used by this setting.
+ // This property can be either an object, containing both a (private) key and a (public) certificate,
+ // or a function that returns such an object:
+ //// https object:
//https: {
- // key: fs.readFileSync('privatekey.pem'),
- // cert: fs.readFileSync('certificate.pem')
+ // key: fs.readFileSync('privkey.pem'),
+ // cert: fs.readFileSync('cert.pem')
//},
+ ////https synchronous function:
+ //https: function() {
+ // return {
+ // key: fs.readFileSync('privkey.pem'),
+ // cert: fs.readFileSync('cert.pem')
+ // }
+ //},
+ //// https asynchronous function:
+ //https: function() {
+ // return Promise.resolve({
+ // key: fs.readFileSync('privkey.pem'),
+ // cert: fs.readFileSync('cert.pem')
+ // });
+ //},
+
+ // The following property can be used to refresh the https settings at a
+ // regular time interval in hours.
+ // This requires:
+ // - the `https` setting to be a function that can be called to get
+ // the refreshed settings.
+ // - Node.js 11 or later.
+ //httpsRefreshInterval : 12,
// The following property can be used to cause insecure HTTP connections to
// be redirected to HTTPS.
@@ -182,6 +205,17 @@ module.exports = {
// next();
//},
+
+ // The following property can be used to add a custom middleware function
+ // in front of all admin http routes. For example, to set custom http
+ // headers
+ // httpAdminMiddleware: function(req,res,next) {
+ // // Set the X-Frame-Options header to limit where the editor
+ // // can be embedded
+ // //res.set('X-Frame-Options', 'sameorigin');
+ // next();
+ // },
+
// The following property can be used to pass custom options to the Express.js
// server used by Node-RED. For a full list of available options, refer
// to http://expressjs.com/en/api.html#app.settings.table
@@ -243,7 +277,7 @@ module.exports = {
// palette. If a node's category is not in the list, the category will get
// added to the end of the palette.
// If not set, the following default order is used:
- //paletteCategories: ['subflows','flow','input','output','function','parser','social','mobile','storage','analysis','advanced'],
+ //paletteCategories: ['subflows', 'common', 'function', 'network', 'sequence', 'parser', 'storage'],
// Configure the logging output
logging: {
diff --git a/scripts/install-ui-test-dependencies.sh b/scripts/install-ui-test-dependencies.sh
index 0e13f0b4f..155d2b275 100755
--- a/scripts/install-ui-test-dependencies.sh
+++ b/scripts/install-ui-test-dependencies.sh
@@ -3,5 +3,7 @@ npm install --no-save \
wdio-chromedriver-service@^0.1.5 \
wdio-mocha-framework@^0.6.4 \
wdio-spec-reporter@^0.1.5 \
- webdriverio@^4.14.1 \
- chromedriver@^78.0.1
+ webdriverio@^4.14.4 \
+ chromedriver@^79.0.0 \
+ wdio-browserstack-service@^0.1.19 \
+ browserstack-local@^1.4.4
diff --git a/test/editor/pageobjects/editor/palette_page.js b/test/editor/pageobjects/editor/palette_page.js
index a35859c5f..3b484a58c 100644
--- a/test/editor/pageobjects/editor/palette_page.js
+++ b/test/editor/pageobjects/editor/palette_page.js
@@ -15,22 +15,40 @@
**/
var idMap = {
- // input
+ // common
"inject": ".red-ui-palette-node[data-palette-type='inject']",
- "httpIn": ".red-ui-palette-node[data-palette-type='http in']",
- "mqttIn": ".red-ui-palette-node[data-palette-type='mqtt in']",
- // output
"debug": ".red-ui-palette-node[data-palette-type='debug']",
- "httpResponse": ".red-ui-palette-node[data-palette-type='http response']",
- "mqttOut": ".red-ui-palette-node[data-palette-type='mqtt out']",
+ "complete": ".red-ui-palette-node[data-palette-type='complete']",
+ "catch": ".red-ui-palette-node[data-palette-type='catch']",
+ "status": ".red-ui-palette-node[data-palette-type='status']",
+ "comment": ".red-ui-palette-node[data-palette-type='comment']",
// function
"function": ".red-ui-palette-node[data-palette-type='function']",
- "template": ".red-ui-palette-node[data-palette-type='template']",
+ "switch": ".red-ui-palette-node[data-palette-type='switch']",
"change": ".red-ui-palette-node[data-palette-type='change']",
"range": ".red-ui-palette-node[data-palette-type='range']",
+ "template": ".red-ui-palette-node[data-palette-type='template']",
+ "delay": ".red-ui-palette-node[data-palette-type='delay']",
+ "trigger": ".red-ui-palette-node[data-palette-type='trigger']",
+ "exec": ".red-ui-palette-node[data-palette-type='exec']",
+ // network
+ "mqttIn": ".red-ui-palette-node[data-palette-type='mqtt in']",
+ "mqttOut": ".red-ui-palette-node[data-palette-type='mqtt out']",
+ "httpIn": ".red-ui-palette-node[data-palette-type='http in']",
+ "httpResponse": ".red-ui-palette-node[data-palette-type='http response']",
"httpRequest": ".red-ui-palette-node[data-palette-type='http request']",
+ "websocketIn": ".red-ui-palette-node[data-palette-type='websocket in']",
+ "websocketOut": ".red-ui-palette-node[data-palette-type='websocket out']",
+ // sequence
+ "split": ".red-ui-palette-node[data-palette-type='split']",
+ "join": ".red-ui-palette-node[data-palette-type='join']",
+ "batch": ".red-ui-palette-node[data-palette-type='batch']",
+ // parser
+ "csv": ".red-ui-palette-node[data-palette-type='csv']",
"html": ".red-ui-palette-node[data-palette-type='html']",
"json": ".red-ui-palette-node[data-palette-type='json']",
+ "xml": ".red-ui-palette-node[data-palette-type='xml']",
+ "yaml": ".red-ui-palette-node[data-palette-type='yaml']",
// storage
"fileIn": ".red-ui-palette-node[data-palette-type='file in']",
};
diff --git a/test/editor/pageobjects/editor/workspace_page.js b/test/editor/pageobjects/editor/workspace_page.js
index ddf0a1c60..92a725dac 100644
--- a/test/editor/pageobjects/editor/workspace_page.js
+++ b/test/editor/pageobjects/editor/workspace_page.js
@@ -42,7 +42,7 @@ function addNode(type, x, y) {
}
}
browser.waitForVisible('#red-ui-palette-search');
- browser.setValue('//*[@id="red-ui-palette-search"]/div/input', type.replace(/([A-Z])/g,' $1').toLowerCase());
+ browser.setValue('//*[@id="red-ui-palette-search"]/div/form/input', type.replace(/([A-Z])/g, ' $1').toLowerCase());
browser.pause(300);
browser.waitForVisible(palette.getId(type));
browser.moveToObject(palette.getId(type));
@@ -66,8 +66,8 @@ function deleteAllNodes() {
function deploy() {
browser.call(function () {
- return when.promise(function(resolve, reject) {
- events.on("runtime-event", function(event) {
+ return when.promise(function (resolve, reject) {
+ events.on("runtime-event", function (event) {
if (event.id === 'runtime-deploy') {
events.removeListener("runtime-event", arguments.callee);
resolve();
diff --git a/test/editor/pageobjects/nodes/core/common/21-debug_page.js b/test/editor/pageobjects/nodes/core/common/21-debug_page.js
index 602d4d542..990ed6cd9 100644
--- a/test/editor/pageobjects/nodes/core/common/21-debug_page.js
+++ b/test/editor/pageobjects/nodes/core/common/21-debug_page.js
@@ -18,8 +18,6 @@ var util = require("util");
var nodePage = require("../../node_page");
-var keyPage = require("../../../util/key_page");
-
function debugNode(id) {
nodePage.call(this, id);
}
@@ -33,6 +31,8 @@ debugNode.prototype.setOutput = function (complete) {
// Select the "msg" type.
browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options")][1]/a[1]');
// Input the path in msg.
+ browser.clickWithWait('//*[contains(@class, "red-ui-typedInput-input")]/input');
+ browser.keys(Array('payload'.length).fill('Backspace'));
browser.setValue('//*[contains(@class, "red-ui-typedInput-input")]/input', complete);
} else {
// Select the "complete msg object" type.
diff --git a/test/editor/pageobjects/nodes/core/common/24-complete_page.js b/test/editor/pageobjects/nodes/core/common/24-complete_page.js
new file mode 100644
index 000000000..558ee709a
--- /dev/null
+++ b/test/editor/pageobjects/nodes/core/common/24-complete_page.js
@@ -0,0 +1,47 @@
+/**
+ * Copyright JS Foundation and other contributors, http://js.foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+var util = require('util');
+
+var nodePage = require('../../node_page');
+
+function completeNode(id) {
+ nodePage.call(this, id);
+}
+
+util.inherits(completeNode, nodePage);
+
+completeNode.prototype.setScope = function (scope) {
+ if (scope) {
+ browser.clickWithWait('//*[@id="node-input-complete-target-select"]');
+ browser.pause(1000);
+ if (Array.isArray(scope)) {
+ for (var i = 0; i < scope.length; i++) {
+ browser.moveToObject(scope[i].id);
+ browser.buttonDown();
+ browser.buttonUp();
+ }
+ } else {
+ browser.moveToObject(scope.id);
+ browser.buttonDown();
+ browser.buttonUp();
+ }
+ browser.clickWithWait('//*[contains(@class, "red-ui-notification")]/div/button[2]');
+ browser.pause(1000);
+ }
+}
+
+module.exports = completeNode;
diff --git a/test/editor/pageobjects/nodes/core/common/25-catch_page.js b/test/editor/pageobjects/nodes/core/common/25-catch_page.js
new file mode 100644
index 000000000..89bcb7f1e
--- /dev/null
+++ b/test/editor/pageobjects/nodes/core/common/25-catch_page.js
@@ -0,0 +1,48 @@
+/**
+ * Copyright JS Foundation and other contributors, http://js.foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+var util = require('util');
+
+var nodePage = require('../../node_page');
+
+function catchNode(id) {
+ nodePage.call(this, id);
+}
+
+util.inherits(catchNode, nodePage);
+
+catchNode.prototype.setScope = function (scope) {
+ if (scope) {
+ browser.selectWithWait('#node-input-scope-select', 'target');
+ browser.clickWithWait('//*[@id="node-input-catch-target-select"]');
+ browser.pause(1000);
+ if (Array.isArray(scope)) {
+ for (var i = 0; i < scope.length; i++) {
+ browser.moveToObject(scope[i].id);
+ browser.buttonDown();
+ browser.buttonUp();
+ }
+ } else {
+ browser.moveToObject(scope.id);
+ browser.buttonDown();
+ browser.buttonUp();
+ }
+ browser.clickWithWait('//*[contains(@class, "red-ui-notification")]/div/button[2]');
+ browser.pause(1000);
+ }
+}
+
+module.exports = catchNode;
diff --git a/test/editor/pageobjects/nodes/core/common/25-status_page.js b/test/editor/pageobjects/nodes/core/common/25-status_page.js
new file mode 100644
index 000000000..6de0fcde7
--- /dev/null
+++ b/test/editor/pageobjects/nodes/core/common/25-status_page.js
@@ -0,0 +1,48 @@
+/**
+ * Copyright JS Foundation and other contributors, http://js.foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+var util = require('util');
+
+var nodePage = require('../../node_page');
+
+function statusNode(id) {
+ nodePage.call(this, id);
+}
+
+util.inherits(statusNode, nodePage);
+
+statusNode.prototype.setScope = function (scope) {
+ if (scope) {
+ browser.selectWithWait('#node-input-scope-select', 'target');
+ browser.clickWithWait('//*[@id="node-input-status-target-select"]');
+ browser.pause(1000);
+ if (Array.isArray(scope)) {
+ for (var i = 0; i < scope.length; i++) {
+ browser.moveToObject(scope[i].id);
+ browser.buttonDown();
+ browser.buttonUp();
+ }
+ } else {
+ browser.moveToObject(scope.id);
+ browser.buttonDown();
+ browser.buttonUp();
+ }
+ browser.clickWithWait('//*[contains(@class, "red-ui-notification")]/div/button[2]');
+ browser.pause(1000);
+ }
+}
+
+module.exports = statusNode;
diff --git a/test/editor/pageobjects/nodes/core/common/90-comment_page.js b/test/editor/pageobjects/nodes/core/common/90-comment_page.js
new file mode 100644
index 000000000..2687dacfe
--- /dev/null
+++ b/test/editor/pageobjects/nodes/core/common/90-comment_page.js
@@ -0,0 +1,27 @@
+/**
+ * Copyright JS Foundation and other contributors, http://js.foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+var util = require('util');
+
+var nodePage = require('../../node_page');
+
+function commentNode(id) {
+ nodePage.call(this, id);
+}
+
+util.inherits(commentNode, nodePage);
+
+module.exports = commentNode;
diff --git a/test/editor/pageobjects/nodes/core/function/10-switch_page.js b/test/editor/pageobjects/nodes/core/function/10-switch_page.js
new file mode 100644
index 000000000..a04014063
--- /dev/null
+++ b/test/editor/pageobjects/nodes/core/function/10-switch_page.js
@@ -0,0 +1,234 @@
+/**
+ * Copyright JS Foundation and other contributors, http://js.foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+var util = require('util');
+
+var nodePage = require('../../node_page');
+
+function switchNode(id) {
+ nodePage.call(this, id);
+}
+
+util.inherits(switchNode, nodePage);
+
+var vtType = {
+ "msg": 1,
+ "flow": 2,
+ "global": 3,
+ "str": 4,
+ "num": 5,
+ "jsonata": 6,
+ "env": 7,
+ "prev": 8
+};
+
+function setT(t, index) {
+ browser.selectWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/select', t);
+}
+
+function setV(v, vt, index) {
+ if (vt) {
+ browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/button[1]');
+ browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + vtType[vt] + ']');
+ }
+ if (v) {
+ browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/div/input', v);
+ }
+}
+
+function setBetweenV(v, vt, v2, v2t, index) {
+ if (vt) {
+ browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div[2]/button[1]');
+ browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + vtType[vt] + ']');
+ }
+ if (v) {
+ browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div[2]/div[1]/input', v);
+ }
+ if (v2t) {
+ browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[3]/div/button[1]');
+ browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + vtType[v2t] + ']');
+ }
+ if (v2) {
+ browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[3]/div/div[1]/input', v2);
+ }
+}
+
+function setSequenceV(v, vt, index) {
+ var sType = {
+ "flow": 1,
+ "global": 2,
+ "num": 3,
+ "jsonata": 4,
+ "env": 5,
+ };
+
+ if (vt) {
+ browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div[2]/button[1]');
+ browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + sType[vt] + ']');
+ }
+ if (v) {
+ browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div[2]/div[1]/input', v);
+ }
+}
+
+switchNode.prototype.ruleEqual = function (v, vt, index) {
+ index = index || 1;
+ setT('eq', index);
+ setV(v, vt, index);
+}
+
+switchNode.prototype.ruleNotEqual = function (v, vt, index) {
+ index = index || 1;
+ setT('neq', index);
+ setV(v, vt, index);
+}
+
+switchNode.prototype.ruleLessThan = function (v, vt, index) {
+ index = index || 1;
+ setT('lt', index);
+ setV(v, vt, index);
+}
+
+switchNode.prototype.ruleLessThanOrEqual = function (v, vt, index) {
+ index = index || 1;
+ setT('lte', index);
+ setV(v, vt, index);
+}
+
+switchNode.prototype.ruleGreaterThan = function (v, vt, index) {
+ index = index || 1;
+ setT('gt', index);
+ setV(v, vt, index);
+}
+
+switchNode.prototype.ruleGreaterThanOrEqual = function (v, vt, index) {
+ index = index || 1;
+ setT('gte', index);
+ setV(v, vt, index);
+}
+
+switchNode.prototype.ruleHasKey = function (v, vt, index) {
+ index = index || 1;
+ setT('hask', index);
+ setV(v, vt, index);
+}
+
+switchNode.prototype.ruleIsBetween = function (v, vt, v2, v2t, index) {
+ index = index || 1;
+ setT('btwn', index);
+ setBetweenV(v, vt, v2, v2t, index);
+}
+
+switchNode.prototype.ruleContains = function (v, vt, index) {
+ index = index || 1;
+ setT('cont', index);
+ setV(v, vt, index);
+}
+
+switchNode.prototype.ruleMatchesRegex = function (v, vt, index) {
+ index = index || 1;
+ setT('regex', index);
+ setV(v, vt, index);
+}
+
+switchNode.prototype.ruleIsTrue = function (index) {
+ index = index || 1;
+ setT('true', index);
+}
+
+switchNode.prototype.ruleIsFalse = function (index) {
+ index = index || 1;
+ setT('false', index);
+}
+
+switchNode.prototype.ruleIsNull = function (index) {
+ index = index || 1;
+ setT('null', index);
+}
+
+switchNode.prototype.ruleIsNotNull = function (index) {
+ index = index || 1;
+ setT('nnull', index);
+}
+
+switchNode.prototype.ruleIsOfType = function (t, index) {
+ index = index || 1;
+ setT('istype', index);
+
+ var tType = {
+ "str": 1,
+ "num": 2,
+ "boolean": 3,
+ "array": 4,
+ "buffer": 5,
+ "object": 6,
+ "json": 7,
+ "undefined": 8,
+ "null": 9
+ };
+
+ if (t) {
+ browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div[2]/button[1]');
+ browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + tType[t] + ']');
+ }
+}
+
+switchNode.prototype.ruleIsEmpty = function (index) {
+ index = index || 1;
+ setT('empty', index);
+}
+
+switchNode.prototype.ruleIsNotEmpty = function (index) {
+ index = index || 1;
+ setT('nempty', index);
+}
+
+switchNode.prototype.ruleHead = function (v, vt, index) {
+ index = index || 1;
+ setT('head', index);
+ setSequenceV(v, vt, index);
+}
+
+switchNode.prototype.ruleIndexBetween = function (v, vt, v2, v2t, index) {
+ index = index || 1;
+ setT('index', index);
+ setBetweenV(v, vt, v2, v2t, index);
+}
+
+switchNode.prototype.ruleTail = function (v, vt, index) {
+ index = index || 1;
+ setT('tail', index);
+ setSequenceV(v, vt, index);
+}
+
+switchNode.prototype.ruleJSONataExp = function (v, index) {
+ index = index || 1;
+ setT('jsonata_exp', index);
+ if (v) {
+ browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div[2]/div[1]/input', v);
+ }
+}
+
+switchNode.prototype.ruleOtherwise = function (index) {
+ index = index || 1;
+ setT('else', index);
+}
+
+switchNode.prototype.addRule = function () {
+ browser.clickWithWait('//div[contains(@class, "red-ui-editableList")]/a');
+}
+
+module.exports = switchNode;
diff --git a/test/editor/pageobjects/nodes/core/function/15-change_page.js b/test/editor/pageobjects/nodes/core/function/15-change_page.js
index 8e72afe38..eb26f48aa 100644
--- a/test/editor/pageobjects/nodes/core/function/15-change_page.js
+++ b/test/editor/pageobjects/nodes/core/function/15-change_page.js
@@ -14,9 +14,9 @@
* limitations under the License.
**/
-var util = require("util");
+var util = require('util');
-var nodePage = require("../../node_page");
+var nodePage = require('../../node_page');
function changeNode(id) {
nodePage.call(this, id);
@@ -51,41 +51,82 @@ function setT(t, index) {
// It is better to create a function whose input value is the object type in the future,
changeNode.prototype.ruleSet = function (p, pt, to, tot, index) {
index = index || 1;
- setT("set", index);
+ setT('set', index);
if (pt) {
browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/button[1]');
- var num = 5 * (index - 1) + 1;
- var ptXPath = '//div[contains(@class, "red-ui-typedInput-options")][' + num + ']/a[' + ptType[pt] + ']';
- browser.clickWithWait(ptXPath);
+ browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + ptType[pt] + ']');
}
if (p) {
browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/div/input', p);
}
if (tot) {
browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[2]/div[2]/button[1]');
- var num = 5 * (index - 1) + 2;
- var totXPath = '//div[contains(@class, "red-ui-typedInput-options")][' + num + ']/a[' + totType[tot] + ']';
- browser.clickWithWait(totXPath);
+ browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + totType[tot] + ']');
}
if (to) {
browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[2]/div[2]/div/input', to);
}
}
-changeNode.prototype.ruleDelete = function (index) {
+changeNode.prototype.ruleChange = function (p, pt, from, fromt, to, tot, index) {
index = index || 1;
- setT("delete", index);
+ setT('change', index);
+ if (pt) {
+ browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/button[1]');
+ browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + ptType[pt] + ']');
+ }
+ if (p) {
+ browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/div/input', p);
+ }
+ if (fromt) {
+ browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[3]/div[1]/div[2]/button[1]');
+ browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + totType[pt] + ']');
+ }
+ if (from) {
+ browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[3]/div[1]/div[2]/div[1]/input', from);
+ }
+ if (tot) {
+ browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[3]/div[2]/div[2]/button[1]');
+ browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + totType[pt] + ']');
+ }
+ if (to) {
+ browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[3]/div[2]/div[2]/div[1]/input', to);
+ }
}
-changeNode.prototype.ruleMove = function (p, to, index) {
+changeNode.prototype.ruleDelete = function (p, pt, index) {
index = index || 1;
- setT("move", index);
- browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/div/input', p);
- browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[4]/div[2]/div/input', to);
+ setT('delete', index);
+ if (pt) {
+ browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/button[1]');
+ browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + ptType[pt] + ']');
+ }
+ if (p) {
+ browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/div/input', p);
+ }
+}
+
+changeNode.prototype.ruleMove = function (p, pt, to, tot, index) {
+ index = index || 1;
+ setT('move', index);
+ if (pt) {
+ browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/button[1]');
+ browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + ptType[pt] + ']');
+ }
+ if (p) {
+ browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/div/input', p);
+ }
+ if (tot) {
+ browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[4]/div[2]/button[1]');
+ browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + totType[pt] + ']');
+ }
+ if (to) {
+ browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[4]/div[2]/div/input', to);
+ }
}
changeNode.prototype.addRule = function () {
- browser.clickWithWait('//*[@id="dialog-form"]/div[3]/div/a');
+ browser.clickWithWait('//div[contains(@class, "red-ui-editableList")]/a');
}
module.exports = changeNode;
diff --git a/test/editor/pageobjects/nodes/core/network/10-mqttin_page.js b/test/editor/pageobjects/nodes/core/function/89-delay_page.js
similarity index 71%
rename from test/editor/pageobjects/nodes/core/network/10-mqttin_page.js
rename to test/editor/pageobjects/nodes/core/function/89-delay_page.js
index 31b909116..3604beb67 100644
--- a/test/editor/pageobjects/nodes/core/network/10-mqttin_page.js
+++ b/test/editor/pageobjects/nodes/core/function/89-delay_page.js
@@ -18,18 +18,14 @@ var util = require("util");
var nodePage = require("../../node_page");
-function mqttInNode(id) {
+function delayNode(id) {
nodePage.call(this, id);
}
-util.inherits(mqttInNode, nodePage);
+util.inherits(delayNode, nodePage);
-mqttInNode.prototype.setTopic = function (topic) {
- browser.setValue('#node-input-topic', topic);
+delayNode.prototype.setTimeout = function (timeout) {
+ browser.setValue('//*[@id="node-input-timeout"]', timeout);
}
-mqttInNode.prototype.setQoS = function (qos) {
- browser.selectWithWait('#node-input-qos', qos);
-}
-
-module.exports = mqttInNode;
+module.exports = delayNode;
diff --git a/test/editor/pageobjects/nodes/core/function/89-trigger_page.js b/test/editor/pageobjects/nodes/core/function/89-trigger_page.js
new file mode 100644
index 000000000..5d24de380
--- /dev/null
+++ b/test/editor/pageobjects/nodes/core/function/89-trigger_page.js
@@ -0,0 +1,83 @@
+/**
+ * Copyright JS Foundation and other contributors, http://js.foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+var util = require("util");
+
+var nodePage = require("../../node_page");
+
+function triggerNode(id) {
+ nodePage.call(this, id);
+}
+
+util.inherits(triggerNode, nodePage);
+
+triggerNode.prototype.setSend = function (send, sendt) {
+ var sendType = {
+ "flow": 1,
+ "global": 2,
+ "str": 3,
+ "num": 4,
+ "bool": 5,
+ "json": 6,
+ "bin": 7,
+ "date": 8,
+ "env": 9,
+ "pay": 10,
+ "nul": 11
+ };
+
+ if (sendt) {
+ browser.clickWithWait('//*[@id="dialog-form"]/div[1]/div/button[1]');
+ browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + sendType[sendt] + ']');
+ }
+ if (send) {
+ browser.setValue('//*[@id="dialog-form"]/div[1]/div/div[1]/input', send);
+ }
+}
+
+triggerNode.prototype.setDuration = function (duration, units) {
+ browser.setValue('//*[@id="node-input-duration"]', duration);
+ if (units) {
+ browser.selectWithWait('//*[@id="node-input-units"]', units);
+ }
+}
+
+triggerNode.prototype.setThenSend = function (thenSend, thenSendt) {
+ var thenSendType = {
+ "flow": 1,
+ "global": 2,
+ "str": 3,
+ "num": 4,
+ "bool": 5,
+ "json": 6,
+ "bin": 7,
+ "date": 8,
+ "env": 9,
+ "pay": 10,
+ "payl": 11,
+ "nul": 12
+ };
+
+ if (thenSendt) {
+ browser.clickWithWait('//*[@id="dialog-form"]/div[5]/div/button[1]');
+ browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + thenSendType[thenSendt] + ']');
+ }
+ if (thenSend) {
+ browser.setValue('//*[@id="dialog-form"]/div[5]/div/div[1]/input', thenSend);
+ }
+}
+
+module.exports = triggerNode;
diff --git a/test/editor/pageobjects/nodes/core/network/10-mqttout_page.js b/test/editor/pageobjects/nodes/core/function/90-exec_page.js
similarity index 66%
rename from test/editor/pageobjects/nodes/core/network/10-mqttout_page.js
rename to test/editor/pageobjects/nodes/core/function/90-exec_page.js
index 783d87b55..69b8b6c9a 100644
--- a/test/editor/pageobjects/nodes/core/network/10-mqttout_page.js
+++ b/test/editor/pageobjects/nodes/core/function/90-exec_page.js
@@ -18,18 +18,20 @@ var util = require("util");
var nodePage = require("../../node_page");
-function mqttOutNode(id) {
+function execNode(id) {
nodePage.call(this, id);
}
-util.inherits(mqttOutNode, nodePage);
+util.inherits(execNode, nodePage);
-mqttOutNode.prototype.setTopic = function(topic) {
- browser.setValue('#node-input-topic', topic);
+execNode.prototype.setCommand = function (command) {
+ browser.setValue('//*[@id="node-input-command"]', command);
}
-mqttOutNode.prototype.setRetain = function (retain) {
- browser.selectWithWait('#node-input-retain', retain);
+execNode.prototype.setAppend = function (checkbox) {
+ if (browser.isSelected('#node-input-addpay') !== checkbox) {
+ browser.click('#node-input-addpay');
+ }
}
-module.exports = mqttOutNode;
\ No newline at end of file
+module.exports = execNode;
diff --git a/test/editor/pageobjects/nodes/core/network/10-mqttconfig_page.js b/test/editor/pageobjects/nodes/core/network/10-mqtt_page.js
similarity index 52%
rename from test/editor/pageobjects/nodes/core/network/10-mqttconfig_page.js
rename to test/editor/pageobjects/nodes/core/network/10-mqtt_page.js
index c7cdc90c5..4bdd92336 100644
--- a/test/editor/pageobjects/nodes/core/network/10-mqttconfig_page.js
+++ b/test/editor/pageobjects/nodes/core/network/10-mqtt_page.js
@@ -14,27 +14,61 @@
* limitations under the License.
**/
-function setServer(broker, port) {
+var util = require("util");
+
+var nodePage = require("../../node_page");
+
+var mqttBrokerNode = {};
+
+mqttBrokerNode.setServer = function (broker, port) {
browser.setValue('#node-config-input-broker', broker);
port = port ? port : 1883;
browser.setValue('#node-config-input-port', port);
-}
+};
-function clickOk() {
+mqttBrokerNode.clickOk = function () {
browser.clickWithWait('#node-config-dialog-ok');
// Wait until an config dialog closes.
browser.waitForVisible('#node-config-dialog-ok', 10000, true);
-}
+};
-function edit() {
+mqttBrokerNode.edit = function () {
browser.waitForVisible('#node-input-lookup-broker');
browser.click('#node-input-lookup-broker');
// Wait until a config dialog opens.
browser.waitForVisible('#node-config-dialog-ok', 10000);
+};
+
+function mqttInNode(id) {
+ nodePage.call(this, id);
}
-module.exports = {
- setServer: setServer,
- clickOk: clickOk,
- edit: edit
+util.inherits(mqttInNode, nodePage);
+
+mqttInNode.prototype.setTopic = function (topic) {
+ browser.setValue('#node-input-topic', topic);
};
+
+mqttInNode.prototype.setQoS = function (qos) {
+ browser.selectWithWait('#node-input-qos', qos);
+};
+
+mqttInNode.prototype.mqttBrokerNode = mqttBrokerNode;
+module.exports.mqttInNode = mqttInNode;
+
+function mqttOutNode(id) {
+ nodePage.call(this, id);
+}
+
+util.inherits(mqttOutNode, nodePage);
+
+mqttOutNode.prototype.setTopic = function (topic) {
+ browser.setValue('#node-input-topic', topic);
+};
+
+mqttOutNode.prototype.setRetain = function (retain) {
+ browser.selectWithWait('#node-input-retain', retain);
+};
+
+mqttOutNode.prototype.mqttBrokerNode = mqttBrokerNode;
+module.exports.mqttOutNode = mqttOutNode;
diff --git a/test/editor/pageobjects/nodes/core/network/22-websocket_page.js b/test/editor/pageobjects/nodes/core/network/22-websocket_page.js
new file mode 100644
index 000000000..8f7dc261e
--- /dev/null
+++ b/test/editor/pageobjects/nodes/core/network/22-websocket_page.js
@@ -0,0 +1,93 @@
+/**
+ * Copyright JS Foundation and other contributors, http://js.foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+var util = require("util");
+
+var nodePage = require("../../node_page");
+
+var websocketListenerNode = {};
+
+websocketListenerNode.setPath = function (path) {
+ browser.setValue('#node-config-input-path', path);
+};
+
+websocketListenerNode.setSendReceive = function (wholemsg) {
+ browser.selectWithWait('#node-config-input-wholemsg', wholemsg);
+};
+
+websocketListenerNode.clickOk = function () {
+ browser.clickWithWait('#node-config-dialog-ok');
+ // Wait until an config dialog closes.
+ browser.waitForVisible('#node-config-dialog-ok', 10000, true);
+};
+
+websocketListenerNode.edit = function () {
+ browser.waitForVisible('#node-input-lookup-server');
+ browser.click('#node-input-lookup-server');
+ // Wait until a config dialog opens.
+ browser.waitForVisible('#node-config-dialog-ok', 10000);
+};
+
+var websocketClientNode = {};
+
+websocketClientNode.setPath = function (path) {
+ browser.setValue('#node-config-input-path', path);
+};
+
+websocketClientNode.setSendReceive = function (wholemsg) {
+ browser.selectWithWait('#node-config-input-wholemsg', wholemsg);
+};
+
+websocketClientNode.clickOk = function () {
+ browser.clickWithWait('#node-config-dialog-ok');
+ // Wait until an config dialog closes.
+ browser.waitForVisible('#node-config-dialog-ok', 10000, true);
+};
+
+websocketClientNode.edit = function () {
+ browser.waitForVisible('#node-input-lookup-client');
+ browser.click('#node-input-lookup-client');
+ // Wait until a config dialog opens.
+ browser.waitForVisible('#node-config-dialog-ok', 10000);
+};
+
+function websocketInNode(id) {
+ nodePage.call(this, id);
+}
+
+util.inherits(websocketInNode, nodePage);
+
+websocketInNode.prototype.setType = function (type) {
+ browser.selectWithWait('#node-input-mode', type);
+};
+
+websocketInNode.prototype.websocketListenerNode = websocketListenerNode;
+websocketInNode.prototype.websocketClientNode = websocketClientNode;
+module.exports.websocketInNode = websocketInNode;
+
+function websocketOutNode(id) {
+ nodePage.call(this, id);
+}
+
+util.inherits(websocketOutNode, nodePage);
+
+websocketOutNode.prototype.setType = function (type) {
+ browser.selectWithWait('#node-input-mode', type);
+};
+
+websocketOutNode.prototype.websocketListenerNode = websocketListenerNode;
+websocketOutNode.prototype.websocketClientNode = websocketClientNode;
+module.exports.websocketOutNode = websocketOutNode;
diff --git a/test/editor/pageobjects/nodes/core/parsers/70-CSV_page.js b/test/editor/pageobjects/nodes/core/parsers/70-CSV_page.js
new file mode 100644
index 000000000..e4bc9502c
--- /dev/null
+++ b/test/editor/pageobjects/nodes/core/parsers/70-CSV_page.js
@@ -0,0 +1,51 @@
+/**
+ * Copyright JS Foundation and other contributors, http://js.foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+var util = require('util');
+
+var nodePage = require('../../node_page');
+
+function csvNode(id) {
+ nodePage.call(this, id);
+}
+
+util.inherits(csvNode, nodePage);
+
+csvNode.prototype.setColumns = function (columns) {
+ browser.setValue('#node-input-temp', columns);
+}
+
+csvNode.prototype.setSkipLines = function (skip) {
+ browser.setValue('#node-input-skip', skip);
+}
+
+csvNode.prototype.setFirstRow4Names = function (checkbox) {
+ if (browser.isSelected('#node-input-hdrin') !== checkbox) {
+ browser.click('#node-input-hdrin');
+ }
+}
+
+csvNode.prototype.setOutput = function (output) {
+ browser.selectWithWait('#node-input-multi', output);
+}
+
+csvNode.prototype.setIncludeRow = function (checkbox) {
+ if (browser.isSelected('#node-input-hdrout') !== checkbox) {
+ browser.click('#node-input-hdrout');
+ }
+}
+
+module.exports = csvNode;
diff --git a/test/editor/pageobjects/nodes/core/parsers/70-HTML_page.js b/test/editor/pageobjects/nodes/core/parsers/70-HTML_page.js
index 9c89bc8e2..243e4c905 100644
--- a/test/editor/pageobjects/nodes/core/parsers/70-HTML_page.js
+++ b/test/editor/pageobjects/nodes/core/parsers/70-HTML_page.js
@@ -14,9 +14,9 @@
* limitations under the License.
**/
-var util = require("util");
+var util = require('util');
-var nodePage = require("../../node_page");
+var nodePage = require('../../node_page');
function htmlNode(id) {
nodePage.call(this, id);
@@ -24,7 +24,7 @@ function htmlNode(id) {
util.inherits(htmlNode, nodePage);
-htmlNode.prototype.setSelector = function(tag) {
+htmlNode.prototype.setSelector = function (tag) {
browser.setValue('#node-input-tag', tag);
}
diff --git a/test/editor/pageobjects/nodes/core/parsers/70-JSON_page.js b/test/editor/pageobjects/nodes/core/parsers/70-JSON_page.js
index 10a7e648f..e0b31dd36 100644
--- a/test/editor/pageobjects/nodes/core/parsers/70-JSON_page.js
+++ b/test/editor/pageobjects/nodes/core/parsers/70-JSON_page.js
@@ -14,9 +14,9 @@
* limitations under the License.
**/
-var util = require("util");
+var util = require('util');
-var nodePage = require("../../node_page");
+var nodePage = require('../../node_page');
function jsonNode(id) {
nodePage.call(this, id);
@@ -28,8 +28,8 @@ jsonNode.prototype.setAction = function (action) {
browser.setValue('node-input-action', action);
}
-jsonNode.prototype.setProperty = function(property) {
- browser.setValue('//*[@id="dialog-form"]/div[2]/div/div/input', property);
+jsonNode.prototype.setProperty = function (property) {
+ browser.setValue('//*[contains(@class, "red-ui-typedInput-container")]/div[1]/input', property);
}
module.exports = jsonNode;
diff --git a/test/editor/pageobjects/nodes/core/parsers/70-XML_page.js b/test/editor/pageobjects/nodes/core/parsers/70-XML_page.js
new file mode 100644
index 000000000..696ec59cb
--- /dev/null
+++ b/test/editor/pageobjects/nodes/core/parsers/70-XML_page.js
@@ -0,0 +1,35 @@
+/**
+ * Copyright JS Foundation and other contributors, http://js.foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+var util = require('util');
+
+var nodePage = require('../../node_page');
+
+function xmlNode(id) {
+ nodePage.call(this, id);
+}
+
+util.inherits(xmlNode, nodePage);
+
+xmlNode.prototype.setAction = function (action) {
+ browser.setValue('node-input-action', action);
+}
+
+xmlNode.prototype.setProperty = function (property) {
+ browser.setValue('//*[contains(@class, "red-ui-typedInput-container")]/div[1]/input', property);
+}
+
+module.exports = xmlNode;
diff --git a/test/editor/pageobjects/nodes/core/parsers/70-YAML_page.js b/test/editor/pageobjects/nodes/core/parsers/70-YAML_page.js
new file mode 100644
index 000000000..1002f3eb4
--- /dev/null
+++ b/test/editor/pageobjects/nodes/core/parsers/70-YAML_page.js
@@ -0,0 +1,35 @@
+/**
+ * Copyright JS Foundation and other contributors, http://js.foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+var util = require('util');
+
+var nodePage = require('../../node_page');
+
+function yamlNode(id) {
+ nodePage.call(this, id);
+}
+
+util.inherits(yamlNode, nodePage);
+
+yamlNode.prototype.setAction = function (action) {
+ browser.setValue('node-input-action', action);
+}
+
+yamlNode.prototype.setProperty = function (property) {
+ browser.setValue('//*[contains(@class, "red-ui-typedInput-container")]/div[1]/input', property);
+}
+
+module.exports = yamlNode;
diff --git a/test/editor/pageobjects/nodes/core/sequence/17-split_page.js b/test/editor/pageobjects/nodes/core/sequence/17-split_page.js
new file mode 100644
index 000000000..8fc32ab1e
--- /dev/null
+++ b/test/editor/pageobjects/nodes/core/sequence/17-split_page.js
@@ -0,0 +1,47 @@
+/**
+ * Copyright JS Foundation and other contributors, http://js.foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+var util = require('util');
+
+var nodePage = require('../../node_page');
+
+function splitNode(id) {
+ nodePage.call(this, id);
+}
+
+util.inherits(splitNode, nodePage);
+
+splitNode.prototype.setSplitUsing = function (splt, spltt) {
+ var spltType = {
+ "str": 1,
+ "bin": 2,
+ "len": 3
+ };
+
+ browser.clickWithWait('//*[@id="dialog-form"]/div[3]/div/button[1]');
+ browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + spltType[spltt] + ']');
+ browser.setValue('//*[@id="dialog-form"]/div[3]/div/div[1]/input', splt);
+};
+
+module.exports.splitNode = splitNode;
+
+function joinNode(id) {
+ nodePage.call(this, id);
+}
+
+util.inherits(joinNode, nodePage);
+
+module.exports.joinNode = joinNode;
diff --git a/test/editor/pageobjects/nodes/core/sequence/19-batch_page.js b/test/editor/pageobjects/nodes/core/sequence/19-batch_page.js
new file mode 100644
index 000000000..0d7b13028
--- /dev/null
+++ b/test/editor/pageobjects/nodes/core/sequence/19-batch_page.js
@@ -0,0 +1,39 @@
+/**
+ * Copyright JS Foundation and other contributors, http://js.foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+var util = require('util');
+
+var nodePage = require('../../node_page');
+
+function batchNode(id) {
+ nodePage.call(this, id);
+}
+
+batchNode.prototype.setMode = function (mode) {
+ browser.selectWithWait('#node-input-mode', mode);
+}
+
+batchNode.prototype.setCount = function (count) {
+ browser.setValue('#node-input-count', count);
+}
+
+batchNode.prototype.setOverlap = function (overlap) {
+ browser.setValue('#node-input-overlap', overlap);
+}
+
+util.inherits(batchNode, nodePage);
+
+module.exports = batchNode;
diff --git a/test/editor/pageobjects/nodes/node_page.js b/test/editor/pageobjects/nodes/node_page.js
index 5250250e7..03e734cab 100644
--- a/test/editor/pageobjects/nodes/node_page.js
+++ b/test/editor/pageobjects/nodes/node_page.js
@@ -35,10 +35,11 @@ Node.prototype.clickOk = function () {
browser.pause(50);
}
-Node.prototype.connect = function (targetNode) {
- var outputPort = this.id + '/*[@class="red-ui-flow-port-output"]';
+Node.prototype.connect = function (targetNode, port) {
+ port = port || 1;
+ var outputPort = this.id + '/*[@class="red-ui-flow-port-output"][' + port + ']';
var inputPort = targetNode.id + '/*[@class="red-ui-flow-port-input"]';
- browser.dragAndDrop(outputPort, inputPort)
+ browser.dragAndDrop(outputPort, inputPort);
}
Node.prototype.clickLeftButton = function () {
diff --git a/test/editor/pageobjects/nodes/nodefactory_page.js b/test/editor/pageobjects/nodes/nodefactory_page.js
index 744c1570d..008ecc625 100644
--- a/test/editor/pageobjects/nodes/nodefactory_page.js
+++ b/test/editor/pageobjects/nodes/nodefactory_page.js
@@ -16,37 +16,70 @@
var injectNode = require('./core/common/20-inject_page');
var debugNode = require('./core/common/21-debug_page');
+var completeNode = require('./core/common/24-complete_page');
+var catchNode = require('./core/common/25-catch_page');
+var statusNode = require('./core/common/25-status_page');
+var commentNode = require('./core/common/90-comment_page');
var functionNode = require('./core/function/10-function_page');
+var switchNode = require('./core/function/10-switch_page');
var changeNode = require('./core/function/15-change_page');
var rangeNode = require('./core/function/16-range_page');
var templateNode = require('./core/function/80-template_page');
-var mqttInNode = require('./core/network/10-mqttin_page');
-var mqttOutNode = require('./core/network/10-mqttout_page');
+var delayNode = require('./core/function/89-delay_page');
+var triggerNode = require('./core/function/89-trigger_page');
+var execNode = require('./core/function/90-exec_page');
+var mqttInNode = require('./core/network/10-mqtt_page').mqttInNode;
+var mqttOutNode = require('./core/network/10-mqtt_page').mqttOutNode;
var httpInNode = require('./core/network/21-httpin_page');
var httpResponseNode = require('./core/network/21-httpresponse_page');
var httpRequestNode = require('./core/network/21-httprequest_page');
+var websocketInNode = require('./core/network/22-websocket_page').websocketInNode;
+var websocketOutNode = require('./core/network/22-websocket_page').websocketOutNode;
+var splitNode = require('./core/sequence/17-split_page').splitNode;
+var joinNode = require('./core/sequence/17-split_page').joinNode;
+var batchNode = require('./core/sequence/19-batch_page');
+var csvNode = require('./core/parsers/70-CSV_page');
var htmlNode = require('./core/parsers/70-HTML_page');
var jsonNode = require('./core/parsers/70-JSON_page');
+var xmlNode = require('./core/parsers/70-XML_page');
+var yamlNode = require('./core/parsers/70-YAML_page');
var fileInNode = require('./core/storage/10-filein_page');
var nodeCatalog = {
// common
"inject": injectNode,
"debug": debugNode,
+ "complete": completeNode,
+ "catch": catchNode,
+ "status": statusNode,
+ "comment": commentNode,
// function
"function": functionNode,
+ "switch": switchNode,
"change": changeNode,
"range": rangeNode,
"template": templateNode,
+ "delay": delayNode,
+ "trigger": triggerNode,
+ "exec": execNode,
// network
"mqttIn": mqttInNode,
"mqttOut": mqttOutNode,
"httpIn": httpInNode,
"httpResponse": httpResponseNode,
"httpRequest": httpRequestNode,
+ "websocketIn": websocketInNode,
+ "websocketOut": websocketOutNode,
+ // sequence
+ "split": splitNode,
+ "join": joinNode,
+ "batch": batchNode,
// parser
+ "csv": csvNode,
"html": htmlNode,
"json": jsonNode,
+ "xml": xmlNode,
+ "yaml": yamlNode,
// storage
"fileIn": fileInNode
};
diff --git a/test/editor/pageobjects/util/key_page.js b/test/editor/pageobjects/util/key_page.js
index 509af9e22..497a8a141 100644
--- a/test/editor/pageobjects/util/key_page.js
+++ b/test/editor/pageobjects/util/key_page.js
@@ -27,6 +27,12 @@ var shortCutKeyMapForMac = {
};
function getShortCutKey(type) {
+ if (process.env.BROWSERSTACK) {
+ if (browser.desiredCapabilities.os === 'OS X') {
+ return shortCutKeyMapForMac[type];
+ }
+ return shortCutKeyMap[type];
+ }
if (os.type() === 'Darwin') {
return shortCutKeyMapForMac[type];
}
diff --git a/test/editor/pageobjects/util/util_page.js b/test/editor/pageobjects/util/util_page.js
index 02508c831..3a764eb93 100644
--- a/test/editor/pageobjects/util/util_page.js
+++ b/test/editor/pageobjects/util/util_page.js
@@ -70,7 +70,7 @@ function init() {
var ret = repeatUntilSuccess(function(args) {
return browser.selectByValue(args[0], args[1]);
- }, [selector, value]);
+ }, [selector, value.toString()]);
return ret;
} catch (e) {
console.trace();
diff --git a/test/editor/specs/scenario/cookbook_dataformats_uispec.js b/test/editor/specs/scenario/cookbook_dataformats_uispec.js
new file mode 100644
index 000000000..052481e91
--- /dev/null
+++ b/test/editor/specs/scenario/cookbook_dataformats_uispec.js
@@ -0,0 +1,364 @@
+/**
+ * Copyright JS Foundation and other contributors, http://js.foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+var when = require('when');
+var should = require('should');
+var fs = require('fs-extra');
+
+var helper = require('../../editor_helper');
+var debugTab = require('../../pageobjects/editor/debugTab_page');
+var workspace = require('../../pageobjects/editor/workspace_page');
+var specUtil = require('../../pageobjects/util/spec_util_page');
+
+var httpNodeRoot = '/api';
+
+// https://cookbook.nodered.org/
+describe('cookbook', function () {
+ beforeEach(function () {
+ workspace.init();
+ });
+
+ before(function () {
+ helper.startServer();
+ });
+
+ after(function () {
+ helper.stopServer();
+ });
+
+ describe('working with data formats', function () {
+ it('convert to/from JSON', function () {
+ var injectNode1 = workspace.addNode('inject');
+ var jsonNode1 = workspace.addNode('json');
+ var debugNode1 = workspace.addNode('debug');
+
+ injectNode1.edit();
+ injectNode1.setPayload('str', '{"a":1}');
+ injectNode1.clickOk();
+
+ jsonNode1.edit();
+ jsonNode1.setProperty('payload');
+ jsonNode1.clickOk();
+
+ injectNode1.connect(jsonNode1);
+ jsonNode1.connect(debugNode1);
+
+ var injectNode2 = workspace.addNode('inject');
+ var jsonNode2 = workspace.addNode('json');
+ var debugNode2 = workspace.addNode('debug');
+
+ injectNode2.edit();
+ injectNode2.setPayload('json', '{"a":1}');
+ injectNode2.clickOk();
+
+ jsonNode2.edit();
+ jsonNode2.setProperty('payload');
+ jsonNode2.clickOk();
+
+ injectNode2.connect(jsonNode2);
+ jsonNode2.connect(debugNode2);
+
+ workspace.deploy();
+
+ debugTab.open();
+ injectNode1.clickLeftButton();
+ debugTab.getMessage().should.eql('1');
+ debugTab.clearMessage();
+ injectNode2.clickLeftButton();
+ debugTab.getMessage().should.eql('"{"a":1}"');
+ });
+
+ it('convert to/from XML', function () {
+ var injectNode1 = workspace.addNode('inject', 0);
+ var templateNode1 = workspace.addNode('template', 200);
+ var xmlNode1 = workspace.addNode('xml', 400);
+ var debugNode1 = workspace.addNode('debug', 600);
+
+ injectNode1.edit();
+ injectNode1.setPayload('str', '{"a":1}');
+ injectNode1.clickOk();
+
+ templateNode1.edit();
+ templateNode1.setFormat('text');
+ templateNode1.setSyntax('plain');
+ templateNode1.setTemplate(''
+ + ' Nick '
+ + ' Dave '
+ + ' Reminder '
+ + ' Update the website'
+ + ' ');
+ templateNode1.clickOk();
+
+ xmlNode1.edit();
+ xmlNode1.setProperty('payload');
+ xmlNode1.clickOk();
+
+ injectNode1.connect(templateNode1);
+ templateNode1.connect(xmlNode1);
+ xmlNode1.connect(debugNode1);
+
+ var injectNode2 = workspace.addNode('inject');
+ var xmlNode2 = workspace.addNode('xml');
+ var debugNode2 = workspace.addNode('debug');
+
+ injectNode2.edit();
+ injectNode2.setPayload('json', '{'
+ + ' "note": {'
+ + ' "$": { "priority": "high" },'
+ + ' "to": [ "Nick" ],'
+ + ' "from": [ "Dave" ],'
+ + ' "heading": [ "Reminder" ],'
+ + ' "body": [ "Update the website" ]'
+ + ' }'
+ + '}');
+ injectNode2.clickOk();
+
+ xmlNode2.edit();
+ xmlNode2.setProperty('payload');
+ xmlNode2.clickOk();
+
+ injectNode2.connect(xmlNode2);
+ xmlNode2.connect(debugNode2);
+
+ workspace.deploy();
+
+ debugTab.open();
+ injectNode1.clickLeftButton();
+ debugTab.getMessage().should.eql('object');
+ debugTab.clearMessage();
+ injectNode2.clickLeftButton();
+ debugTab.getMessage().should.eql('"'
+ + ''
+ + 'Nick '
+ + 'Dave '
+ + 'Reminder '
+ + 'Update the website'
+ + ' "');
+ });
+
+ it('convert to/from YAML', function () {
+ var injectNode1 = workspace.addNode('inject', 0);
+ var templateNode1 = workspace.addNode('template', 200);
+ var yamlNode1 = workspace.addNode('yaml', 400);
+ var debugNode1 = workspace.addNode('debug', 600);
+
+ injectNode1.edit();
+ injectNode1.setPayload('str', '{"a":1}');
+ injectNode1.clickOk();
+
+ templateNode1.edit();
+ templateNode1.setFormat('yaml');
+ templateNode1.setSyntax('plain');
+ templateNode1.setTemplate('a: 1\n'
+ + 'b:\n'
+ + ' - 1\n'
+ + '- 2\n'
+ + '- 3');
+ templateNode1.clickOk();
+
+ yamlNode1.edit();
+ yamlNode1.setProperty('payload');
+ yamlNode1.clickOk();
+
+ injectNode1.connect(templateNode1);
+ templateNode1.connect(yamlNode1);
+ yamlNode1.connect(debugNode1);
+
+ var injectNode2 = workspace.addNode('inject');
+ var yamlNode2 = workspace.addNode('yaml');
+ var debugNode2 = workspace.addNode('debug');
+
+ injectNode2.edit();
+ injectNode2.setPayload('json', '{"a":1, "b":[1,2,3]}');
+ injectNode2.clickOk();
+
+ yamlNode2.edit();
+ yamlNode2.setProperty('payload');
+ yamlNode2.clickOk();
+
+ injectNode2.connect(yamlNode2);
+ yamlNode2.connect(debugNode2);
+
+ workspace.deploy();
+
+ debugTab.open();
+ injectNode1.clickLeftButton();
+ debugTab.getMessage().should.eql([ '1', 'array[3]' ]);
+ debugTab.clearMessage();
+ injectNode2.clickLeftButton();
+ debugTab.getMessage().should.eql('"a: 1↵b:↵ - 1↵ - 2↵ - 3↵"');
+ });
+
+ it('generate CSV output', function () {
+ var injectNode1 = workspace.addNode('inject', 0);
+ var changeNode1 = workspace.addNode('change', 200);
+ var csvNode1 = workspace.addNode('csv', 400);
+ var debugNode1 = workspace.addNode('debug', 600);
+
+ changeNode1.edit();
+ changeNode1.ruleSet('payload', 'msg', '{'
+ + ' "a": $floor(100*$random()),'
+ + ' "b": $floor(100*$random()),'
+ + ' "c": $floor(100*$random())'
+ + '}', 'jsonata');
+ changeNode1.clickOk();
+
+ csvNode1.edit();
+ csvNode1.setColumns('a,b,c');
+ csvNode1.clickOk();
+
+ injectNode1.connect(changeNode1);
+ changeNode1.connect(csvNode1);
+ csvNode1.connect(debugNode1);
+
+ var injectNode2 = workspace.addNode('inject', 0, 80);
+ var changeNode2 = workspace.addNode('change', 200, 80);
+ var csvNode2 = workspace.addNode('csv', 400, 80);
+ var debugNode2 = workspace.addNode('debug', 600, 80);
+
+ changeNode2.edit();
+ changeNode2.ruleSet('payload', 'msg', '['
+ + ' {'
+ + ' "a": $floor(100*$random()),'
+ + ' "b": $floor(100*$random()),'
+ + ' "c": $floor(100*$random())'
+ + ' }, {'
+ + ' "a": $floor(100*$random()),'
+ + ' "b": $floor(100*$random()),'
+ + ' "c": $floor(100*$random())'
+ + ' }, {'
+ + ' "a": $floor(100*$random()),'
+ + ' "b": $floor(100*$random()),'
+ + ' "c": $floor(100*$random())'
+ + ' }, {'
+ + ' "a": $floor(100*$random()),'
+ + ' "b": $floor(100*$random()),'
+ + ' "c": $floor(100*$random())'
+ + ' }'
+ + ']', 'jsonata');
+ changeNode2.clickOk();
+
+ csvNode2.edit();
+ csvNode2.setColumns('a,b,c');
+ csvNode2.setIncludeRow(true);
+ csvNode2.clickOk();
+
+ injectNode2.connect(changeNode2);
+ changeNode2.connect(csvNode2);
+ csvNode2.connect(debugNode2);
+
+ workspace.deploy();
+
+ debugTab.open();
+ injectNode1.clickLeftButton();
+ debugTab.getMessage().should.match(/^"([1-9]?[0-9],){2}[1-9]?[0-9]↵"$/);
+ debugTab.clearMessage();
+ injectNode2.clickLeftButton();
+ debugTab.getMessage().should.match(/^"a,b,c↵(([1-9]?[0-9],){2}[1-9]?[0-9]↵){4}"$/);
+ });
+
+ it('parse CSV input', function () {
+ var injectNode = workspace.addNode('inject');
+ var templateNode = workspace.addNode('template');
+ var csvNode = workspace.addNode('csv');
+ var debugNode = workspace.addNode('debug');
+
+ templateNode.edit();
+ templateNode.setFormat('handlebars');
+ templateNode.setSyntax('mustache');
+ templateNode.setTemplate('# This is some random data\n'
+ + 'a,b,c\n'
+ + '80,18,2\n'
+ + '52,36,10\n'
+ + '91,18,61\n'
+ + '32,47,65');
+ templateNode.clickOk();
+
+ csvNode.edit();
+ csvNode.setSkipLines(1);
+ csvNode.setFirstRow4Names(true);
+ csvNode.setOutput('mult');
+ csvNode.clickOk();
+
+ injectNode.connect(templateNode);
+ templateNode.connect(csvNode);
+ csvNode.connect(debugNode);
+
+ workspace.deploy();
+
+ debugTab.open();
+ injectNode.clickLeftButton();
+ debugTab.getMessage().should.eql([ 'object', 'object', 'object', 'object' ]);
+ });
+
+ it('simple GET request', function () {
+ var injectNode = workspace.addNode('inject');
+ var httpRequestNode = workspace.addNode('httpRequest');
+ var htmlNode = workspace.addNode('html');
+ var debugNode = workspace.addNode('debug');
+
+ httpRequestNode.edit();
+ httpRequestNode.setMethod('GET');
+ httpRequestNode.setUrl('https://nodered.org');
+ httpRequestNode.clickOk();
+
+ htmlNode.edit();
+ htmlNode.setSelector('.node-red-latest-version');
+ htmlNode.clickOk();
+
+ injectNode.connect(httpRequestNode);
+ httpRequestNode.connect(htmlNode);
+ htmlNode.connect(debugNode);
+
+ workspace.deploy();
+
+ debugTab.open();
+ injectNode.clickLeftButton();
+ debugTab.getMessage().should.match(/^"v[0-9]+\.[0-9]+\.[0-9]"$/);
+ });
+
+ it('split text into one message per line', function () {
+ var injectNode = workspace.addNode('inject');
+ var templateNode = workspace.addNode('template');
+ var splitNode = workspace.addNode('split');
+ var changeNode = workspace.addNode('change');
+ var joinNode = workspace.addNode('join');
+ var debugNode = workspace.addNode('debug');
+
+ templateNode.edit();
+ templateNode.setFormat('handlebars');
+ templateNode.setSyntax('mustache');
+ templateNode.setTemplate('one\ntwo\nthree\nfour\nfive');
+ templateNode.clickOk();
+
+ changeNode.edit();
+ changeNode.ruleSet('payload', 'msg', '(parts.index+1) & ": " & payload', 'jsonata');
+ changeNode.clickOk();
+
+ injectNode.connect(templateNode);
+ templateNode.connect(splitNode);
+ splitNode.connect(changeNode);
+ changeNode.connect(joinNode);
+ joinNode.connect(debugNode);
+
+ workspace.deploy();
+
+ debugTab.open();
+ injectNode.clickLeftButton();
+ debugTab.getMessage().should.eql('"1: one↵2: two↵3: three↵4: four↵5: five"');
+ });
+ });
+});
diff --git a/test/editor/specs/scenario/cookbook_errorhandling_uispec.js b/test/editor/specs/scenario/cookbook_errorhandling_uispec.js
new file mode 100644
index 000000000..5285c5c0f
--- /dev/null
+++ b/test/editor/specs/scenario/cookbook_errorhandling_uispec.js
@@ -0,0 +1,74 @@
+/**
+ * Copyright JS Foundation and other contributors, http://js.foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+var when = require('when');
+var should = require('should');
+var fs = require('fs-extra');
+
+var helper = require('../../editor_helper');
+var debugTab = require('../../pageobjects/editor/debugTab_page');
+var workspace = require('../../pageobjects/editor/workspace_page');
+var specUtil = require('../../pageobjects/util/spec_util_page');
+
+var httpNodeRoot = '/api';
+
+// https://cookbook.nodered.org/
+describe('cookbook', function () {
+ beforeEach(function () {
+ workspace.init();
+ });
+
+ before(function () {
+ helper.startServer();
+ });
+
+ after(function () {
+ helper.stopServer();
+ });
+
+ describe('messages', function () {
+ it('trigger a flow when a node throws an error', function () {
+ var injectNode = workspace.addNode('inject');
+ var functionNode = workspace.addNode('function');
+ var catchNode = workspace.addNode('catch', 0 , 80);
+ var debugNode = workspace.addNode('debug');
+
+ functionNode.edit();
+ functionNode.setFunction('node.error("an example error", msg);');
+ functionNode.clickOk();
+
+ catchNode.edit();
+ catchNode.setScope(functionNode);
+ catchNode.clickOk();
+
+ debugNode.edit();
+ debugNode.setOutput('error');
+ debugNode.clickOk();
+
+ injectNode.connect(functionNode);
+ catchNode.connect(debugNode);
+
+ workspace.deploy();
+
+ debugTab.open();
+ injectNode.clickLeftButton();
+ debugTab.getMessage().should.eql(['"an example error"', 'function']);
+ });
+
+ // skip this case since the flow outputs random results.
+ it.skip('automatically retry an action after an error');
+ });
+});
diff --git a/test/editor/specs/scenario/cookbook_flowcontrol_uispec.js b/test/editor/specs/scenario/cookbook_flowcontrol_uispec.js
new file mode 100644
index 000000000..724d1c56f
--- /dev/null
+++ b/test/editor/specs/scenario/cookbook_flowcontrol_uispec.js
@@ -0,0 +1,81 @@
+/**
+ * Copyright JS Foundation and other contributors, http://js.foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+var when = require('when');
+var should = require('should');
+var fs = require('fs-extra');
+
+var helper = require('../../editor_helper');
+var debugTab = require('../../pageobjects/editor/debugTab_page');
+var workspace = require('../../pageobjects/editor/workspace_page');
+var specUtil = require('../../pageobjects/util/spec_util_page');
+
+var httpNodeRoot = '/api';
+
+// https://cookbook.nodered.org/
+describe('cookbook', function () {
+ beforeEach(function () {
+ workspace.init();
+ });
+
+ before(function () {
+ helper.startServer();
+ });
+
+ after(function () {
+ helper.stopServer();
+ });
+
+ describe('flow control', function () {
+ it('trigger a flow whenever Node-RED starts', function () {
+ var injectNode = workspace.addNode('inject');
+ var debugNode = workspace.addNode('debug');
+
+ injectNode.edit();
+ injectNode.setPayload('str', 'Started!');
+ injectNode.setOnce(true);
+ injectNode.clickOk();
+ injectNode.connect(debugNode);
+
+ debugTab.open();
+ workspace.deploy();
+ debugTab.getMessage().should.eql('"Started!"');
+ });
+
+ it('trigger a flow at regular intervals', function () {
+ var injectNode = workspace.addNode('inject');
+ var debugNode = workspace.addNode('debug');
+
+ injectNode.edit();
+ injectNode.setRepeat('interval');
+ injectNode.setRepeatInterval(1);
+ injectNode.clickOk();
+ injectNode.connect(debugNode);
+
+ workspace.deploy();
+
+ debugTab.open();
+ specUtil.pause(1000);
+ var t1 = Number(debugTab.getMessage(1));
+ t1.should.within(1500000000000, 3000000000000);
+ specUtil.pause(1000);
+ debugTab.getMessage(2).should.within(t1 + 900, 3000000000000);
+ });
+
+ // skip this case since it needs up to one minite.
+ it.skip('trigger a flow at a specific time');
+ });
+});
diff --git a/test/editor/specs/scenario/cookbook_endpoint_uispec.js b/test/editor/specs/scenario/cookbook_httpendpoints_uispec.js
similarity index 98%
rename from test/editor/specs/scenario/cookbook_endpoint_uispec.js
rename to test/editor/specs/scenario/cookbook_httpendpoints_uispec.js
index 9a2ed0710..134498370 100644
--- a/test/editor/specs/scenario/cookbook_endpoint_uispec.js
+++ b/test/editor/specs/scenario/cookbook_httpendpoints_uispec.js
@@ -23,16 +23,16 @@ var workspace = require('../../pageobjects/editor/workspace_page');
var httpNodeRoot = "/api";
// https://cookbook.nodered.org/
-describe('cookbook', function() {
- beforeEach(function() {
+describe('cookbook', function () {
+ beforeEach(function () {
workspace.init();
});
- before(function() {
+ before(function () {
helper.startServer();
});
- after(function() {
+ after(function () {
helper.stopServer();
});
@@ -359,7 +359,7 @@ describe('cookbook', function() {
debugTab.getMessage().indexOf('Text file').should.not.eql(-1);
});
- it('post raw data to a flow', function() {
+ it('post raw data to a flow', function () {
var httpInNode = workspace.addNode("httpIn");
var templateNode = workspace.addNode("template");
var httpResponseNode = workspace.addNode("httpResponse");
@@ -383,7 +383,7 @@ describe('cookbook', function() {
var httpRequestNode = workspace.addNode("httpRequest");
var debugNode = workspace.addNode("debug");
- injectNode.edit()
+ injectNode.edit();
injectNode.setPayload("str", "Nick");
injectNode.clickOk();
@@ -427,7 +427,7 @@ describe('cookbook', function() {
var httpRequestNode = workspace.addNode("httpRequest");
var debugNode = workspace.addNode("debug");
- injectNode.edit()
+ injectNode.edit();
injectNode.setPayload("str", "name=Nick");
injectNode.clickOk();
@@ -451,7 +451,7 @@ describe('cookbook', function() {
debugTab.getMessage().indexOf('Hello Nick!').should.not.eql(-1);
});
- it('post JSON data to a flow', function() {
+ it('post JSON data to a flow', function () {
var httpInNode = workspace.addNode("httpIn");
var templateNode = workspace.addNode("template");
var httpResponseNode = workspace.addNode("httpResponse");
@@ -476,7 +476,7 @@ describe('cookbook', function() {
var httpRequestNode = workspace.addNode("httpRequest");
var debugNode = workspace.addNode("debug");
- injectNode.edit()
+ injectNode.edit();
injectNode.setPayload("json", '{"name":"Nick"}');
injectNode.clickOk();
diff --git a/test/editor/specs/scenario/cookbook_httprequests_uispec.js b/test/editor/specs/scenario/cookbook_httprequests_uispec.js
new file mode 100644
index 000000000..797b06041
--- /dev/null
+++ b/test/editor/specs/scenario/cookbook_httprequests_uispec.js
@@ -0,0 +1,300 @@
+/**
+ * Copyright JS Foundation and other contributors, http://js.foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+var when = require('when');
+var should = require('should');
+var fs = require('fs-extra');
+
+var helper = require('../../editor_helper');
+var debugTab = require('../../pageobjects/editor/debugTab_page');
+var workspace = require('../../pageobjects/editor/workspace_page');
+var specUtil = require('../../pageobjects/util/spec_util_page');
+
+var httpNodeRoot = '/api';
+
+// https://cookbook.nodered.org/
+describe('cookbook', function () {
+ beforeEach(function () {
+ workspace.init();
+ });
+
+ before(function () {
+ helper.startServer();
+ });
+
+ after(function () {
+ helper.stopServer();
+ });
+
+ describe('HTTP requests', function () {
+ it('simple get request', function () {
+ var injectNode = workspace.addNode('inject');
+ var httpRequestNode = workspace.addNode('httpRequest');
+ var htmlNode = workspace.addNode('html');
+ var debugNode = workspace.addNode('debug');
+
+ httpRequestNode.edit();
+ httpRequestNode.setMethod('GET');
+ httpRequestNode.setUrl(helper.url());
+ httpRequestNode.clickOk();
+
+ htmlNode.edit();
+ htmlNode.setSelector('title');
+ htmlNode.clickOk();
+
+ injectNode.connect(httpRequestNode);
+ httpRequestNode.connect(htmlNode);
+ htmlNode.connect(debugNode);
+
+ workspace.deploy();
+
+ debugTab.open();
+ injectNode.clickLeftButton();
+ debugTab.getMessage().should.eql('"Node-RED"');
+ });
+
+ it('set the URL of a request', function () {
+ var injectNode = workspace.addNode('inject');
+ var changeNode = workspace.addNode('change');
+ var httpRequestNode = workspace.addNode('httpRequest');
+ var debugNode = workspace.addNode('debug');
+
+ injectNode.edit();
+ injectNode.setPayload('str', helper.url());
+ injectNode.clickOk();
+
+ changeNode.edit();
+ changeNode.ruleSet('url', 'msg', 'payload', 'msg');
+ changeNode.clickOk();
+
+ injectNode.connect(changeNode);
+ changeNode.connect(httpRequestNode);
+ httpRequestNode.connect(debugNode);
+
+ workspace.deploy();
+
+ debugTab.open();
+ injectNode.clickLeftButton();
+ debugTab.getMessage().should.containEql('Node-RED ');
+ });
+
+ it('set the URL of a request using a template', function () {
+ var injectNode = workspace.addNode('inject');
+ var changeNode = workspace.addNode('change');
+ var httpRequestNode = workspace.addNode('httpRequest');
+ var debugNode = workspace.addNode('debug');
+
+ injectNode.edit();
+ injectNode.setPayload('str', 'settings');
+ injectNode.clickOk();
+
+ changeNode.edit();
+ changeNode.ruleSet('query', 'msg', 'payload', 'msg');
+ changeNode.clickOk();
+
+ httpRequestNode.edit();
+ httpRequestNode.setUrl(helper.url() + '/{{{query}}}');
+ httpRequestNode.clickOk();
+
+ injectNode.connect(changeNode);
+ changeNode.connect(httpRequestNode);
+ httpRequestNode.connect(debugNode);
+
+ workspace.deploy();
+
+ debugTab.open();
+ injectNode.clickLeftButton();
+ debugTab.getMessage().should.containEql('httpNodeRoot');
+ });
+
+ it('set the query string parameters', function () {
+ var injectNode = workspace.addNode('inject');
+ var changeNode = workspace.addNode('change');
+ var httpRequestNode = workspace.addNode('httpRequest');
+ var debugNode = workspace.addNode('debug');
+
+ injectNode.edit();
+ injectNode.setPayload('str', 'Nick');
+ injectNode.clickOk();
+
+ changeNode.edit();
+ changeNode.ruleSet('query', 'msg', 'payload', 'msg');
+ changeNode.clickOk();
+
+ httpRequestNode.edit();
+ httpRequestNode.setUrl(helper.url() + httpNodeRoot + '/set-query?q={{{query}}}');
+ httpRequestNode.clickOk();
+
+ injectNode.connect(changeNode);
+ changeNode.connect(httpRequestNode);
+ httpRequestNode.connect(debugNode);
+
+ // The code for confirmation starts from here.
+ var httpInNode = workspace.addNode('httpIn', 0, 200);
+ var templateNode = workspace.addNode('template');
+ var httpResponseNode = workspace.addNode('httpResponse');
+
+ httpInNode.edit();
+ httpInNode.setMethod('get');
+ httpInNode.setUrl('/set-query');
+ httpInNode.clickOk();
+
+ templateNode.edit();
+ templateNode.setSyntax('mustache');
+ templateNode.setFormat('handlebars');
+ templateNode.setTemplate('Hello {{req.query.q}}');
+ templateNode.clickOk();
+
+ httpInNode.connect(templateNode);
+ templateNode.connect(httpResponseNode);
+ // The code for confirmation ends here.
+
+ workspace.deploy();
+ debugTab.open();
+ injectNode.clickLeftButton();
+ debugTab.getMessage().should.eql('"Hello Nick"');
+ });
+
+ it('get a parsed JSON response', function () {
+ var injectNode = workspace.addNode('inject');
+ var changeNodeSetPost = workspace.addNode('change');
+ var httpRequestNode = workspace.addNode('httpRequest');
+ var debugNode = workspace.addNode('debug');
+
+ injectNode.edit();
+ injectNode.setPayload('str', 'json-response');
+ injectNode.clickOk();
+
+ changeNodeSetPost.edit();
+ changeNodeSetPost.ruleSet('post', 'msg', 'payload', 'msg');
+ changeNodeSetPost.clickOk();
+
+ httpRequestNode.edit();
+ httpRequestNode.setMethod('GET');
+ var url = helper.url() + httpNodeRoot + '/{{post}}';
+ httpRequestNode.setUrl(url);
+ httpRequestNode.setReturn('obj');
+ httpRequestNode.clickOk();
+
+ debugNode.edit();
+ debugNode.setOutput('payload.title');
+ debugNode.clickOk();
+
+ injectNode.connect(changeNodeSetPost);
+ changeNodeSetPost.connect(httpRequestNode);
+ httpRequestNode.connect(debugNode);
+
+ // The code for confirmation starts from here.
+ var httpInNode = workspace.addNode('httpIn', 0, 200);
+ var templateNode = workspace.addNode('template');
+ var changeNodeSetHeader = workspace.addNode('change');
+ var httpResponseNode = workspace.addNode('httpResponse');
+
+ httpInNode.edit();
+ httpInNode.setMethod('get');
+ httpInNode.setUrl('/json-response');
+ httpInNode.clickOk();
+
+ templateNode.edit();
+ templateNode.setSyntax('mustache');
+ templateNode.setFormat('handlebars');
+ templateNode.setTemplate('{"title": "Hello"}');
+ templateNode.clickOk();
+
+ changeNodeSetHeader.edit();
+ changeNodeSetHeader.ruleSet('headers', 'msg', '{"content-type":"application/json"}', 'json');
+ changeNodeSetHeader.clickOk();
+
+ httpInNode.connect(templateNode);
+ templateNode.connect(changeNodeSetHeader);
+ changeNodeSetHeader.connect(httpResponseNode);
+ // The code for confirmation ends here.
+
+ workspace.deploy();
+ debugTab.open();
+ injectNode.clickLeftButton();
+ debugTab.getMessage().should.eql('"Hello"');
+ });
+
+ it('get a binary response', function () {
+ var injectNode = workspace.addNode('inject');
+ var httpRequestNode = workspace.addNode('httpRequest');
+ var debugNode = workspace.addNode('debug');
+
+ httpRequestNode.edit();
+ httpRequestNode.setMethod('GET');
+ httpRequestNode.setUrl(helper.url() + '/settings');
+ httpRequestNode.setReturn('bin');
+ httpRequestNode.clickOk();
+
+ injectNode.connect(httpRequestNode);
+ httpRequestNode.connect(debugNode);
+
+ workspace.deploy();
+
+ debugTab.open();
+ injectNode.clickLeftButton();
+
+ debugTab.getMessage().should.eql(['123', '34', '104', '116', '116', '112', '78', '111', '100', '101']);
+ });
+
+ it('set a request header', function () {
+ var injectNode = workspace.addNode('inject');
+ var functionNode = workspace.addNode('function');
+ var httpRequestNode = workspace.addNode('httpRequest');
+ var debugNode = workspace.addNode('debug');
+
+ functionNode.edit();
+ functionNode.setFunction('msg.payload = "data to post";\nreturn msg;');
+ functionNode.clickOk();
+
+ httpRequestNode.edit();
+ httpRequestNode.setMethod('POST');
+ var url = helper.url() + httpNodeRoot + '/set-header';
+ httpRequestNode.setUrl(url);
+ httpRequestNode.clickOk();
+
+ injectNode.connect(functionNode);
+ functionNode.connect(httpRequestNode);
+ httpRequestNode.connect(debugNode);
+
+ // The code for confirmation starts from here.
+ var httpInNode = workspace.addNode('httpIn', 0, 200);
+ var templateNode = workspace.addNode('template');
+ var httpResponseNode = workspace.addNode('httpResponse');
+
+ httpInNode.edit();
+ httpInNode.setMethod('post');
+ httpInNode.setUrl('/set-header');
+ httpInNode.clickOk();
+
+ templateNode.edit();
+ templateNode.setSyntax('mustache');
+ templateNode.setFormat('handlebars');
+ templateNode.setTemplate('{{ payload }}');
+ templateNode.clickOk();
+
+ httpInNode.connect(templateNode);
+ templateNode.connect(httpResponseNode);
+ // The code for confirmation ends here.
+
+ workspace.deploy();
+ debugTab.open();
+ injectNode.clickLeftButton();
+ debugTab.getMessage().should.eql('"data to post"');
+ });
+ });
+});
diff --git a/test/editor/specs/scenario/cookbook_messages_uispec.js b/test/editor/specs/scenario/cookbook_messages_uispec.js
new file mode 100644
index 000000000..78facbcda
--- /dev/null
+++ b/test/editor/specs/scenario/cookbook_messages_uispec.js
@@ -0,0 +1,142 @@
+/**
+ * Copyright JS Foundation and other contributors, http://js.foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+var when = require('when');
+var should = require('should');
+var fs = require('fs-extra');
+
+var helper = require('../../editor_helper');
+var debugTab = require('../../pageobjects/editor/debugTab_page');
+var workspace = require('../../pageobjects/editor/workspace_page');
+var specUtil = require('../../pageobjects/util/spec_util_page');
+
+var httpNodeRoot = '/api';
+
+// https://cookbook.nodered.org/
+describe('cookbook', function () {
+ beforeEach(function () {
+ workspace.init();
+ });
+
+ before(function () {
+ helper.startServer();
+ });
+
+ after(function () {
+ helper.stopServer();
+ });
+
+ describe('messages', function () {
+ it('set a message property to a fixed value', function () {
+ var injectNode = workspace.addNode('inject');
+ var changeNode = workspace.addNode('change');
+ var debugNode = workspace.addNode('debug');
+
+ changeNode.edit();
+ changeNode.ruleSet('payload', 'msg', 'Hello World!');
+ changeNode.clickOk();
+
+ injectNode.connect(changeNode);
+ changeNode.connect(debugNode);
+
+ workspace.deploy();
+
+ debugTab.open();
+ injectNode.clickLeftButton();
+ debugTab.getMessage().should.eql('"Hello World!"');
+ });
+
+ it('delete a message property', function () {
+ var injectNode = workspace.addNode('inject');
+ var changeNode = workspace.addNode('change');
+ var debugNode = workspace.addNode('debug');
+
+ changeNode.edit();
+ changeNode.ruleDelete();
+ changeNode.clickOk();
+
+ injectNode.connect(changeNode);
+ changeNode.connect(debugNode);
+
+ workspace.deploy();
+
+ debugTab.open();
+ injectNode.clickLeftButton();
+ debugTab.getMessage().should.eql('undefined');
+ });
+
+ it('move a message property', function () {
+ var injectNode = workspace.addNode('inject');
+ var changeNode = workspace.addNode('change');
+ var debugNode = workspace.addNode('debug');
+
+ injectNode.edit();
+ injectNode.setTopic('Hello');
+ injectNode.clickOk();
+
+ changeNode.edit();
+ changeNode.ruleMove('topic', 'msg', 'payload', 'msg');
+ changeNode.clickOk();
+
+ injectNode.connect(changeNode);
+ changeNode.connect(debugNode);
+
+ workspace.deploy();
+
+ debugTab.open();
+ injectNode.clickLeftButton();
+ debugTab.getMessage().should.eql('"Hello"');
+ });
+
+ it('map a property between different numeric ranges', function () {
+ var injectNode1 = workspace.addNode('inject');
+ var injectNode2 = workspace.addNode('inject', 0, 100);
+ var injectNode3 = workspace.addNode('inject', 0, 200);
+ var rangeNode = workspace.addNode('range', 200, 100);
+ var debugNode = workspace.addNode('debug', 400);
+
+ injectNode1.edit();
+ injectNode1.setPayload('num', 0);
+ injectNode1.clickOk();
+ injectNode2.edit();
+ injectNode2.setPayload('num', 512);
+ injectNode2.clickOk();
+ injectNode3.edit();
+ injectNode3.setPayload('num', 1023);
+ injectNode3.clickOk();
+
+ rangeNode.edit();
+ rangeNode.setAction('clamp');
+ rangeNode.setRange(0, 1023, 0, 5);
+ rangeNode.clickOk();
+
+ injectNode1.connect(rangeNode);
+ injectNode2.connect(rangeNode);
+ injectNode3.connect(rangeNode);
+ rangeNode.connect(debugNode);
+
+ workspace.deploy();
+
+ debugTab.open();
+ injectNode1.clickLeftButton();
+ debugTab.getMessage(1).should.eql('0');
+ injectNode2.clickLeftButton();
+ debugTab.getMessage(2).should.eql('2.5024437927663734');
+ injectNode3.clickLeftButton();
+ debugTab.getMessage(3).should.eql('5');
+ });
+ });
+});
diff --git a/test/editor/specs/scenario/cookbook_mqtt_uispec.js b/test/editor/specs/scenario/cookbook_mqtt_uispec.js
index 655074750..b68170eb5 100644
--- a/test/editor/specs/scenario/cookbook_mqtt_uispec.js
+++ b/test/editor/specs/scenario/cookbook_mqtt_uispec.js
@@ -22,7 +22,6 @@ var helper = require("../../editor_helper");
var debugTab = require('../../pageobjects/editor/debugTab_page');
var workspace = require('../../pageobjects/editor/workspace_page');
var specUtil = require('../../pageobjects/util/spec_util_page');
-var mqttConfig = require('../../pageobjects/nodes/core/network/10-mqttconfig_page.js');
var httpNodeRoot = "/api";
@@ -73,9 +72,9 @@ describe('cookbook', function () {
var mqttOutNode = workspace.addNode("mqttOut");
mqttOutNode.edit();
- mqttConfig.edit();
- mqttConfig.setServer("localhost", moscaSettings.port);
- mqttConfig.clickOk();
+ mqttOutNode.mqttBrokerNode.edit();
+ mqttOutNode.mqttBrokerNode.setServer("localhost", moscaSettings.port);
+ mqttOutNode.mqttBrokerNode.clickOk();
mqttOutNode.clickOk();
workspace.deploy();
diff --git a/test/editor/specs/scenario/cookbook_uispec.js b/test/editor/specs/scenario/cookbook_uispec.js
deleted file mode 100644
index 6e16ca8a7..000000000
--- a/test/editor/specs/scenario/cookbook_uispec.js
+++ /dev/null
@@ -1,441 +0,0 @@
-/**
- * Copyright JS Foundation and other contributors, http://js.foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- **/
-
-var when = require('when');
-var should = require("should");
-var fs = require('fs-extra');
-
-var helper = require("../../editor_helper");
-var debugTab = require('../../pageobjects/editor/debugTab_page');
-var workspace = require('../../pageobjects/editor/workspace_page');
-var specUtil = require('../../pageobjects/util/spec_util_page');
-
-var httpNodeRoot = "/api";
-
-// https://cookbook.nodered.org/
-describe('cookbook', function() {
- beforeEach(function() {
- workspace.init();
- });
-
- before(function() {
- helper.startServer();
- });
-
- after(function() {
- helper.stopServer();
- });
-
- describe('messages', function() {
- it('set a message property to a fixed value', function() {
- var injectNode = workspace.addNode("inject");
- var changeNode = workspace.addNode("change");
- var debugNode = workspace.addNode("debug");
-
- changeNode.edit();
- changeNode.ruleSet("payload", "msg", "Hello World!");
- changeNode.clickOk();
-
- injectNode.connect(changeNode);
- changeNode.connect(debugNode);
-
- workspace.deploy();
-
- debugTab.open();
- injectNode.clickLeftButton();
- debugTab.getMessage().should.eql('"Hello World!"');
- });
-
- it('delete a message property', function() {
- var injectNode = workspace.addNode("inject");
- var changeNode = workspace.addNode("change");
- var debugNode = workspace.addNode("debug");
-
- changeNode.edit();
- changeNode.ruleDelete();
- changeNode.clickOk();
-
- injectNode.connect(changeNode);
- changeNode.connect(debugNode);
-
- workspace.deploy();
-
- debugTab.open();
- injectNode.clickLeftButton();
- debugTab.getMessage().should.eql("undefined");
- });
-
- it('move a message property', function() {
- var injectNode = workspace.addNode("inject");
- var changeNode = workspace.addNode("change");
- var debugNode = workspace.addNode("debug");
-
- injectNode.edit();
- injectNode.setTopic("Hello");
- injectNode.clickOk();
-
- changeNode.edit();
- changeNode.ruleMove("topic", "payload");
- changeNode.clickOk();
-
- injectNode.connect(changeNode);
- changeNode.connect(debugNode);
-
- workspace.deploy();
-
- debugTab.open();
- injectNode.clickLeftButton();
- debugTab.getMessage().should.eql('"Hello"');
- });
-
- it('map a property between different numeric ranges', function() {
- var injectNode1 = workspace.addNode("inject");
- var injectNode2 = workspace.addNode("inject", 0, 100);
- var injectNode3 = workspace.addNode("inject", 0, 200);
- var rangeNode = workspace.addNode("range", 200, 100);
- var debugNode = workspace.addNode("debug", 400);
-
- injectNode1.edit();
- injectNode1.setPayload("num", 0);
- injectNode1.clickOk();
- injectNode2.edit();
- injectNode2.setPayload("num", 512);
- injectNode2.clickOk();
- injectNode3.edit();
- injectNode3.setPayload("num", 1023);
- injectNode3.clickOk();
-
- rangeNode.edit();
- rangeNode.setAction("clamp");
- rangeNode.setRange(0, 1023, 0, 5);
- rangeNode.clickOk();
-
- injectNode1.connect(rangeNode);
- injectNode2.connect(rangeNode);
- injectNode3.connect(rangeNode);
- rangeNode.connect(debugNode);
-
- workspace.deploy();
-
- debugTab.open();
- injectNode1.clickLeftButton();
- debugTab.getMessage(1).should.eql('0');
- injectNode2.clickLeftButton();
- debugTab.getMessage(2).should.eql('2.5024437927663734');
- injectNode3.clickLeftButton();
- debugTab.getMessage(3).should.eql('5');
- });
- });
-
- describe('flow control', function() {
- it('trigger a flow whenever Node-RED starts', function() {
- var injectNode = workspace.addNode("inject");
- var debugNode = workspace.addNode("debug");
-
- injectNode.edit();
- injectNode.setPayload("str", "Started!")
- injectNode.setOnce(true);
- injectNode.clickOk();
- injectNode.connect(debugNode);
-
- debugTab.open();
- workspace.deploy();
- debugTab.getMessage().should.eql('"Started!"');
- });
-
- it('trigger a flow at regular intervals', function() {
- var injectNode = workspace.addNode("inject");
- var debugNode = workspace.addNode("debug");
-
- injectNode.edit();
- injectNode.setRepeat("interval");
- injectNode.setRepeatInterval(1);
- injectNode.clickOk();
- injectNode.connect(debugNode);
-
- workspace.deploy();
-
- debugTab.open();
- specUtil.pause(1000);
- var t1 = Number(debugTab.getMessage(1));
- t1.should.within(1500000000000, 3000000000000);
- specUtil.pause(1000);
- debugTab.getMessage(2).should.within(t1 + 900, 3000000000000);
- });
-
- // skip this case since it needs up to one minite.
- it.skip('trigger a flow at a specific time');
- });
-
- describe('HTTP requests', function() {
- it('simple get request', function() {
- var injectNode = workspace.addNode("inject");
- var httpRequetNode = workspace.addNode("httpRequest");
- var htmlNode = workspace.addNode("html");
- var debugNode = workspace.addNode("debug");
-
- httpRequetNode.edit();
- httpRequetNode.setMethod("GET");
- httpRequetNode.setUrl(helper.url());
- httpRequetNode.clickOk();
-
- htmlNode.edit();
- htmlNode.setSelector("title");
- htmlNode.clickOk();
-
- injectNode.connect(httpRequetNode);
- httpRequetNode.connect(htmlNode);
- htmlNode.connect(debugNode);
-
- workspace.deploy();
-
- debugTab.open();
- injectNode.clickLeftButton();
- debugTab.getMessage().should.eql('"Node-RED"');
- });
-
- it('set the URL of a request', function() {
- var injectNode = workspace.addNode("inject");
- var changeNode = workspace.addNode("change");
- var httpRequetNode = workspace.addNode("httpRequest");
- var debugNode = workspace.addNode("debug");
-
- injectNode.edit();
- injectNode.setPayload("str", helper.url());
- injectNode.clickOk();
-
- changeNode.edit();
- changeNode.ruleSet("url", "msg", "payload", "msg");
- changeNode.clickOk();
-
- injectNode.connect(changeNode);
- changeNode.connect(httpRequetNode);
- httpRequetNode.connect(debugNode);
-
- workspace.deploy();
-
- debugTab.open();
- injectNode.clickLeftButton();
- debugTab.getMessage().should.containEql('Node-RED ');
- });
-
- it('set the URL of a request using a template', function() {
- var injectNode = workspace.addNode("inject");
- var changeNode = workspace.addNode("change");
- var httpRequetNode = workspace.addNode("httpRequest");
- var debugNode = workspace.addNode("debug");
-
- injectNode.edit();
- injectNode.setPayload("str", 'settings');
- injectNode.clickOk();
-
- changeNode.edit();
- changeNode.ruleSet("query", "msg", "payload", "msg");
- changeNode.clickOk();
-
- httpRequetNode.edit();
- httpRequetNode.setUrl(helper.url() + "/{{{query}}}");
- httpRequetNode.clickOk();
-
- injectNode.connect(changeNode);
- changeNode.connect(httpRequetNode);
- httpRequetNode.connect(debugNode);
-
- workspace.deploy();
-
- debugTab.open();
- injectNode.clickLeftButton();
- debugTab.getMessage().should.containEql('httpNodeRoot');
- });
-
- it('set the query string parameters', function() {
- var injectNode = workspace.addNode("inject");
- var changeNode = workspace.addNode("change");
- var httpRequetNode = workspace.addNode("httpRequest");
- var debugNode = workspace.addNode("debug");
-
- injectNode.edit();
- injectNode.setPayload("str", 'Nick');
- injectNode.clickOk();
-
- changeNode.edit();
- changeNode.ruleSet("query", "msg", "payload", "msg");
- changeNode.clickOk();
-
- httpRequetNode.edit();
- httpRequetNode.setUrl(helper.url() + httpNodeRoot + '/set-query?q={{{query}}}');
- httpRequetNode.clickOk();
-
- injectNode.connect(changeNode);
- changeNode.connect(httpRequetNode);
- httpRequetNode.connect(debugNode);
-
- // The code for confirmation starts from here.
- var httpInNode = workspace.addNode("httpIn", 0, 200);
- var templateNode = workspace.addNode("template");
- var httpResponseNode = workspace.addNode("httpResponse");
-
- httpInNode.edit();
- httpInNode.setMethod("get");
- httpInNode.setUrl("/set-query");
- httpInNode.clickOk();
-
- templateNode.edit();
- templateNode.setSyntax("mustache");
- templateNode.setFormat("handlebars");
- templateNode.setTemplate("Hello {{req.query.q}}");
- templateNode.clickOk();
-
- httpInNode.connect(templateNode);
- templateNode.connect(httpResponseNode);
- // The code for confirmation ends here.
-
- workspace.deploy();
- debugTab.open();
- injectNode.clickLeftButton();
- debugTab.getMessage().should.eql('"Hello Nick"');
- });
-
- it('get a parsed JSON response', function() {
- var injectNode = workspace.addNode("inject");
- var changeNodeSetPost = workspace.addNode("change");
- var httpRequetNode = workspace.addNode("httpRequest");
- var debugNode = workspace.addNode("debug");
-
- injectNode.edit();
- injectNode.setPayload("str", "json-response");
- injectNode.clickOk();
-
- changeNodeSetPost.edit();
- changeNodeSetPost.ruleSet("post", "msg", "payload", "msg");
- changeNodeSetPost.clickOk();
-
- httpRequetNode.edit();
- httpRequetNode.setMethod("GET");
- var url = helper.url() + httpNodeRoot + "/{{post}}";
- httpRequetNode.setUrl(url);
- httpRequetNode.setReturn("obj");
- httpRequetNode.clickOk();
-
- debugNode.edit();
- debugNode.setOutput(".title");
- debugNode.clickOk();
-
- injectNode.connect(changeNodeSetPost);
- changeNodeSetPost.connect(httpRequetNode);
- httpRequetNode.connect(debugNode);
-
- // The code for confirmation starts from here.
- var httpInNode = workspace.addNode("httpIn", 0, 200);
- var templateNode = workspace.addNode("template");
- var changeNodeSetHeader = workspace.addNode("change");
- var httpResponseNode = workspace.addNode("httpResponse");
-
- httpInNode.edit();
- httpInNode.setMethod("get");
- httpInNode.setUrl("/json-response");
- httpInNode.clickOk();
-
- templateNode.edit();
- templateNode.setSyntax("mustache");
- templateNode.setFormat("handlebars");
- templateNode.setTemplate('{"title": "Hello"}');
- templateNode.clickOk();
-
- changeNodeSetHeader.edit();
- changeNodeSetHeader.ruleSet("headers", "msg", '{"content-type":"application/json"}', "json");
- changeNodeSetHeader.clickOk();
-
- httpInNode.connect(templateNode);
- templateNode.connect(changeNodeSetHeader);
- changeNodeSetHeader.connect(httpResponseNode);
- // The code for confirmation ends here.
-
- workspace.deploy();
- debugTab.open();
- injectNode.clickLeftButton();
- debugTab.getMessage().should.eql('"Hello"');
- });
-
- it('get a binary response', function() {
- var injectNode = workspace.addNode("inject");
- var httpRequetNode = workspace.addNode("httpRequest");
- var debugNode = workspace.addNode("debug");
-
- httpRequetNode.edit();
- httpRequetNode.setMethod("GET");
- httpRequetNode.setUrl(helper.url() + "/settings");
- httpRequetNode.setReturn("bin");
- httpRequetNode.clickOk();
-
- injectNode.connect(httpRequetNode);
- httpRequetNode.connect(debugNode);
-
- workspace.deploy();
-
- debugTab.open();
- injectNode.clickLeftButton();
-
- debugTab.getMessage().should.eql(['123', '34', '104', '116', '116', '112', '78', '111', '100', '101']);
- });
-
- it('set a request header', function() {
- var injectNode = workspace.addNode("inject");
- var functionNode = workspace.addNode("function");
- var httpRequetNode = workspace.addNode("httpRequest");
- var debugNode = workspace.addNode("debug");
-
- functionNode.edit();
- functionNode.setFunction('msg.payload = "data to post";\nreturn msg;');
- functionNode.clickOk();
-
- httpRequetNode.edit();
- httpRequetNode.setMethod("POST");
- var url = helper.url() + httpNodeRoot + "/set-header";
- httpRequetNode.setUrl(url);
- httpRequetNode.clickOk();
-
- injectNode.connect(functionNode);
- functionNode.connect(httpRequetNode);
- httpRequetNode.connect(debugNode);
-
- // The code for confirmation starts from here.
- var httpInNode = workspace.addNode("httpIn", 0, 200);
- var templateNode = workspace.addNode("template");
- var httpResponseNode = workspace.addNode("httpResponse");
-
- httpInNode.edit();
- httpInNode.setMethod("post");
- httpInNode.setUrl("/set-header");
- httpInNode.clickOk();
-
- templateNode.edit();
- templateNode.setSyntax("mustache");
- templateNode.setFormat("handlebars");
- templateNode.setTemplate("{{ payload }}");
- templateNode.clickOk();
-
- httpInNode.connect(templateNode);
- templateNode.connect(httpResponseNode);
- // The code for confirmation ends here.
-
- workspace.deploy();
- debugTab.open();
- injectNode.clickLeftButton();
- debugTab.getMessage().should.eql('"data to post"');
- });
- });
-});
diff --git a/test/editor/wdio.conf.js b/test/editor/wdio.conf.js
index 7bbfcbe24..4e5a602e0 100644
--- a/test/editor/wdio.conf.js
+++ b/test/editor/wdio.conf.js
@@ -14,6 +14,7 @@
* limitations under the License.
**/
+var browserstack = require('browserstack-local');
exports.config = {
//
@@ -48,27 +49,20 @@ exports.config = {
// and 30 processes will get spawned. The property handles how many capabilities
// from the same test should run tests.
//
- maxInstances: 10,
+ // maxInstances: 10,
//
// If you have trouble getting all important capabilities together, check out the
// Sauce Labs platform configurator - a great tool to configure your capabilities:
// https://docs.saucelabs.com/reference/platforms-configurator
//
- capabilities: [{
+ // capabilities: [{
// maxInstances can get overwritten per capability. So if you have an in-house Selenium
// grid with only 5 firefox instances available you can make sure that not more than
// 5 instances get started at a time.
- maxInstances: 2,
+ // maxInstances: 5,
//
- browserName: 'chrome',
- 'goog:chromeOptions': {
- args: process.env.NODE_RED_NON_HEADLESS
- // Runs tests with opening a browser.
- ? ['--disable-gpu', '--no-sandbox']
- // Runs tests without opening a browser.
- : ['--headless', '--disable-gpu', 'window-size=1920,1080', '--no-sandbox']
- },
- }],
+ // browserName: 'firefox'
+ // }],
//
// ===================
// Test Configurations
@@ -103,7 +97,7 @@ exports.config = {
baseUrl: 'http://localhost',
//
// Default timeout for all waitFor* commands.
- waitforTimeout: 10000,
+ waitforTimeout: 20000,
//
// Default timeout in milliseconds for request
// if Selenium Grid doesn't send response
@@ -134,9 +128,7 @@ exports.config = {
// Services take over a specific job you don't want to take care of. They enhance
// your test setup with almost no effort. Unlike plugins, they don't add new
// commands. Instead, they hook themselves up into the test process.
- port: 9515,
- path: '/',
- services: ['chromedriver'],
+ //services: ['chromedriver'],
//
// Framework you want to run your specs with.
// The following are supported: Mocha, Jasmine, and Cucumber
@@ -155,7 +147,7 @@ exports.config = {
// Options to be passed to Mocha.
// See the full list at http://mochajs.org/
mochaOpts: {
- timeout: 100000,
+ timeout: 1000000,
ui: 'bdd'
},
//
@@ -171,8 +163,44 @@ exports.config = {
* @param {Object} config wdio configuration object
* @param {Array.} capabilities list of capabilities details
*/
- // onPrepare: function (config, capabilities) {
- // },
+ onPrepare: function (config, capabilities) {
+ if (process.env.BROWSERSTACK) {
+ return new Promise(function (resolve, reject) {
+ var options = { key: exports.config.key };
+ var proxy = process.env.http_proxy || process.env.HTTP_PROXY;
+ if (proxy) {
+ var proxyConfigs = proxy.match(/^(https?):\/\/(([^:@\/]+):([^:@\/]+)@)?([^:@\/]+)(:([^:@\/]+))?\/?$/);
+ if (proxyConfigs) {
+ var protocol = proxyConfigs[1];
+ var user = proxyConfigs[3];
+ var pass = proxyConfigs[4];
+ var host = proxyConfigs[5];
+ var port = proxyConfigs[7];
+ if (!port) {
+ if (protocol === 'http') {
+ port = 80;
+ } else if (protocol === 'https') {
+ port = 443;
+ }
+ }
+ if (host) { options.proxyHost = host; }
+ if (port) { options.proxyPort = port; }
+ if (user) { options.proxyUser = user; }
+ if (pass) { options.proxyPass = pass; }
+ } else {
+ reject('error in parsing the environment variable, http_proxy');
+ }
+ }
+ exports.bs_local = new browserstack.Local();
+ exports.bs_local.start(options, function (error) {
+ if (error) {
+ return reject(error);
+ }
+ resolve();
+ });
+ });
+ }
+ },
/**
* Gets executed just before initialising the webdriver session and test framework. It allows you
* to manipulate configurations depending on the capability or spec.
@@ -267,6 +295,44 @@ exports.config = {
* @param {Object} config wdio configuration object
* @param {Array.} capabilities list of capabilities details
*/
- // onComplete: function(exitCode, config, capabilities) {
- // }
+ onComplete: function(exitCode, config, capabilities) {
+ if (process.env.BROWSERSTACK) {
+ exports.bs_local.stop(function () {});
+ }
+ }
+};
+
+if (process.env.BROWSERSTACK) {
+ exports.config.maxInstances = 1;
+ if (process.env.BROWSERSTACK_USERNAME && process.env.BROWSERSTACK_ACCESS_KEY) {
+ exports.config.user = process.env.BROWSERSTACK_USERNAME;
+ exports.config.key = process.env.BROWSERSTACK_ACCESS_KEY;
+ } else {
+ console.log('You need to set the following environment variables.');
+ console.log('BROWSERSTACK_USERNAME=');
+ console.log('BROWSERSTACK_ACCESS_KEY=');
+ }
+ exports.config.services = ['browserstack'];
+ var capabilities = [];
+ capabilities.push({ os: 'Windows', os_version: '10', browser: 'Chrome', resolution: '1920x1080', 'browserstack.local': true });
+ capabilities.push({ os: 'Windows', os_version: '10', browser: 'Firefox', resolution: '1920x1080', 'browserstack.local': true });
+ capabilities.push({ os: 'OS X', os_version: 'Catalina', browser: 'Chrome', resolution: '1920x1080', 'browserstack.local': true });
+ capabilities.push({ os: 'OS X', os_version: 'Catalina', browser: 'Firefox', resolution: '1920x1080', 'browserstack.local': true });
+ exports.config.capabilities = capabilities;
+} else {
+ exports.config.maxInstances = 10;
+ exports.config.port = 9515;
+ exports.config.path = '/';
+ exports.config.services = ['chromedriver'];
+ exports.config.capabilities = [{
+ maxInstances: 2,
+ browserName: 'chrome',
+ 'goog:chromeOptions': {
+ args: process.env.NODE_RED_NON_HEADLESS
+ // Runs tests with opening a browser.
+ ? ['--disable-gpu', '--no-sandbox']
+ // Runs tests without opening a browser.
+ : ['--headless', '--disable-gpu', 'window-size=1920,1080', '--no-sandbox']
+ }
+ }];
}
diff --git a/test/nodes/core/common/20-inject_spec.js b/test/nodes/core/common/20-inject_spec.js
index ea78d0f53..ff5eb7f73 100644
--- a/test/nodes/core/common/20-inject_spec.js
+++ b/test/nodes/core/common/20-inject_spec.js
@@ -488,6 +488,77 @@ describe('inject node', function() {
});
});
+
+ it('should inject multiple properties ', function (done) {
+ var flow = [{id: "n1", type: "inject", props: [{p:"topic", v:"t1", vt:"str"}, {p:"payload", v:"foo", vt:"str"}, {p:"x", v: 10, "vt":"num"}, {p:"y", v: "x+2", "vt":"jsonata"}], wires: [["n2"]], z: "flow"},
+ {id: "n2", type: "helper"}];
+ helper.load(injectNode, flow, function () {
+ var n1 = helper.getNode("n1");
+ var n2 = helper.getNode("n2");
+ n2.on("input", function (msg) {
+ try {
+ msg.should.have.property("topic", "t1");
+ msg.should.have.property("payload", "foo");
+ msg.should.have.property("x", 10);
+ msg.should.have.property("y", 12);
+ done();
+ } catch (err) {
+ done(err);
+ }
+ });
+ n1.receive({});
+ });
+ });
+
+ it('should inject multiple properties using legacy props if needed', function (done) {
+ var flow = [{id: "n1", type: "inject", payload:"123", payloadType:"num", topic:"foo", props: [{p:"topic", vt:"str"}, {p:"payload"}], wires: [["n2"]], z: "flow"},
+ {id: "n2", type: "helper"}];
+ helper.load(injectNode, flow, function () {
+ var n1 = helper.getNode("n1");
+ var n2 = helper.getNode("n2");
+ n2.on("input", function (msg) {
+ try {
+ msg.should.have.property("topic", "foo");
+ msg.should.have.property("payload", 123);
+ done();
+ } catch (err) {
+ done(err);
+ }
+ });
+ n1.receive({});
+ });
+ });
+
+
+ it('should report invalid JSONata expression', function (done) {
+ var flow = [{id: "n1", type: "inject", props: [{p:"topic", v:"t1", vt:"str"}, {p:"payload", v:"@", vt:"jsonata"}], wires: [["n2"]], z: "flow"},
+ {id: "n2", type: "helper"}];
+ helper.load(injectNode, flow, function () {
+ var n1 = helper.getNode("n1");
+ var n2 = helper.getNode("n2");
+ var count = 0;
+ n2.on("input", function (msg) {
+ try {
+ msg.should.have.property("topic", "t1");
+ msg.should.not.have.property("payload");
+ count++;
+ if (count == 2) {
+ done();
+ }
+ } catch (err) {
+ done(err);
+ }
+ });
+ n1.on("call:error", function(err) {
+ count++;
+ if (count == 2) {
+ done();
+ }
+ });
+ n1.receive({});
+ });
+ });
+
describe('post', function() {
it('should inject message', function(done) {
helper.load(injectNode,
diff --git a/test/nodes/core/common/21-debug_spec.js b/test/nodes/core/common/21-debug_spec.js
index bdff7976c..990797ef3 100644
--- a/test/nodes/core/common/21-debug_spec.js
+++ b/test/nodes/core/common/21-debug_spec.js
@@ -603,6 +603,30 @@ describe('debug node', function() {
.post('/debug/n99/enable')
.expect(404).end(done);
});
+
+ it('should return 400 for invalid bulk disable', function(done) {
+ var flow = [{id:"n1", type:"debug", active: true }];
+ helper.load(debugNode, flow, function() {
+ helper.request()
+ .post('/debug/disable')
+ .send({})
+ .set('Content-type', 'application/json')
+ .expect(400).end(done);
+ });
+
+ })
+
+ it('should return success for bulk disable', function(done) {
+ var flow = [{id:"n1", type:"debug", active: true }];
+ helper.load(debugNode, flow, function() {
+ helper.request()
+ .post('/debug/disable')
+ .send({nodes:['n1']})
+ .set('Content-type', 'application/json')
+ .expect(201).end(done);
+ });
+
+ })
});
describe('get', function() {
diff --git a/test/nodes/core/function/10-function_spec.js b/test/nodes/core/function/10-function_spec.js
index c13dd1972..ab7e4b486 100644
--- a/test/nodes/core/function/10-function_spec.js
+++ b/test/nodes/core/function/10-function_spec.js
@@ -53,7 +53,6 @@ describe('function node', function() {
});
});
-
it('should be loaded', function(done) {
var flow = [{id:"n1", type:"function", name: "function" }];
helper.load(functionNode, flow, function() {
@@ -1336,6 +1335,46 @@ describe('function node', function() {
});
});
+ it('should execute initialization', function(done) {
+ var flow = [{id:"n1",type:"function",wires:[["n2"]],func:"msg.payload = global.get('X'); return msg;",initialize:"global.set('X','bar');"},
+ {id:"n2", type:"helper"}];
+ helper.load(functionNode, flow, function() {
+ var n1 = helper.getNode("n1");
+ var n2 = helper.getNode("n2");
+ n2.on("input", function(msg) {
+ msg.should.have.property("payload", "bar");
+ done();
+ });
+ n1.receive({payload: "foo"});
+ });
+ });
+
+ it('should wait completion of initialization', function(done) {
+ var flow = [{id:"n1",type:"function",wires:[["n2"]],func:"msg.payload = global.get('X'); return msg;",initialize:"global.set('X', '-'); return new Promise((resolve, reject) => setTimeout(() => { global.set('X','bar'); resolve(); }, 500));"},
+ {id:"n2", type:"helper"}];
+ helper.load(functionNode, flow, function() {
+ var n1 = helper.getNode("n1");
+ var n2 = helper.getNode("n2");
+ n2.on("input", function(msg) {
+ msg.should.have.property("payload", "bar");
+ done();
+ });
+ n1.receive({payload: "foo"});
+ });
+ });
+
+ it('should execute finalization', function(done) {
+ var flow = [{id:"n1",type:"function",wires:[],func:"return msg;",finalize:"global.set('X','bar');"}];
+ helper.load(functionNode, flow, function() {
+ var n1 = helper.getNode("n1");
+ var ctx = n1.context().global;
+ helper.unload().then(function () {
+ ctx.get('X').should.equal("bar");
+ done();
+ });
+ });
+ });
+
describe('Logger', function () {
it('should log an Info Message', function (done) {
var flow = [{id: "n1", type: "function", wires: [["n2"]], func: "node.log('test');"}];
diff --git a/test/nodes/core/function/89-trigger_spec.js b/test/nodes/core/function/89-trigger_spec.js
index 5f7bfc2d1..3377ee1f7 100644
--- a/test/nodes/core/function/89-trigger_spec.js
+++ b/test/nodes/core/function/89-trigger_spec.js
@@ -102,20 +102,20 @@ describe('trigger node', function() {
function basicTest(type, val, rval) {
it('should output 1st value when triggered ('+type+')', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1:val, op1type:type, op2:"", op2type:"null", duration:"20", wires:[["n2"]] },
- {id:"n2", type:"helper"} ];
+ {id:"n2", type:"helper"} ];
process.env[val] = rval;
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
- if (rval) {
- msg.should.have.property("payload");
- should.deepEqual(msg.payload, rval);
- }
- else {
- msg.should.have.property("payload", val);
- }
+ if (rval) {
+ msg.should.have.property("payload");
+ should.deepEqual(msg.payload, rval);
+ }
+ else {
+ msg.should.have.property("payload", val);
+ }
delete process.env[val];
done();
}
@@ -127,7 +127,7 @@ describe('trigger node', function() {
it('should output 2st value when triggered ('+type+')', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1:"foo", op1type:"str", op2:val, op2type:type, duration:"20", wires:[["n2"]] },
- {id:"n2", type:"helper"} ];
+ {id:"n2", type:"helper"} ];
process.env[val] = rval;
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
@@ -136,17 +136,17 @@ describe('trigger node', function() {
n2.on("input", function(msg) {
try {
if (c === 0) {
- msg.should.have.property("payload", "foo");
+ msg.should.have.property("payload", "foo");
c++;
}
else {
- if (rval) {
- msg.should.have.property("payload");
- should.deepEqual(msg.payload, rval);
- }
- else {
- msg.should.have.property("payload", val);
- }
+ if (rval) {
+ msg.should.have.property("payload");
+ should.deepEqual(msg.payload, rval);
+ }
+ else {
+ msg.should.have.property("payload", val);
+ }
delete process.env[val];
done();
}
@@ -378,6 +378,51 @@ describe('trigger node', function() {
});
});
+ it('should handle multiple other properties individually if asked to do so', function(done) {
+ var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", bytopic:"topic", topic:"foo", op1:"1", op2:"0", op1type:"num", op2type:"num", duration:"30", wires:[["n2"]] },
+ {id:"n2", type:"helper"} ];
+ helper.load(triggerNode, flow, function() {
+ var n1 = helper.getNode("n1");
+ var n2 = helper.getNode("n2");
+ var c = 0;
+ n2.on("input", function(msg) {
+ try {
+ c += 1;
+ if (c === 1) {
+ msg.should.have.a.property("payload", 1);
+ msg.should.have.a.property("foo", "A");
+ }
+ else if (c === 2) {
+ msg.should.have.a.property("payload", 1);
+ msg.should.have.a.property("foo", "B");
+ }
+ else if (c === 3) {
+ msg.should.have.a.property("payload", 1);
+ msg.should.have.a.property("foo", "C");
+ }
+ else if (c === 4) {
+ msg.should.have.a.property("payload", 0);
+ msg.should.have.a.property("foo", "A");
+ }
+ else if (c === 5) {
+ msg.should.have.a.property("payload", 0);
+ msg.should.have.a.property("foo", "B");
+ }
+ else if (c === 6) {
+ msg.should.have.a.property("payload", 0);
+ msg.should.have.a.property("foo", "C");
+ done();
+ }
+ } catch(err) {
+ done(err);
+ }
+ });
+ n1.emit("input", {payload:1,foo:"A"});
+ n1.emit("input", {payload:2,foo:"B"});
+ n1.emit("input", {payload:3,foo:"C"});
+ });
+ });
+
it('should be able to return things from flow and global context variables', function(done) {
var spy = sinon.stub(RED.util, 'evaluateNodeProperty',
function(arg1, arg2, arg3, arg4, arg5) { if (arg5) { arg5(null, arg1) } else { return arg1; } }
@@ -408,8 +453,8 @@ describe('trigger node', function() {
it('should be able to return things from persistable flow and global context variables', function (done) {
var flow = [{"id": "n1", "type": "trigger", "name": "triggerNode", "op1": "#:(memory1)::foo", "op1type": "flow",
- "op2": "#:(memory1)::bar", "op2type": "global", "duration": "20", "wires": [["n2"]], "z": "flow" },
- {"id": "n2", "type": "helper"}];
+ "op2": "#:(memory1)::bar", "op2type": "global", "duration": "20", "wires": [["n2"]], "z": "flow" },
+ {"id": "n2", "type": "helper"}];
helper.load(triggerNode, flow, function () {
initContext(function () {
var n1 = helper.getNode("n1");
@@ -442,11 +487,11 @@ describe('trigger node', function() {
it('should be able to return things from multiple persistable global context variables', function (done) {
var flow = [{"id": "n1", "z": "flow", "type": "trigger",
- "duration": "20", "wires": [["n2"]],
- "op1": "#:(memory1)::val", "op1type": "global",
- "op2": "#:(memory2)::val", "op2type": "global"
- },
- {"id": "n2", "type": "helper"}];
+ "duration": "20", "wires": [["n2"]],
+ "op1": "#:(memory1)::val", "op1type": "global",
+ "op2": "#:(memory2)::val", "op2type": "global"
+ },
+ {"id": "n2", "type": "helper"}];
helper.load(triggerNode, flow, function () {
initContext(function () {
var n1 = helper.getNode("n1");
@@ -481,11 +526,11 @@ describe('trigger node', function() {
it('should be able to return things from multiple persistable flow context variables', function (done) {
var flow = [{"id": "n1", "z": "flow", "type": "trigger",
- "duration": "20", "wires": [["n2"]],
- "op1": "#:(memory1)::val", "op1type": "flow",
- "op2": "#:(memory2)::val", "op2type": "flow"
- },
- {"id": "n2", "type": "helper"}];
+ "duration": "20", "wires": [["n2"]],
+ "op1": "#:(memory1)::val", "op1type": "flow",
+ "op2": "#:(memory2)::val", "op2type": "flow"
+ },
+ {"id": "n2", "type": "helper"}];
helper.load(triggerNode, flow, function () {
initContext(function () {
var n1 = helper.getNode("n1");
@@ -520,11 +565,11 @@ describe('trigger node', function() {
it('should be able to return things from multiple persistable flow & global context variables', function (done) {
var flow = [{"id": "n1", "z": "flow", "type": "trigger",
- "duration": "20", "wires": [["n2"]],
- "op1": "#:(memory1)::val", "op1type": "flow",
- "op2": "#:(memory2)::val", "op2type": "global"
- },
- {"id": "n2", "type": "helper"}];
+ "duration": "20", "wires": [["n2"]],
+ "op1": "#:(memory1)::val", "op1type": "flow",
+ "op2": "#:(memory2)::val", "op2type": "global"
+ },
+ {"id": "n2", "type": "helper"}];
helper.load(triggerNode, flow, function () {
initContext(function () {
var n1 = helper.getNode("n1");
@@ -736,10 +781,12 @@ describe('trigger node', function() {
try {
if (c === 0) {
msg.should.have.a.property("payload", "Goodbye");
+ msg.should.have.a.property("topic", "test2");
c += 1;
}
else {
msg.should.have.a.property("payload", "World");
+ msg.should.have.a.property("topic", "test3");
(Date.now() - ss).should.be.greaterThan(70);
done();
}
@@ -747,16 +794,51 @@ describe('trigger node', function() {
catch(err) { done(err); }
});
var ss = Date.now();
- n1.emit("input", {payload:"Hello"});
+ n1.emit("input", {payload:"Hello", topic:"test1"});
setTimeout( function() {
- n1.emit("input", {payload:"Goodbye"});
+ n1.emit("input", {payload:"Goodbye", topic:"test2"});
},20);
setTimeout( function() {
- n1.emit("input", {payload:"World"});
+ n1.emit("input", {payload:"World", topic:"test3"});
},80);
});
});
+ it('should be able output the 2nd payload and handle multiple topics', function(done) {
+ var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", extend:"false", op1type:"nul", op2type:"payl", op1:"false", op2:"true", duration:"80", bytopic:"topic", wires:[["n2"]] },
+ {id:"n2", type:"helper"} ];
+ helper.load(triggerNode, flow, function() {
+ var n1 = helper.getNode("n1");
+ var n2 = helper.getNode("n2");
+ var c = 0;
+ n2.on("input", function(msg) {
+ try {
+ if (c === 0) {
+ msg.should.have.a.property("payload", "Goodbye1");
+ msg.should.have.a.property("topic", "test1");
+ c += 1;
+ }
+ else {
+ msg.should.have.a.property("payload", "Goodbye2");
+ msg.should.have.a.property("topic", "test2");
+ done();
+ }
+ }
+ catch(err) { done(err); }
+ });
+ n1.emit("input", {payload:"Hello1", topic:"test1"});
+ setTimeout( function() {
+ n1.emit("input", {payload:"Hello2", topic:"test2"});
+ },20);
+ setTimeout( function() {
+ n1.emit("input", {payload:"Goodbye2", topic:"test2"});
+ },20);
+ setTimeout( function() {
+ n1.emit("input", {payload:"Goodbye1", topic:"test1"});
+ },20);
+ });
+ });
+
it('should be able to apply mustache templates to payloads', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1type:"val", op2type:"val", op1:"{{payload}}", op2:"{{topic}}", duration:"50", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
@@ -781,6 +863,40 @@ describe('trigger node', function() {
});
});
+ it('should be able to send 2nd message to a 2nd output', function(done) {
+ var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1type:"val", op2type:"val", op1:"hello", op2:"world", duration:"50", outputs:2, wires:[["n2"],["n3"]] },
+ {id:"n2", type:"helper"}, {id:"n3", type:"helper"} ];
+ helper.load(triggerNode, flow, function() {
+ var n1 = helper.getNode("n1");
+ var n2 = helper.getNode("n2");
+ var n3 = helper.getNode("n3");
+ var c = 0;
+ n2.on("input", function(msg) {
+ try {
+ if (c === 0) {
+ msg.should.have.a.property("payload", "hello");
+ msg.should.have.a.property("topic", "test");
+ c+=1;
+ }
+ else { done(err); }
+ }
+ catch(err) { done(err); }
+ });
+ n3.on("input", function(msg) {
+ try {
+ if (c === 1) {
+ msg.should.have.a.property("payload", "world");
+ msg.should.have.a.property("topic", "test");
+ done();
+ }
+ else { done(err); }
+ }
+ catch(err) { done(err); }
+ });
+ n1.emit("input", {payload:"go",topic:"test"});
+ });
+ });
+
it('should handle string null as null', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1type:"val", op2type:"pay", op1:"null", op2:"null", duration:"40", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
diff --git a/test/nodes/core/parsers/70-CSV_spec.js b/test/nodes/core/parsers/70-CSV_spec.js
index f46afc24a..10e167b6d 100644
--- a/test/nodes/core/parsers/70-CSV_spec.js
+++ b/test/nodes/core/parsers/70-CSV_spec.js
@@ -1,3 +1,4 @@
+/* eslint-disable no-undef */
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
@@ -70,12 +71,13 @@ describe('CSV node', function() {
it('should convert a simple csv string to a javascript object', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
- {id:"n2", type:"helper"} ];
+ {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('payload', { a: 1, b: 2, c: 3, d: 4 });
+ msg.should.have.property('columns', "a,b,c,d");
check_parts(msg, 0, 1);
done();
});
@@ -86,7 +88,7 @@ describe('CSV node', function() {
it('should remove quotes and whitespace from template', function(done) {
var flow = [ { id:"n1", type:"csv", temp:'"a", "b" , " c "," d " ', wires:[["n2"]] },
- {id:"n2", type:"helper"} ];
+ {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@@ -102,12 +104,13 @@ describe('CSV node', function() {
it('should create column names if no template provided', function(done) {
var flow = [ { id:"n1", type:"csv", temp:'', wires:[["n2"]] },
- {id:"n2", type:"helper"} ];
+ {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('payload', { col1: 1, col2: 2, col3: 3, col4: 4 });
+ msg.should.have.property('columns', "col1,col2,col3,col4");
check_parts(msg, 0, 1);
done();
});
@@ -118,12 +121,13 @@ describe('CSV node', function() {
it('should allow dropping of fields from the template', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,,,d", wires:[["n2"]] },
- {id:"n2", type:"helper"} ];
+ {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('payload', { a: 1, d: 4 });
+ msg.should.have.property('columns', 'a,d');
check_parts(msg, 0, 1);
done();
});
@@ -134,7 +138,7 @@ describe('CSV node', function() {
it('should leave numbers starting with 0, e and + as strings (except 0.)', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] },
- {id:"n2", type:"helper"} ];
+ {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@@ -150,7 +154,7 @@ describe('CSV node', function() {
it('should not parse numbers when told not to do so', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", strings:false, wires:[["n2"]] },
- {id:"n2", type:"helper"} ];
+ {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@@ -166,7 +170,7 @@ describe('CSV node', function() {
it('should leave handle strings with scientific notation as numbers', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] },
- {id:"n2", type:"helper"} ];
+ {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@@ -181,44 +185,128 @@ describe('CSV node', function() {
});
- it('should allow quotes in the input', function(done) {
- var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] },
- {id:"n2", type:"helper"} ];
+ it('should allow quotes in the input (but drop blank strings)', function(done) {
+ var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g,h", wires:[["n2"]] },
+ {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
//console.log(msg);
- msg.should.have.property('payload', { a: 1, b: -2, c: '+3', d: '04', e: '-05', f: 'ab"cd', g: 'with,a,comma' });
+ msg.should.have.property('payload', { a:1, b:-2, c:'+3', d:'04', f:'-05', g:'ab"cd', h:'with,a,comma' });
check_parts(msg, 0, 1);
done();
});
- var testString = '"1","-2","+3","04","-05","ab""cd","with,a,comma"'+String.fromCharCode(10);
+ var testString = '"1","-2","+3","04","","-05","ab""cd","with,a,comma"'+String.fromCharCode(10);
+ n1.emit("input", {payload:testString});
+ });
+ });
+
+ it('should allow blank strings in the input if selected', function(done) {
+ var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", include_empty_strings:true, wires:[["n2"]] },
+ {id:"n2", type:"helper"} ];
+ helper.load(csvNode, flow, function() {
+ var n1 = helper.getNode("n1");
+ var n2 = helper.getNode("n2");
+ n2.on("input", function(msg) {
+ //console.log(msg);
+ msg.should.have.property('payload', { a: 1, b: '', c: '', d: '', e: '-05', f: 'ab"cd', g: 'with,a,comma' });
+ //check_parts(msg, 0, 1);
+ done();
+ });
+ var testString = '"1","","","","-05","ab""cd","with,a,comma"'+String.fromCharCode(10);
+ n1.emit("input", {payload:testString});
+ });
+ });
+
+ it('should allow missing columns (nulls) in the input if selected', function(done) {
+ var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", include_null_values:true, wires:[["n2"]] },
+ {id:"n2", type:"helper"} ];
+ helper.load(csvNode, flow, function() {
+ var n1 = helper.getNode("n1");
+ var n2 = helper.getNode("n2");
+ n2.on("input", function(msg) {
+ //console.log(msg);
+ msg.should.have.property('payload', { a: 1, b: null, c: '+3', d: null, e: '-05', f: 'ab"cd', g: 'with,a,comma' });
+ //check_parts(msg, 0, 1);
+ done();
+ });
+ var testString = '"1",,"+3",,"-05","ab""cd","with,a,comma"'+String.fromCharCode(10);
+ n1.emit("input", {payload:testString});
+ });
+ });
+
+ it('should handle cr and lf in the input', function(done) {
+ var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] },
+ {id:"n2", type:"helper"} ];
+ helper.load(csvNode, flow, function() {
+ var n1 = helper.getNode("n1");
+ var n2 = helper.getNode("n2");
+ n2.on("input", function(msg) {
+ //console.log(msg);
+ msg.should.have.property('payload', { a: "with a\nnew line", b: "and a\rcarriage return", c: "and why\r\nnot both"});
+ check_parts(msg, 0, 1);
+ done();
+ });
+ var testString = '"with a'+String.fromCharCode(10)+'new line","and a'+String.fromCharCode(13)+'carriage return","and why\r\nnot both"'+String.fromCharCode(10);
n1.emit("input", {payload:testString});
});
});
it('should recover from an odd number of quotes in the input', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] },
- {id:"n2", type:"helper"} ];
+ {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
+ var c = 0;
n2.on("input", function(msg) {
- //console.log(msg);
- msg.should.have.property('payload', { a: "with,an", b: "odd,number", c: "ofquotes" });
- //msg.should.have.property('payload', { a: 1, b: -2, c: '+3', d: 4, e: -5, f: 'ab"cd', g: 'with,a,comma' });
- check_parts(msg, 0, 1);
- done();
+ if (c == 0) {
+ c = 1;
+ msg.should.have.property('payload', { a: "with,an", b: "odd,number", c: "ofquotes\n" });
+ check_parts(msg, 0, 1);
+ }
+ else {
+ msg.should.have.property('payload', { a: "this is", b: "a normal", c: "line" });
+ check_parts(msg, 0, 1);
+ done();
+ }
});
var testString = '"with,a"n,odd","num"ber","of"qu"ot"es"'+String.fromCharCode(10);
n1.emit("input", {payload:testString});
+ n1.emit("input", {payload:'"this is","a normal","line"'});
+ });
+ });
+
+ it('should recover from an odd number of quotes in the input (2)', function(done) {
+ var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] },
+ {id:"n2", type:"helper"} ];
+ helper.load(csvNode, flow, function() {
+ var n1 = helper.getNode("n1");
+ var n2 = helper.getNode("n2");
+ var c = 0;
+ n2.on("input", function(msg) {
+ //console.log(msg)
+ if (c == 0) {
+ c = 1;
+ msg.should.have.property('payload', { a: "with,an", b: "odd,number", c: "ofquotes\nthis is,a normal,line" });
+ check_parts(msg, 0, 1);
+ }
+ else {
+ msg.should.have.property('payload', { a: "this is", b: "another", c: "line" });
+ check_parts(msg, 0, 1);
+ done();
+ }
+ });
+ var testString = '"with,a"n,odd","num"ber","of"qu"ot"es"'+String.fromCharCode(10)+'"this is","a normal","line"'+String.fromCharCode(10);
+ n1.emit("input", {payload:testString});
+ n1.emit("input", {payload:'"this is","another","line"'});
});
});
it('should be able to use the first line as a template', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", hdrin:true, wires:[["n2"]] },
- {id:"n2", type:"helper"} ];
+ {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@@ -243,12 +331,13 @@ describe('CSV node', function() {
it('should be able to output multiple lines as one array', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", multi:"yes", wires:[["n2"]] },
- {id:"n2", type:"helper"} ];
+ {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('payload', [ { a: 1, b: 2, c: 3, d: 4 },{ a: 5, b: -6, c: '07', d: '+8' },{ a: 9, b: 0, c: 'a', d: 'b' },{ a: 'c', b: 'd', c: 'e', d: 'f' } ]);
+ msg.should.have.property('columns','a,b,c,d');
msg.should.not.have.property('parts');
done();
});
@@ -259,7 +348,7 @@ describe('CSV node', function() {
it('should handle numbers in strings but not IP addresses', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e", wires:[["n2"]] },
- {id:"n2", type:"helper"} ];
+ {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@@ -275,7 +364,7 @@ describe('CSV node', function() {
it('should preserve parts property', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
- {id:"n2", type:"helper"} ];
+ {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@@ -319,7 +408,7 @@ describe('CSV node', function() {
it('should skip several lines from start if requested', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", skip: 2, wires:[["n2"]] },
- {id:"n2", type:"helper"} ];
+ {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@@ -333,9 +422,9 @@ describe('CSV node', function() {
});
});
- it('should skip several lines from start then use next line as a tempate', function(done) {
+ it('should skip several lines from start then use next line as a template', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", hdrin:true, skip: 2, wires:[["n2"]] },
- {id:"n2", type:"helper"} ];
+ {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@@ -351,7 +440,7 @@ describe('CSV node', function() {
it('should skip several lines from start and correct parts', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", skip: 2, wires:[["n2"]] },
- {id:"n2", type:"helper"} ];
+ {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@@ -383,11 +472,13 @@ describe('CSV node', function() {
n2.on("input", function(msg) {
if (c === 0) {
msg.should.have.property('payload', { w: 1, x: 2, y: 3, z: 4 });
+ msg.should.have.property('columns', 'w,x,y,z');
check_parts(msg, 0, 2);
c += 1;
}
else {
msg.should.have.property('payload', { w: 5, x: 6, y: 7, z: 8 });
+ msg.should.have.property('columns', 'w,x,y,z');
check_parts(msg, 1, 2);
done();
}
@@ -411,7 +502,7 @@ describe('CSV node', function() {
it('should convert a simple object back to a csv', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,,e", wires:[["n2"]] },
- {id:"n2", type:"helper"} ];
+ {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@@ -429,7 +520,7 @@ describe('CSV node', function() {
it('should convert a simple object back to a csv with no template', function(done) {
var flow = [ { id:"n1", type:"csv", temp:" ", wires:[["n2"]] },
- {id:"n2", type:"helper"} ];
+ {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@@ -447,7 +538,7 @@ describe('CSV node', function() {
it('should handle a template with spaces in the property names', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b o,c p,,e", wires:[["n2"]] },
- {id:"n2", type:"helper"} ];
+ {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@@ -465,7 +556,7 @@ describe('CSV node', function() {
it('should convert an array of objects to a multi-line csv', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
- {id:"n2", type:"helper"} ];
+ {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@@ -483,7 +574,7 @@ describe('CSV node', function() {
it('should convert a simple array back to a csv', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
- {id:"n2", type:"helper"} ];
+ {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@@ -501,7 +592,7 @@ describe('CSV node', function() {
it('should convert an array of arrays back to a multi-line csv', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
- {id:"n2", type:"helper"} ];
+ {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@@ -519,7 +610,7 @@ describe('CSV node', function() {
it('should be able to include column names as first row', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", hdrout:true, ret:"\r\n", wires:[["n2"]] },
- {id:"n2", type:"helper"} ];
+ {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@@ -535,9 +626,36 @@ describe('CSV node', function() {
});
});
+ it('should be able to pass in column names', function(done) {
+ var flow = [ { id:"n1", type:"csv", temp:"", hdrout:"once", ret:"\r\n", wires:[["n2"]] },
+ {id:"n2", type:"helper"} ];
+ helper.load(csvNode, flow, function() {
+ var n1 = helper.getNode("n1");
+ var n2 = helper.getNode("n2");
+ var count = 0;
+ n2.on("input", function(msg) {
+ count += 1;
+ try {
+ if (count === 1) {
+ msg.should.have.property('payload', 'a,,b,a\r\n4,,3,4\r\n');
+ }
+ if (count === 3) {
+ msg.should.have.property('payload', '4,,3,4\r\n');
+ done()
+ }
+ }
+ catch(e) { done(e); }
+ });
+ var testJson = [{ d: 1, b: 3, c: 2, a: 4 }];
+ n1.emit("input", {payload:testJson, columns:"a,,b,a"});
+ n1.emit("input", {payload:testJson});
+ n1.emit("input", {payload:testJson});
+ });
+ });
+
it('should handle quotes and sub-properties', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
- {id:"n2", type:"helper"} ];
+ {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@@ -557,7 +675,7 @@ describe('CSV node', function() {
it('should just pass through if no payload provided', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
- {id:"n2", type:"helper"} ];
+ {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@@ -577,7 +695,7 @@ describe('CSV node', function() {
it('should warn if provided a number or boolean', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
- {id:"n2", type:"helper"} ];
+ {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
diff --git a/test/nodes/core/sequence/17-split_spec.js b/test/nodes/core/sequence/17-split_spec.js
index 3d38a9ac8..2c45f0e02 100644
--- a/test/nodes/core/sequence/17-split_spec.js
+++ b/test/nodes/core/sequence/17-split_spec.js
@@ -517,6 +517,49 @@ describe('JOIN node', function() {
});
});
+ it('should join things into an array after a count with a buffer join set', function(done) {
+ var flow = [{id:"n1", type:"join", wires:[["n2"]], count:3, joinerType:"bin", joiner:"" ,mode:"custom"},
+ {id:"n2", type:"helper"}];
+ helper.load(joinNode, flow, function() {
+ var n1 = helper.getNode("n1");
+ var n2 = helper.getNode("n2");
+ n2.on("input", function(msg) {
+ try {
+ msg.should.have.property("payload");
+ msg.payload.should.be.an.Array();
+ msg.payload[0].should.equal(1);
+ msg.payload[1].should.equal(true);
+ //msg.payload[2].a.should.equal(1);
+ done();
+ }
+ catch(e) {done(e);}
+ });
+ n1.receive({payload:1});
+ n1.receive({payload:true});
+ n1.receive({payload:{a:1}});
+ });
+ });
+
+ it('should join strings into a buffer after a count', function(done) {
+ var flow = [{id:"n1", type:"join", wires:[["n2"]], count:2, build:"buffer", joinerType:"bin", joiner:"", mode:"custom"},
+ {id:"n2", type:"helper"}];
+ helper.load(joinNode, flow, function() {
+ var n1 = helper.getNode("n1");
+ var n2 = helper.getNode("n2");
+ n2.on("input", function(msg) {
+ try {
+ msg.should.have.property("payload");
+ msg.payload.length.should.equal(10);
+ msg.payload.toString().should.equal("helloworld");
+ done();
+ }
+ catch(e) {done(e);}
+ });
+ n1.receive({payload:"hello"});
+ n1.receive({payload:"world"});
+ });
+ });
+
it('should join things into an object after a count', function(done) {
var flow = [{id:"n1", type:"join", wires:[["n2"]], count:5, build:"object", mode:"custom"},
{id:"n2", type:"helper"}];
diff --git a/test/nodes/core/sequence/19-batch_spec.js b/test/nodes/core/sequence/19-batch_spec.js
index 3a40ebfb5..b2e1e6de2 100644
--- a/test/nodes/core/sequence/19-batch_spec.js
+++ b/test/nodes/core/sequence/19-batch_spec.js
@@ -107,13 +107,18 @@ describe('BATCH node', function() {
}
}
- function delayed_send(receiver, index, count, delay) {
+ function delayed_send(receiver, index, count, delay, done) {
if (index < count) {
setTimeout(function() {
receiver.receive({payload: index});
- delayed_send(receiver, index+1, count, delay);
+ delayed_send(receiver, index+1, count, delay, done);
}, delay);
}
+ else if(index === count) {
+ if (done) {
+ done();
+ }
+ }
}
function check_interval(flow, results, delay, done) {
@@ -198,10 +203,28 @@ describe('BATCH node', function() {
});
});
+ it('should handle reset', function(done) {
+ var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "count", count: 2, overlap: 0, interval: 0, allowEmptySequence: false, topics: [], wires:[["n2"]]},
+ {id:"n2", type:"helper"}];
+ helper.load(batchNode, flow, function() {
+ var n1 = helper.getNode("n1");
+ var n2 = helper.getNode("n2");
+ var results = [
+ [0, 1],
+ [4, 5]
+ ];
+ check_data(n1, n2, results, done);
+ n1.receive({payload:0});
+ n1.receive({payload:1});
+ n1.receive({payload:2});
+ n1.receive({payload:3, reset: true});
+ n1.receive({payload:4});
+ n1.receive({payload:5});
+ });
+ });
});
describe('mode: interval', function() {
-
it('should create seq. with interval', function(done) {
var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "interval", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [], wires:[["n2"]]},
{id:"n2", type:"helper"}];
@@ -265,10 +288,29 @@ describe('BATCH node', function() {
});
});
+ it('should handle reset', function(done) {
+ var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "interval", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [], wires:[["n2"]]},
+ {id:"n2", type:"helper"}];
+ helper.load(batchNode, flow, function() {
+ var n1 = helper.getNode("n1");
+ var n2 = helper.getNode("n2");
+ var results = [
+ [0, 1],
+ [4, 5]
+ ];
+ check_data(n1, n2, results, done);
+ delayed_send(n1, 0, 3, 400, function () {
+ setTimeout(function () {
+ n1.receive({payload: "3", reset: true});
+ delayed_send(n1, 4, 7, 400);
+ }, 10);
+ });
+ });
+ });
+
});
describe('mode: concat', function() {
-
it('should concat two seq. (series)', function(done) {
var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "concat", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [{topic: "TA"}, {topic: "TB"}], wires:[["n2"]]},
{id:"n2", type:"helper"}];
@@ -355,6 +397,58 @@ describe('BATCH node', function() {
});
});
+ it('should handle reset', function(done) {
+ var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "concat", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [{topic: "TA"}, {topic: "TB"}], wires:[["n2"]]},
+ {id:"n2", type:"helper"}];
+ try {
+ helper.load(batchNode, flow, function() {
+ var n1 = helper.getNode("n1");
+ var n2 = helper.getNode("n2");
+ var results = [
+ [2, 3, 0, 1]
+ ];
+ check_data(n1, n2, results, done);
+ var inputs0 = [
+ ["TB", 0, 0, 2],
+ ["TA", 1, 0, 2],
+ ];
+ for(var data of inputs0) {
+ var msg = {
+ topic: data[0],
+ payload: data[1],
+ parts: {
+ id: data[0],
+ index: data[2],
+ count: data[3]
+ }
+ };
+ n1.receive(msg);
+ }
+ n1.receive({payload: undefined, reset: true});
+ var inputs1 = [
+ ["TB", 0, 0, 2],
+ ["TB", 1, 1, 2],
+ ["TA", 2, 0, 2],
+ ["TA", 3, 1, 2]
+ ];
+ for(var data of inputs1) {
+ var msg = {
+ topic: data[0],
+ payload: data[1],
+ parts: {
+ id: data[0],
+ index: data[2],
+ count: data[3]
+ }
+ };
+ n1.receive(msg);
+ }
+ });
+ }
+ catch (e) {
+ done(e);
+ }
+ });
});
});
diff --git a/test/nodes/subflow/subflow_spec.js b/test/nodes/subflow/subflow_spec.js
index 39f5f6b8a..b60328296 100644
--- a/test/nodes/subflow/subflow_spec.js
+++ b/test/nodes/subflow/subflow_spec.js
@@ -447,117 +447,4 @@ describe('subflow', function() {
});
});
- it('should access env var type of subflow instance', function(done) {
- var flow = [
- {id:"t0", type:"tab", label:"", disabled:false, info:""},
- {id:"n1", x:10, y:10, z:"t0", type:"subflow:s1",
- env: [
- {name: "K", type: "str", value: "V"}
- ],
- wires:[["n2"]]},
- {id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]},
- // Subflow
- {id:"s1", type:"subflow", name:"Subflow", info:"",
- in:[{
- x:10, y:10,
- wires:[ {id:"s1-n1"} ]
- }],
- out:[{
- x:10, y:10,
- wires:[ {id:"s1-n1", port:0} ]
- }]
- },
- {id:"s1-n1", x:10, y:10, z:"s1", type:"function",
- func:"msg.V = env.get('K_type'); return msg;",
- wires:[]}
- ];
- helper.load(functionNode, flow, function() {
- var n1 = helper.getNode("n1");
- var n2 = helper.getNode("n2");
- n2.on("input", function(msg) {
- try {
- msg.should.have.property("V", "str");
- done();
- }
- catch (e) {
- console.log(e);
- done(e);
- }
- });
- n1.receive({payload:"foo"});
- });
- });
-
- it('should access env var info of subflow instance', function(done) {
- var flow = [
- {id:"t0", type:"tab", label:"", disabled:false, info:""},
- {id:"n1", x:10, y:10, z:"t0", type:"subflow:s1",
- env: [
- {name: "K", type: "str", value: "V"}
- ],
- wires:[["n2"]]},
- {id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]},
- // Subflow
- {id:"s1", type:"subflow", name:"Subflow", info:"",
- in:[{
- x:10, y:10,
- wires:[ {id:"s1-n1"} ]
- }],
- out:[{
- x:10, y:10,
- wires:[ {id:"s1-n1", port:0} ]
- }],
- env:[
- {
- name: "K", type: "str", value: "",
- ui: {
- hasUI: true,
- icon: "icon",
- labels: {
- "en-US": "label"
- },
- type: "input",
- inputTypes: {
- str: true
- }
- }
- }
- ]
- },
- {id:"s1-n1", x:10, y:10, z:"s1", type:"function",
- func:"msg.V = env.get('K_info'); return msg;",
- wires:[]}
- ];
- helper.load(functionNode, flow, function() {
- var n1 = helper.getNode("n1");
- var n2 = helper.getNode("n2");
- n2.on("input", function(msg) {
- try {
- msg.should.have.property("V");
- var v = msg.V;
- v.should.have.property("name", "K");
- v.should.have.property("value", "V");
- v.should.have.property("type", "str");
- v.should.have.property("ui");
- var ui = v.ui;
- ui.should.have.property("hasUI", true);
- ui.should.have.property("icon", "icon");
- ui.should.have.property("type", "input");
- ui.should.have.property("labels");
- var labels = ui.labels;
- labels.should.have.property("en-US", "label");
- ui.should.have.property("inputTypes");
- var types = ui.inputTypes;
- types.should.have.property("str", true);
- done();
- }
- catch (e) {
- console.log(e);
- done(e);
- }
- });
- n1.receive({payload:"foo"});
- });
- });
-
});
diff --git a/test/unit/@node-red/editor-api/lib/admin/nodes_spec.js b/test/unit/@node-red/editor-api/lib/admin/nodes_spec.js
index ef98fb677..0d373b8d0 100644
--- a/test/unit/@node-red/editor-api/lib/admin/nodes_spec.js
+++ b/test/unit/@node-red/editor-api/lib/admin/nodes_spec.js
@@ -227,7 +227,7 @@ describe("api/admin/nodes", function() {
});
request(app)
.post('/nodes')
- .send({module: 'foo',version:"1.2.3"})
+ .send({module: 'foo',version:"1.2.3",url:"https://example/foo-1.2.3.tgz"})
.expect(200)
.end(function(err,res) {
if (err) {
@@ -238,6 +238,7 @@ describe("api/admin/nodes", function() {
res.body.nodes[0].should.have.property("id","123");
opts.should.have.property("module","foo");
opts.should.have.property("version","1.2.3");
+ opts.should.have.property("url","https://example/foo-1.2.3.tgz");
done();
});
});
@@ -256,7 +257,7 @@ describe("api/admin/nodes", function() {
});
request(app)
.post('/nodes')
- .send({module: 'foo',version:"1.2.3"})
+ .send({module: 'foo',version:"1.2.3",url:"https://example/foo-1.2.3.tgz"})
.expect(400)
.end(function(err,res) {
if (err) {
diff --git a/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js b/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js
index d30f6198a..848aaf99d 100644
--- a/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js
+++ b/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js
@@ -129,6 +129,61 @@ describe("api/auth/strategies", function() {
})
});
+ describe("Tokens Strategy", function() {
+ it('Succeeds if tokens user enabled custom header',function(done) {
+ var userTokens = sinon.stub(Users,"tokens",function(token) {
+ return when.resolve("tokens-"+token);
+ });
+ var userTokenHeader = sinon.stub(Users,"tokenHeader",function(token) {
+ return "x-test-token";
+ });
+ strategies.tokensStrategy._success = strategies.tokensStrategy.success;
+ strategies.tokensStrategy.success = function(user) {
+ user.should.equal("tokens-1234");
+ strategies.tokensStrategy.success = strategies.tokensStrategy._success;
+ delete strategies.tokensStrategy._success;
+ done();
+ };
+ strategies.tokensStrategy.authenticate({headers:{"x-test-token":"1234"}});
+ });
+ it('Succeeds if tokens user enabled default header',function(done) {
+ var userTokens = sinon.stub(Users,"tokens",function(token) {
+ return when.resolve("tokens-"+token);
+ });
+ var userTokenHeader = sinon.stub(Users,"tokenHeader",function(token) {
+ return "authorization";
+ });
+ strategies.tokensStrategy._success = strategies.tokensStrategy.success;
+ strategies.tokensStrategy.success = function(user) {
+ user.should.equal("tokens-1234");
+ strategies.tokensStrategy.success = strategies.tokensStrategy._success;
+ delete strategies.tokensStrategy._success;
+ done();
+ };
+ strategies.tokensStrategy.authenticate({headers:{"authorization":"Bearer 1234"}});
+ });
+ it('Fails if tokens user not enabled',function(done) {
+ var userTokens = sinon.stub(Users,"tokens",function() {
+ return when.resolve(null);
+ });
+ var userTokenHeader = sinon.stub(Users,"tokenHeader",function(token) {
+ return "authorization";
+ });
+ strategies.tokensStrategy._fail = strategies.tokensStrategy.fail;
+ strategies.tokensStrategy.fail = function(err) {
+ err.should.equal(401);
+ strategies.tokensStrategy.fail = strategies.tokensStrategy._fail;
+ delete strategies.tokensStrategy._fail;
+ done();
+ };
+ strategies.tokensStrategy.authenticate({headers:{"authorization":"Bearer 1234"}});
+ });
+ afterEach(function() {
+ Users.tokens.restore();
+ Users.tokenHeader.restore();
+ })
+ });
+
describe("Bearer Strategy", function() {
it('Rejects invalid token',function(done) {
var getToken = sinon.stub(Tokens,"get",function(token) {
diff --git a/test/unit/@node-red/editor-api/lib/auth/users_spec.js b/test/unit/@node-red/editor-api/lib/auth/users_spec.js
index 515d23034..228163684 100644
--- a/test/unit/@node-red/editor-api/lib/auth/users_spec.js
+++ b/test/unit/@node-red/editor-api/lib/auth/users_spec.js
@@ -227,4 +227,47 @@ describe("api/auth/users", function() {
});
});
});
+
+ describe('Initialised with tokens set as function',function() {
+ before(function() {
+ Users.init({
+ type:"strategy",
+ tokens: function(token) { return("Done-"+token); }
+ });
+ });
+ after(function() {
+ Users.init({});
+ });
+ describe('#tokens',function() {
+ it('handles api.tokens being a function',function(done) {
+ Users.should.have.property('tokens').which.is.a.Function();
+ (Users.tokens("1234")).should.equal("Done-1234");
+ (Users.tokenHeader()).should.equal("authorization");
+ done();
+ });
+ });
+ });
+
+ describe('Initialised with tokens set as function and tokenHeader set as token header name',function() {
+ before(function() {
+ Users.init({
+ type:"strategy",
+ tokens: function(token) { return("Done-"+token); },
+ tokenHeader: "X-TEST-TOKEN"
+ });
+ });
+ after(function() {
+ Users.init({});
+ });
+ describe('#tokens',function() {
+ it('handles api.tokens being a function and api.tokenHeader being a header name',function(done) {
+ Users.should.have.property('tokens').which.is.a.Function();
+ (Users.tokens("1234")).should.equal("Done-1234");
+ Users.should.have.property('tokenHeader').which.is.a.Function();
+ (Users.tokenHeader()).should.equal("x-test-token");
+ done();
+ });
+ });
+ });
+
});
diff --git a/test/unit/@node-red/registry/lib/installer_spec.js b/test/unit/@node-red/registry/lib/installer_spec.js
index f3bae8b3a..045a81e0d 100644
--- a/test/unit/@node-red/registry/lib/installer_spec.js
+++ b/test/unit/@node-red/registry/lib/installer_spec.js
@@ -121,6 +121,17 @@ describe('nodes/registry/installer', function() {
done();
});
});
+ it("rejects when update requested to existing version and url", function(done) {
+ sinon.stub(typeRegistry,"getModuleInfo", function() {
+ return {
+ version: "0.1.1"
+ }
+ });
+ installer.installModule("this_wont_exist","0.1.1","https://example/foo-0.1.1.tgz").catch(function(err) {
+ err.code.should.be.eql('module_already_loaded');
+ done();
+ });
+ });
it("rejects with generic error", function(done) {
var res = {
code: 1,
@@ -201,6 +212,29 @@ describe('nodes/registry/installer', function() {
done(err);
});
});
+ it("succeeds when url is valid node-red module", function(done) {
+ var nodeInfo = {nodes:{module:"foo",types:["a"]}};
+
+ var res = {
+ code: 0,
+ stdout:"",
+ stderr:""
+ }
+ var p = Promise.resolve(res);
+ p.catch((err)=>{});
+ initInstaller(p)
+
+ var addModule = sinon.stub(registry,"addModule",function(md) {
+ return when.resolve(nodeInfo);
+ });
+
+ installer.installModule("this_wont_exist",null,"https://example/foo-0.1.1.tgz").then(function(info) {
+ info.should.eql(nodeInfo);
+ done();
+ }).catch(function(err) {
+ done(err);
+ });
+ });
});
describe("uninstalls module", function() {
diff --git a/test/unit/@node-red/runtime/lib/api/flows_spec.js b/test/unit/@node-red/runtime/lib/api/flows_spec.js
index a7c85efa2..dafbbc69b 100644
--- a/test/unit/@node-red/runtime/lib/api/flows_spec.js
+++ b/test/unit/@node-red/runtime/lib/api/flows_spec.js
@@ -53,7 +53,7 @@ describe("runtime-api/flows", function() {
var loadFlows;
var reloadError = false;
beforeEach(function() {
- setFlows = sinon.spy(function(flows,type) {
+ setFlows = sinon.spy(function(flows,credentials,type) {
if (flows[0] === "error") {
var err = new Error("error");
err.code = "error";
@@ -91,7 +91,19 @@ describe("runtime-api/flows", function() {
result.should.eql({rev:"newRev"});
setFlows.called.should.be.true();
setFlows.lastCall.args[0].should.eql([4,5,6]);
- setFlows.lastCall.args[1].should.eql("full");
+ setFlows.lastCall.args[2].should.eql("full");
+ done();
+ }).catch(done);
+ });
+ it("includes credentials when part of the request", function(done) {
+ flows.setFlows({
+ flows: {flows:[4,5,6], credentials: {$:"creds"}},
+ }).then(function(result) {
+ result.should.eql({rev:"newRev"});
+ setFlows.called.should.be.true();
+ setFlows.lastCall.args[0].should.eql([4,5,6]);
+ setFlows.lastCall.args[1].should.eql({$:"creds"});
+ setFlows.lastCall.args[2].should.eql("full");
done();
}).catch(done);
});
@@ -103,7 +115,7 @@ describe("runtime-api/flows", function() {
result.should.eql({rev:"newRev"});
setFlows.called.should.be.true();
setFlows.lastCall.args[0].should.eql([4,5,6]);
- setFlows.lastCall.args[1].should.eql("nodes");
+ setFlows.lastCall.args[2].should.eql("nodes");
done();
}).catch(done);
});
@@ -125,7 +137,7 @@ describe("runtime-api/flows", function() {
result.should.eql({rev:"newRev"});
setFlows.called.should.be.true();
setFlows.lastCall.args[0].should.eql([4,5,6]);
- setFlows.lastCall.args[1].should.eql("nodes");
+ setFlows.lastCall.args[2].should.eql("nodes");
done();
}).catch(done);
});
diff --git a/test/unit/@node-red/runtime/lib/nodes/context/index_spec.js b/test/unit/@node-red/runtime/lib/nodes/context/index_spec.js
index 3c9030b03..6fd76a421 100644
--- a/test/unit/@node-red/runtime/lib/nodes/context/index_spec.js
+++ b/test/unit/@node-red/runtime/lib/nodes/context/index_spec.js
@@ -32,17 +32,20 @@ describe('context', function() {
return Context.close();
});
it('stores local property',function() {
+ var flowContext = Context.getFlowContext("flowA")
var context1 = Context.get("1","flowA");
should.not.exist(context1.get("foo"));
context1.set("foo","test");
context1.get("foo").should.equal("test");
});
it('stores local property - creates parent properties',function() {
+ var flowContext = Context.getFlowContext("flowA")
var context1 = Context.get("1","flowA");
context1.set("foo.bar","test");
context1.get("foo").should.eql({bar:"test"});
});
it('deletes local property',function() {
+ var flowContext = Context.getFlowContext("flowA")
var context1 = Context.get("1","flowA");
context1.set("foo.abc.bar1","test1");
context1.set("foo.abc.bar2","test2");
@@ -55,12 +58,14 @@ describe('context', function() {
should.not.exist(context1.get("foo"));
});
it('stores flow property',function() {
+ var flowContext = Context.getFlowContext("flowA")
var context1 = Context.get("1","flowA");
should.not.exist(context1.flow.get("foo"));
context1.flow.set("foo","test");
context1.flow.get("foo").should.equal("test");
});
it('stores global property',function() {
+ var flowContext = Context.getFlowContext("flowA")
var context1 = Context.get("1","flowA");
should.not.exist(context1.global.get("foo"));
context1.global.set("foo","test");
@@ -68,6 +73,7 @@ describe('context', function() {
});
it('keeps local context local', function() {
+ var flowContext = Context.getFlowContext("flowA")
var context1 = Context.get("1","flowA");
var context2 = Context.get("2","flowA");
@@ -79,6 +85,7 @@ describe('context', function() {
should.not.exist(context2.get("foo"));
});
it('flow context accessible to all flow nodes', function() {
+ var flowContext = Context.getFlowContext("flowA")
var context1 = Context.get("1","flowA");
var context2 = Context.get("2","flowA");
@@ -91,6 +98,8 @@ describe('context', function() {
});
it('flow context not shared to nodes on other flows', function() {
+ var flowContextA = Context.getFlowContext("flowA")
+ var flowContextB = Context.getFlowContext("flowB")
var context1 = Context.get("1","flowA");
var context2 = Context.get("2","flowB");
@@ -103,6 +112,9 @@ describe('context', function() {
});
it('global context shared to all nodes', function() {
+ var flowContextA = Context.getFlowContext("flowA")
+ var flowContextB = Context.getFlowContext("flowB")
+
var context1 = Context.get("1","flowA");
var context2 = Context.get("2","flowB");
@@ -115,6 +127,7 @@ describe('context', function() {
});
it('context.flow/global are not enumerable', function() {
+ var flowContextA = Context.getFlowContext("flowA")
var context1 = Context.get("1","flowA");
Object.keys(context1).length.should.equal(0);
Object.keys(context1.flow).length.should.equal(0);
@@ -122,6 +135,7 @@ describe('context', function() {
})
it('context.flow/global cannot be deleted', function() {
+ var flowContextA = Context.getFlowContext("flowA")
var context1 = Context.get("1","flowA");
delete context1.flow;
should.exist(context1.flow);
@@ -130,6 +144,7 @@ describe('context', function() {
})
it('deletes context',function() {
+ var flowContextA = Context.getFlowContext("flowA")
var context = Context.get("1","flowA");
should.not.exist(context.get("foo"));
context.set("foo","abc");
@@ -142,6 +157,7 @@ describe('context', function() {
});
it('enumerates context keys - sync', function() {
+ var flowContextA = Context.getFlowContext("flowA")
var context = Context.get("1","flowA");
var keys = context.keys();
@@ -160,6 +176,7 @@ describe('context', function() {
});
it('enumerates context keys - async', function(done) {
+ var flowContextA = Context.getFlowContext("flowA")
var context = Context.get("1","flowA");
var keys = context.keys(function(err,keys) {
@@ -183,6 +200,7 @@ describe('context', function() {
it('should enumerate only context keys when GlobalContext was given - sync', function() {
Context.init({functionGlobalContext: {foo:"bar"}});
Context.load().then(function(){
+ var flowContextA = Context.getFlowContext("flowA")
var context = Context.get("1","flowA");
context.global.set("foo2","bar2");
var keys = context.global.keys();
@@ -195,6 +213,7 @@ describe('context', function() {
it('should enumerate only context keys when GlobalContext was given - async', function(done) {
Context.init({functionGlobalContext: {foo:"bar"}});
Context.load().then(function(){
+ var flowContextA = Context.getFlowContext("flowA")
var context = Context.get("1","flowA");
context.global.set("foo2","bar2");
context.global.keys(function(err,keys) {
@@ -210,6 +229,7 @@ describe('context', function() {
it('returns functionGlobalContext value if store value undefined', function() {
Context.init({functionGlobalContext: {foo:"bar"}});
return Context.load().then(function(){
+ var flowContextA = Context.getFlowContext("flowA")
var context = Context.get("1","flowA");
var v = context.global.get('foo');
v.should.equal('bar');
@@ -219,6 +239,7 @@ describe('context', function() {
it('returns functionGlobalContext sub-value if store value undefined', function() {
Context.init({functionGlobalContext: {foo:{bar:123}}});
return Context.load().then(function(){
+ var flowContextA = Context.getFlowContext("flowA")
var context = Context.get("1","flowA");
var v = context.global.get('foo.bar');
should.equal(v,123);
@@ -227,40 +248,67 @@ describe('context', function() {
describe("$parent", function() {
it('should get undefined for $parent without key', function() {
+ var flowContextA = Context.getFlowContext("flowA")
+ var flowContextB = Context.getFlowContext("flowB","flowA")
var context0 = Context.get("0","flowA");
- var context1 = Context.get("1","flowB", context0);
+ var context1 = Context.get("1","flowB");
var parent = context1.get("$parent");
should.equal(parent, undefined);
});
it('should get undefined for $parent of root', function() {
+ var flowContextA = Context.getFlowContext("flowA")
+ var flowContextB = Context.getFlowContext("flowB","flowA")
var context0 = Context.get("0","flowA");
- var context1 = Context.get("1","flowB", context0);
- var parent = context1.get("$parent.$parent.K");
+ var context1 = Context.get("1","flowB");
+ var parent = context1.flow.get("$parent.$parent.K");
should.equal(parent, undefined);
});
- it('should get value in $parent', function() {
+ it('should get undefined for $parent of root - callback', function(done) {
+ var flowContextA = Context.getFlowContext("flowA")
+ var flowContextB = Context.getFlowContext("flowB","flowA")
var context0 = Context.get("0","flowA");
- var context1 = Context.get("1","flowB", context0);
- context0.set("K", "v");
- var v = context1.get("$parent.K");
+ var context1 = Context.get("1","flowB");
+ context1.flow.get("$parent.$parent.K", function(err, result) {
+ try {
+ should.equal(err, undefined);
+ should.equal(result, undefined);
+ done();
+ } catch(err) {
+ done(err);
+ }
+ });
+
+ });
+
+ it('should get value in $parent', function() {
+ var flowContextA = Context.getFlowContext("flowA")
+ var flowContextB = Context.getFlowContext("flowB","flowA")
+ var context0 = Context.get("0","flowA");
+ var context1 = Context.get("1","flowB");
+ flowContextA.set("K", "v");
+ var v = context1.flow.get("$parent.K");
should.equal(v, "v");
});
it('should set value in $parent', function() {
+ var flowContextA = Context.getFlowContext("flowA")
+ var flowContextB = Context.getFlowContext("flowB","flowA")
var context0 = Context.get("0","flowA");
- var context1 = Context.get("1","flowB", context0);
- context1.set("$parent.K", "v");
- var v = context0.get("K");
+ var context1 = Context.get("1","flowB");
+ context1.flow.set("$parent.K", "v");
+ var v = flowContextA.get("K");
should.equal(v, "v");
});
it('should not contain $parent in keys', function() {
+ var flowContextA = Context.getFlowContext("flowA")
+ var flowContextB = Context.getFlowContext("flowB","flowA")
var context0 = Context.get("0","flowA");
- var context1 = Context.get("1","flowB", context0);
+ var context1 = Context.get("1","flowB");
var parent = context1.get("$parent");
- context0.set("K0", "v0");
+ flowContextA.set("K0", "v0");
context1.set("K1", "v1");
var keys = context1.keys();
keys.should.have.length(1);
@@ -366,6 +414,7 @@ describe('context', function() {
it('should ignore reserved storage name `_`', function(done) {
Context.init({contextStorage:{_:{module:testPlugin}}});
Context.load().then(function(){
+ var flowContext = Context.getFlowContext("flow")
var context = Context.get("1","flow");
var cb = function(){}
context.set("foo","bar","_",cb);
@@ -452,6 +501,7 @@ describe('context', function() {
Context.init({contextStorage:contextStorage});
var cb = function(){done("An error occurred")}
Context.load().then(function(){
+ var flowContext = Context.getFlowContext("flow");
var context = Context.get("1","flow");
context.set("foo","bar","test",cb);
context.get("foo","test",cb);
@@ -465,6 +515,7 @@ describe('context', function() {
it('should store flow property to external context storage',function(done) {
Context.init({contextStorage:contextStorage});
Context.load().then(function(){
+ var flowContext = Context.getFlowContext("flow");
var context = Context.get("1","flow");
var cb = function(){done("An error occurred")}
context.flow.set("foo","bar","test",cb);
@@ -479,6 +530,7 @@ describe('context', function() {
it('should store global property to external context storage',function(done) {
Context.init({contextStorage:contextStorage});
Context.load().then(function(){
+ var flowContext = Context.getFlowContext("flow");
var context = Context.get("1","flow");
var cb = function(){done("An error occurred")}
context.global.set("foo","bar","test",cb);
@@ -493,6 +545,7 @@ describe('context', function() {
it('should store data to the default context when non-existent context storage was specified', function(done) {
Context.init({contextStorage:contextDefaultStorage});
Context.load().then(function(){
+ var flowContext = Context.getFlowContext("flow");
var context = Context.get("1","flow");
var cb = function(){done("An error occurred")}
context.set("foo","bar","nonexist",cb);
@@ -510,6 +563,7 @@ describe('context', function() {
it('should use the default context', function(done) {
Context.init({contextStorage:contextDefaultStorage});
Context.load().then(function(){
+ var flowContext = Context.getFlowContext("flow");
var context = Context.get("1","flow");
var cb = function(){done("An error occurred")}
context.set("foo","bar","default",cb);
@@ -527,6 +581,7 @@ describe('context', function() {
it('should use the alias of default context', function(done) {
Context.init({contextStorage:contextDefaultStorage});
Context.load().then(function(){
+ var flowContext = Context.getFlowContext("flow");
var context = Context.get("1","flow");
var cb = function(){done("An error occurred")}
context.set("foo","alias",cb);
@@ -541,10 +596,11 @@ describe('context', function() {
done();
}).catch(done);
});
-
+
it('should allow the store name to be provide in the key', function(done) {
Context.init({contextStorage:contextDefaultStorage});
Context.load().then(function(){
+ var flowContext = Context.getFlowContext("flow");
var context = Context.get("1","flow");
var cb = function(){done("An error occurred")}
context.set("#:(test)::foo","bar");
@@ -561,6 +617,7 @@ describe('context', function() {
it('should use default as the alias of other context', function(done) {
Context.init({contextStorage:contextAlias});
Context.load().then(function(){
+ var flowContext = Context.getFlowContext("flow");
var context = Context.get("1","flow");
var cb = function(){done("An error occurred")}
context.set("foo","alias",cb);
@@ -575,6 +632,7 @@ describe('context', function() {
it('should not throw an error using undefined storage for local context', function(done) {
Context.init({contextStorage:contextStorage});
Context.load().then(function(){
+ var flowContext = Context.getFlowContext("flow");
var context = Context.get("1","flow");
var cb = function(){done("An error occurred")}
context.get("local","nonexist",cb);
@@ -584,6 +642,7 @@ describe('context', function() {
it('should throw an error using undefined storage for flow context', function(done) {
Context.init({contextStorage:contextStorage});
Context.load().then(function(){
+ var flowContext = Context.getFlowContext("flow");
var context = Context.get("1","flow");
var cb = function(){done("An error occurred")}
context.flow.get("flow","nonexist",cb);
@@ -595,6 +654,7 @@ describe('context', function() {
var fGC = { "foo": 456 };
Context.init({contextStorage:memoryStorage, functionGlobalContext:fGC });
Context.load().then(function() {
+ var flowContext = Context.getFlowContext("flow");
var context = Context.get("1","flow");
// Get foo - should be value from fGC
var v = context.global.get("foo");
@@ -615,6 +675,7 @@ describe('context', function() {
var fGC = { "foo": 456 };
Context.init({contextStorage:memoryStorage, functionGlobalContext:fGC });
Context.load().then(function() {
+ var flowContext = Context.getFlowContext("flow");
var context = Context.get("1","flow");
// Get foo - should be value from fGC
context.global.get("foo", function(err, v) {
@@ -647,6 +708,7 @@ describe('context', function() {
it('should return multiple values if key is an array', function(done) {
Context.init({contextStorage:memoryStorage});
Context.load().then(function(){
+ var flowContext = Context.getFlowContext("flow");
var context = Context.get("1","flow");
context.set("foo1","bar1","memory");
context.set("foo2","bar2","memory");
@@ -667,6 +729,7 @@ describe('context', function() {
var fGC = { "foo1": 456, "foo2": {"bar":789} };
Context.init({contextStorage:memoryStorage, functionGlobalContext:fGC });
Context.load().then(function(){
+ var flowContext = Context.getFlowContext("flow");
var context = Context.get("1","flow");
context.global.get(["foo1","foo2.bar","foo3"], "memory", function(err,foo1,foo2,foo3){
if (err) {
@@ -685,6 +748,7 @@ describe('context', function() {
Context.init({contextStorage:contextStorage});
stubGet.onFirstCall().callsArgWith(2, "error2", "bar1");
Context.load().then(function(){
+ var flowContext = Context.getFlowContext("flow")
var context = Context.get("1","flow");
context.global.get(["foo1","foo2","foo3"], "memory", function(err,foo1,foo2,foo3){
if (err === "error2") {
@@ -702,6 +766,7 @@ describe('context', function() {
stubGet.onSecondCall().callsArgWith(2, null, "bar2");
stubGet.onThirdCall().callsArgWith(2, "error3");
Context.load().then(function(){
+ var flowContext = Context.getFlowContext("flow");
var context = Context.get("1","flow");
context.get(["foo1","foo2","foo3"], "memory", function(err,foo1,foo2,foo3){
if (err === "error1") {
@@ -716,6 +781,7 @@ describe('context', function() {
it('should store multiple properties if key and value are arrays', function(done) {
Context.init({contextStorage:memoryStorage});
Context.load().then(function(){
+ var flowContext = Context.getFlowContext("flow");
var context = Context.get("1","flow");
context.set(["foo1","foo2","foo3"], ["bar1","bar2","bar3"], "memory", function(err){
if (err) {
@@ -739,6 +805,7 @@ describe('context', function() {
it('should deletes multiple properties', function(done) {
Context.init({contextStorage:memoryStorage});
Context.load().then(function(){
+ var flowContext = Context.getFlowContext("flow");
var context = Context.get("1","flow");
context.set(["foo1","foo2","foo3"], ["bar1","bar2","bar3"], "memory", function(err){
if (err) {
@@ -777,6 +844,7 @@ describe('context', function() {
it('should use null for missing values if the value array is shorter than the key array', function(done) {
Context.init({contextStorage:memoryStorage});
Context.load().then(function(){
+ var flowContext = Context.getFlowContext("flow");
var context = Context.get("1","flow");
context.set(["foo1","foo2","foo3"], ["bar1","bar2"], "memory", function(err){
if (err) {
@@ -804,6 +872,7 @@ describe('context', function() {
it('should use null for missing values if the value is not array', function(done) {
Context.init({contextStorage:memoryStorage});
Context.load().then(function(){
+ var flowContext = Context.getFlowContext("flow");
var context = Context.get("1","flow");
context.set(["foo1","foo2","foo3"], "bar1", "memory", function(err){
if (err) {
@@ -831,6 +900,7 @@ describe('context', function() {
it('should ignore the extra values if the value array is longer than the key array', function(done) {
Context.init({contextStorage:memoryStorage});
Context.load().then(function(){
+ var flowContext = Context.getFlowContext("flow");
var context = Context.get("1","flow");
context.set(["foo1","foo2","foo3"], ["bar1","bar2","bar3","ignored"], "memory", function(err){
if (err) {
@@ -859,6 +929,7 @@ describe('context', function() {
Context.init({contextStorage:contextStorage});
stubSet.onFirstCall().callsArgWith(3, "error2");
Context.load().then(function(){
+ var flowContext = Context.getFlowContext("flow");
var context = Context.get("1","flow");
context.set(["foo1","foo2","foo3"], ["bar1","bar2","bar3"], "memory", function(err){
if (err === "error2") {
@@ -873,6 +944,7 @@ describe('context', function() {
it('should throw an error if callback of context.get is not a function', function (done) {
Context.init({ contextStorage: memoryStorage });
Context.load().then(function () {
+ var flowContext = Context.getFlowContext("flow");
var context = Context.get("1", "flow");
context.get("foo", "memory", "callback");
done("should throw an error.");
@@ -884,6 +956,7 @@ describe('context', function() {
it('should not throw an error if callback of context.get is not specified', function (done) {
Context.init({ contextStorage: memoryStorage });
Context.load().then(function () {
+ var flowContext = Context.getFlowContext("flow");
var context = Context.get("1", "flow");
context.get("foo", "memory");
done();
@@ -893,6 +966,7 @@ describe('context', function() {
it('should throw an error if callback of context.set is not a function', function (done) {
Context.init({ contextStorage: memoryStorage });
Context.load().then(function () {
+ var flowContext = Context.getFlowContext("flow");
var context = Context.get("1", "flow");
context.set("foo", "bar", "memory", "callback");
done("should throw an error.");
@@ -904,6 +978,7 @@ describe('context', function() {
it('should not throw an error if callback of context.set is not specified', function (done) {
Context.init({ contextStorage: memoryStorage });
Context.load().then(function () {
+ var flowContext = Context.getFlowContext("flow");
var context = Context.get("1", "flow");
context.set("foo", "bar", "memory");
done();
@@ -913,6 +988,7 @@ describe('context', function() {
it('should throw an error if callback of context.keys is not a function', function (done) {
Context.init({ contextStorage: memoryStorage });
Context.load().then(function () {
+ var flowContext = Context.getFlowContext("flow");
var context = Context.get("1", "flow");
context.keys("memory", "callback");
done("should throw an error.");
@@ -924,6 +1000,7 @@ describe('context', function() {
it('should not throw an error if callback of context.keys is not specified', function (done) {
Context.init({ contextStorage: memoryStorage });
Context.load().then(function () {
+ var flowContext = Context.getFlowContext("flow");
var context = Context.get("1", "flow");
context.keys("memory");
done();
@@ -953,7 +1030,6 @@ describe('context', function() {
}).catch(done);
});
});
-
describe('delete context',function(){
it('should not call delete() when external context storage is used', function(done) {
Context.init({contextStorage:contextDefaultStorage});
diff --git a/test/unit/@node-red/runtime/lib/nodes/flows/index_spec.js b/test/unit/@node-red/runtime/lib/nodes/flows/index_spec.js
index a37d88f78..8865c5d3f 100644
--- a/test/unit/@node-red/runtime/lib/nodes/flows/index_spec.js
+++ b/test/unit/@node-red/runtime/lib/nodes/flows/index_spec.js
@@ -67,7 +67,10 @@ describe('flows/index', function() {
});
return when.resolve();
});
- credentialsLoad = sinon.stub(credentials,"load",function() {
+ credentialsLoad = sinon.stub(credentials,"load",function(creds) {
+ if (creds && creds.hasOwnProperty("$") && creds['$'] === "fail") {
+ return when.reject("creds error");
+ }
return when.resolve();
});
flowCreate = sinon.stub(Flow,"create",function(parent, global, flow) {
@@ -177,6 +180,23 @@ describe('flows/index', function() {
});
});
+ it('sets the full flow including credentials', function(done) {
+ var originalConfig = [
+ {id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
+ {id:"t1",type:"tab"}
+ ];
+ var credentials = {"t1-1":{"a":1}};
+
+ flows.init({log:mockLog, settings:{},storage:storage});
+ flows.setFlows(originalConfig,credentials).then(function() {
+ credentialsClean.called.should.be.false();
+ credentialsLoad.called.should.be.true();
+ credentialsLoad.lastCall.args[0].should.eql(credentials);
+ flows.getFlows().flows.should.eql(originalConfig);
+ done();
+ });
+ });
+
it('updates existing flows with partial deployment - nodes', function(done) {
var originalConfig = [
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
@@ -235,6 +255,20 @@ describe('flows/index', function() {
});
});
+ it('returns error if it cannot decrypt credentials', function(done) {
+ var originalConfig = [
+ {id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
+ {id:"t1",type:"tab"}
+ ];
+ var credentials = {"$":"fail"};
+
+ flows.init({log:mockLog, settings:{},storage:storage});
+ flows.setFlows(originalConfig,credentials).then(function() {
+ done("Unexpected success when credentials couldn't be decrypted")
+ }).catch(function(err) {
+ done();
+ });
+ });
});
describe('#load', function() {
diff --git a/test/unit/@node-red/runtime/lib/nodes/flows/util_spec.js b/test/unit/@node-red/runtime/lib/nodes/flows/util_spec.js
index ac8efef79..c20d222e2 100644
--- a/test/unit/@node-red/runtime/lib/nodes/flows/util_spec.js
+++ b/test/unit/@node-red/runtime/lib/nodes/flows/util_spec.js
@@ -150,7 +150,7 @@ describe('flows/util', function() {
{id:"t1",type:"tab"}
];
var parsedConfig = flowUtil.parseConfig(originalConfig);
- var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"t1":{"id":"t1","type":"tab"}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}}},"missingTypes":[]};
+ var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"t1":{"id":"t1","type":"tab"}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}}},"groups":{},"missingTypes":[]};
parsedConfig.should.eql(expectedConfig);
});
@@ -161,7 +161,7 @@ describe('flows/util', function() {
{id:"t1",type:"tab"}
];
var parsedConfig = flowUtil.parseConfig(originalConfig);
- var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]},"cn":{"id":"cn","type":"test"},"t1":{"id":"t1","type":"tab"}},"subflows":{},"configs":{"cn":{"id":"cn","type":"test","_users":["t1-1"]}},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]}}}},"missingTypes":[]};
+ var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]},"cn":{"id":"cn","type":"test"},"t1":{"id":"t1","type":"tab"}},"subflows":{},"configs":{"cn":{"id":"cn","type":"test","_users":["t1-1"]}},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]}}}},"groups":{},"missingTypes":[]};
parsedConfig.should.eql(expectedConfig);
});
@@ -173,7 +173,7 @@ describe('flows/util', function() {
{id:"t2-1",x:10,y:10,z:"t2",type:"test",wires:[]}
];
var parsedConfig = flowUtil.parseConfig(originalConfig);
- var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"t2":{"id":"t2","type":"tab"},"t2-1":{"id":"t2-1","x":10,"y":10,"z":"t2","type":"test","wires":[]}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}},"t2":{"id":"t2","type":"tab","subflows":{},"configs":{},"nodes":{"t2-1":{"id":"t2-1","x":10,"y":10,"z":"t2","type":"test","wires":[]}}}},"missingTypes":[]};
+ var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"t2":{"id":"t2","type":"tab"},"t2-1":{"id":"t2-1","x":10,"y":10,"z":"t2","type":"test","wires":[]}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}},"t2":{"id":"t2","type":"tab","subflows":{},"configs":{},"nodes":{"t2-1":{"id":"t2-1","x":10,"y":10,"z":"t2","type":"test","wires":[]}}}},"groups":{},"missingTypes":[]};
parsedConfig.should.eql(expectedConfig);
});
@@ -185,7 +185,7 @@ describe('flows/util', function() {
{id:"sf1-1",x:10,y:10,z:"sf1",type:"test",wires:[]}
];
var parsedConfig = flowUtil.parseConfig(originalConfig);
- var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[]},"sf1":{"id":"sf1","type":"subflow"},"sf1-1":{"id":"sf1-1","x":10,"y":10,"z":"sf1","type":"test","wires":[]}},"subflows":{"sf1":{"id":"sf1","type":"subflow","configs":{},"nodes":{"sf1-1":{"id":"sf1-1","x":10,"y":10,"z":"sf1","type":"test","wires":[]}},"instances":[{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[],"subflow":"sf1"}]}},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[],"subflow":"sf1"}}}},"missingTypes":[]};
+ var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[]},"sf1":{"id":"sf1","type":"subflow"},"sf1-1":{"id":"sf1-1","x":10,"y":10,"z":"sf1","type":"test","wires":[]}},"subflows":{"sf1":{"id":"sf1","type":"subflow","configs":{},"nodes":{"sf1-1":{"id":"sf1-1","x":10,"y":10,"z":"sf1","type":"test","wires":[]}},"instances":[{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[],"subflow":"sf1"}]}},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[],"subflow":"sf1"}}}},"groups":{},"missingTypes":[]};
parsedConfig.should.eql(expectedConfig);
});
@@ -197,7 +197,7 @@ describe('flows/util', function() {
];
var parsedConfig = flowUtil.parseConfig(originalConfig);
parsedConfig.missingTypes.should.eql(['missing']);
- var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"sf1","wires":[]},"t1-2":{"id":"t1-2","x":10,"y":10,"z":"t1","type":"missing","wires":[]}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"sf1","wires":[]},'t1-2': { id: 't1-2', x: 10, y: 10, z: 't1', type: 'missing', wires: [] }}}},"missingTypes":["missing"]};
+ var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"sf1","wires":[]},"t1-2":{"id":"t1-2","x":10,"y":10,"z":"t1","type":"missing","wires":[]}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"sf1","wires":[]},'t1-2': { id: 't1-2', x: 10, y: 10, z: 't1', type: 'missing', wires: [] }}}},"groups":{},"missingTypes":["missing"]};
redUtil.compareObjects(parsedConfig,expectedConfig).should.be.true();
});
@@ -207,10 +207,20 @@ describe('flows/util', function() {
{id:"cn",type:"test"},
];
var parsedConfig = flowUtil.parseConfig(originalConfig);
- var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]},"cn":{"id":"cn","type":"test"}},"subflows":{},"configs":{"cn":{"id":"cn","type":"test","_users":["t1-1"]}},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]}}}},"missingTypes":[]};
+ var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]},"cn":{"id":"cn","type":"test"}},"subflows":{},"configs":{"cn":{"id":"cn","type":"test","_users":["t1-1"]}},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]}}}},"groups":{},"missingTypes":[]};
parsedConfig.should.eql(expectedConfig);
});
+ it('parses a flow including a group', function() {
+ var originalConfig = [
+ {id:"t1",type:"tab"},
+ {id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
+ {id:"g1",type:"group",z:"t1"}
+ ];
+ var parsedConfig = flowUtil.parseConfig(originalConfig);
+ var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"g1":{"id":"g1","type":"group","z":"t1"}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}}},"groups":{"g1":{"id":"g1","type":"group","z":"t1"}},"missingTypes":[]}
+ parsedConfig.should.eql(expectedConfig);
+ });
});
diff --git a/test/unit/@node-red/runtime/lib/storage/localfilesystem/library_spec.js b/test/unit/@node-red/runtime/lib/storage/localfilesystem/library_spec.js
index 70152ea1c..69b6e3da6 100644
--- a/test/unit/@node-red/runtime/lib/storage/localfilesystem/library_spec.js
+++ b/test/unit/@node-red/runtime/lib/storage/localfilesystem/library_spec.js
@@ -72,45 +72,52 @@ describe('storage/localfilesystem/library', function() {
});
function createObjectLibrary(type) {
- type = type ||"object";
- var objLib = path.join(userDir,"lib",type);
+ type = type || "object";
+ var objLib = path.join(userDir, "lib", type);
try {
fs.mkdirSync(objLib);
- } catch(err) {
+ } catch (err) {
}
- fs.mkdirSync(path.join(objLib,"A"));
- fs.mkdirSync(path.join(objLib,"B"));
- fs.mkdirSync(path.join(objLib,"B","C"));
+ fs.mkdirSync(path.join(objLib, "A"));
+ fs.mkdirSync(path.join(objLib, "B"));
+ fs.mkdirSync(path.join(objLib, "B", "C"));
+ fs.mkdirSync(path.join(objLib, "D"));
if (type === "functions" || type === "object") {
- fs.writeFileSync(path.join(objLib,"file1.js"),"// abc: def\n// not a metaline \n\n Hi",'utf8');
- fs.writeFileSync(path.join(objLib,"B","file2.js"),"// ghi: jkl\n// not a metaline \n\n Hi",'utf8');
+ fs.writeFileSync(path.join(objLib, "file1.js"), "// abc: def\n// not a metaline \n\n Hi", 'utf8');
+ fs.writeFileSync(path.join(objLib, "B", "file2.js"), "// ghi: jkl\n// not a metaline \n\n Hi", 'utf8');
+ fs.writeFileSync(path.join(objLib, "D", "file3.js"), "// mno: 日本語テスト\n\nこんにちわ", 'utf8');
}
if (type === "flows" || type === "object") {
- fs.writeFileSync(path.join(objLib,"B","flow.json"),"Hi",'utf8');
+ fs.writeFileSync(path.join(objLib, "B", "flow.json"), "Hi", 'utf8');
}
}
- it('should return a directory listing of library objects',function(done) {
- localfilesystemLibrary.init({userDir:userDir}).then(function() {
+ it('should return a directory listing of library objects', function (done) {
+ localfilesystemLibrary.init({userDir: userDir}).then(function () {
createObjectLibrary();
- localfilesystemLibrary.getLibraryEntry('object','').then(function(flows) {
- flows.should.eql([ 'A', 'B', { abc: 'def', fn: 'file1.js' } ]);
- localfilesystemLibrary.getLibraryEntry('object','B').then(function(flows) {
- flows.should.eql([ 'C', { ghi: 'jkl', fn: 'file2.js' }, { fn: 'flow.json' } ]);
- localfilesystemLibrary.getLibraryEntry('object','B/C').then(function(flows) {
+ localfilesystemLibrary.getLibraryEntry('object', '').then(function (flows) {
+ flows.should.eql([ 'A', 'B', 'D', { abc: 'def', fn: 'file1.js' }]);
+ localfilesystemLibrary.getLibraryEntry('object', 'B').then(function (flows) {
+ flows.should.eql([ 'C', { ghi: 'jkl', fn: 'file2.js' }, { fn: 'flow.json' }]);
+ localfilesystemLibrary.getLibraryEntry('object', 'B/C').then(function (flows) {
flows.should.eql([]);
- done();
- }).catch(function(err) {
+ localfilesystemLibrary.getLibraryEntry('object', 'D').then(function (flows) {
+ flows.should.eql([{ mno: '日本語テスト', fn: 'file3.js' }]);
+ done();
+ }).catch(function (err) {
+ done(err);
+ });
+ }).catch(function (err) {
done(err);
});
- }).catch(function(err) {
+ }).catch(function (err) {
done(err);
});
- }).catch(function(err) {
+ }).catch(function (err) {
done(err);
});
- }).catch(function(err) {
+ }).catch(function (err) {
done(err);
});
});
@@ -203,4 +210,35 @@ describe('storage/localfilesystem/library', function() {
done(err);
});
});
+
+ it('should return a newly saved library flow (multi-byte character)',function(done) {
+ localfilesystemLibrary.init({userDir:userDir}).then(function() {
+ createObjectLibrary("flows");
+ localfilesystemLibrary.getLibraryEntry('flows','B').then(function(flows) {
+ flows.should.eql([ 'C', {fn:'flow.json'} ]);
+ var ft = path.join("B","D","file4");
+ localfilesystemLibrary.saveLibraryEntry('flows',ft,{mno:'pqr'},"こんにちわこんにちわこんにちわ").then(function() {
+ setTimeout(function() {
+ localfilesystemLibrary.getLibraryEntry('flows',path.join("B","D")).then(function(flows) {
+ flows.should.eql([ { mno: 'pqr', fn: 'file4.json' } ]);
+ localfilesystemLibrary.getLibraryEntry('flows',ft+".json").then(function(body) {
+ body.should.eql("こんにちわこんにちわこんにちわ");
+ done();
+ }).catch(function(err) {
+ done(err);
+ });
+ }).catch(function(err) {
+ done(err);
+ })
+ }, 50);
+ }).catch(function(err) {
+ done(err);
+ });
+ }).catch(function(err) {
+ done(err);
+ });
+ }).catch(function(err) {
+ done(err);
+ });
+ });
});
diff --git a/test/unit/@node-red/util/lib/util_spec.js b/test/unit/@node-red/util/lib/util_spec.js
index cf59df55e..f8c77d68a 100644
--- a/test/unit/@node-red/util/lib/util_spec.js
+++ b/test/unit/@node-red/util/lib/util_spec.js
@@ -143,6 +143,10 @@ describe("@node-red/util/util", function() {
cloned.req.should.equal(msg.req);
cloned.res.should.equal(msg.res);
});
+ it('handles undefined values without throwing an error', function() {
+ var result = util.cloneMessage(undefined);
+ should.not.exist(result);
+ })
});
describe('getObjectProperty', function() {
it('gets a property beginning with "msg."', function() {
@@ -495,12 +499,27 @@ describe("@node-red/util/util", function() {
var result = util.evaluateJSONataExpression(expr,{payload:"hello"});
result.should.eql("bar");
});
+ it('accesses undefined environment variable from an expression', function() {
+ var expr = util.prepareJSONataExpression('$env("UTIL_ENV")',{});
+ var result = util.evaluateJSONataExpression(expr,{});
+ result.should.eql('');
+ });
it('accesses environment variable from an expression', function() {
process.env.UTIL_ENV = 'foo';
var expr = util.prepareJSONataExpression('$env("UTIL_ENV")',{});
var result = util.evaluateJSONataExpression(expr,{});
result.should.eql('foo');
});
+ it('accesses moment from an expression', function() {
+ var expr = util.prepareJSONataExpression('$moment("2020-05-27", "YYYY-MM-DD").add("days", 7).add("months", 1).format("YYYY-MM-DD")',{});
+ var result = util.evaluateJSONataExpression(expr,{});
+ result.should.eql('2020-07-03');
+ });
+ it('accesses moment-timezone from an expression', function() {
+ var expr = util.prepareJSONataExpression('$moment("2013-11-18 11:55Z").tz("Asia/Taipei").format()',{});
+ var result = util.evaluateJSONataExpression(expr,{});
+ result.should.eql('2013-11-18T19:55:00+08:00');
+ });
it('handles non-existant flow context variable', function() {
var expr = util.prepareJSONataExpression('$flowContext("nonExistant")',{context:function() { return {flow:{get: function(key) { return {'foo':'bar'}[key]}}}}});
var result = util.evaluateJSONataExpression(expr,{payload:"hello"});
@@ -840,11 +859,11 @@ describe("@node-red/util/util", function() {
},
}
};
-
+
for (var i = 0; i < 1000; i++) {
msg.msg.obj.big += 'some more string ';
}
-
+
var result = util.encodeObject(msg);
result.format.should.eql("error");
var resultJson = JSON.parse(result.msg);
@@ -862,7 +881,7 @@ describe("@node-red/util/util", function() {
throw new Error('Exception in toString - should have been caught');
}
msg.msg.constructor = { name: "strangeobj" };
-
+
var result = util.encodeObject(msg);
var success = (result.msg.indexOf('[Type not printable]') >= 0);
success.should.eql(true);
@@ -872,11 +891,11 @@ describe("@node-red/util/util", function() {
var msg = {
msg: {
mystrangeobj:"hello",
- constructor: {
+ constructor: {
get name(){
throw new Error('Exception in constructor name');
}
- }
+ }
},
};
var result = util.encodeObject(msg);