',{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 539fb763d..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);
}
@@ -110,8 +120,69 @@ RED.view.tools = (function() {
}
}
+ 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());});
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 de9465340..6dee10bd7 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);
@@ -1659,8 +1929,10 @@ RED.view = (function() {
redraw();
} else if (moving_set.length > 0 || selected_link != null) {
var result;
+ var node;
var removedNodes = [];
var removedLinks = [];
+ var removedGroups = [];
var removedSubflowOutputs = [];
var removedSubflowInputs = [];
var removedSubflowStatus;
@@ -1668,11 +1940,27 @@ RED.view = (function() {
var startDirty = RED.nodes.dirty();
var startChanged = false;
+ var selectedGroups = [];
if (moving_set.length > 0) {
+
for (var i=0;i=0; i--) {
+ var g = selectedGroups[i];
+ removedGroups.push(g);
+ RED.nodes.removeGroup(g);
+ }
if (removedSubflowOutputs.length > 0) {
result = RED.subflow.removeOutput(removedSubflowOutputs);
if (result) {
@@ -1716,7 +2022,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 +2075,7 @@ RED.view = (function() {
t:"delete",
nodes:removedNodes,
links:removedLinks,
+ groups: removedGroups,
subflowOutputs:removedSubflowOutputs,
subflowInputs:removedSubflowInputs,
subflow: {
@@ -1801,20 +2108,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 +2679,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 +3079,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 +3108,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 +3182,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 +3365,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 +3431,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 +3542,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 +3570,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 +3803,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 +3825,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 +3838,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 +3911,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 +3950,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 +4137,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 +4338,6 @@ RED.view = (function() {
if (d3.event) {
d3.event.preventDefault();
}
-
}
function focusView() {
@@ -3474,29 +4375,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 +4467,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 +4500,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 +4561,25 @@ RED.view = (function() {
var historyEvents = [];
for (var i=0;i 0) {
@@ -3668,7 +4594,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 +4638,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 +4663,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 +4702,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 +4777,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) {
@@ -3850,6 +4833,11 @@ RED.view = (function() {
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..f51622b89 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,208 @@ 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;
+ }
+
+}
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..bb51d9969 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;
@@ -68,3 +71,18 @@
border-radius: 4px;
}
}
+.red-ui-searchBox-compact {
+ input.red-ui-searchBox-input,input:focus.red-ui-searchBox-input {
+ border: 1px solid $secondary-border-color;
+ border-radius: 3px;
+ font-size: 12px;
+ height: 26px;
+ }
+ i.fa-times,i.fa-search {
+ top: 7px;
+ }
+ .red-ui-searchBox-resultCount {
+ top: 3px;
+ padding: 0 6px;
+ }
+}
\ No newline at end of file
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 6a87cbf21..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
@@ -6,25 +6,30 @@
-
-
+
-
+
-
+
-
+
+
+
+
+
+
+
-
-
-
-
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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.js b/packages/node_modules/@node-red/nodes/core/function/90-exec.js
index a5132230e..f5e7edd87 100644
--- a/packages/node_modules/@node-red/nodes/core/function/90-exec.js
+++ b/packages/node_modules/@node-red/nodes/core/function/90-exec.js
@@ -145,7 +145,7 @@ module.exports = function(RED) {
if (error.signal) { msg3.payload.signal = error.signal; }
if (error.code === null) { node.status({fill:"red",shape:"dot",text:"killed"}); }
else { node.status({fill:"red",shape:"dot",text:"error:"+error.code}); }
- node.log('error:' + error);
+ if (RED.settings.verbose) { node.log('error:' + error); }
}
else if (node.oldrc === "false") {
msg3 = RED.util.cloneMessage(msg);
diff --git a/packages/node_modules/@node-red/nodes/core/network/10-mqtt.html b/packages/node_modules/@node-red/nodes/core/network/10-mqtt.html
index 0d16abdfc..c1b02e836 100644
--- a/packages/node_modules/@node-red/nodes/core/network/10-mqtt.html
+++ b/packages/node_modules/@node-red/nodes/core/network/10-mqtt.html
@@ -303,7 +303,7 @@
return this.name;
}
var b = this.broker;
- if (b === "") { b = "undefined"; }
+ if (!b) { b = "undefined"; }
var lab = "";
lab = (this.clientid?this.clientid+"@":"")+b;
if (b.indexOf("://") === -1){
diff --git a/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js b/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js
index 6f682b279..7ab624222 100644
--- a/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js
+++ b/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js
@@ -153,7 +153,12 @@ module.exports = function(RED) {
this.brokerurl="mqtt://";
}
if (this.broker !== "") {
- this.brokerurl = this.brokerurl+this.broker+":";
+ //Check for an IPv6 address
+ if (/(?:^|(?<=\s))(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))(?=\s|$)/.test(this.broker)) {
+ this.brokerurl = this.brokerurl+"["+this.broker+"]:";
+ } else {
+ this.brokerurl = this.brokerurl+this.broker+":";
+ }
// port now defaults to 1883 if unset.
if (!this.port){
this.brokerurl = this.brokerurl+"1883";
@@ -464,6 +469,7 @@ module.exports = function(RED) {
this.broker = n.broker;
this.brokerConn = RED.nodes.getNode(this.broker);
var node = this;
+ var chk = /[\+#]/;
if (this.brokerConn) {
this.status({fill:"red",shape:"ring",text:"node-red:common.status.disconnected"});
@@ -482,6 +488,7 @@ module.exports = function(RED) {
}
if ( msg.hasOwnProperty("payload")) {
if (msg.hasOwnProperty("topic") && (typeof msg.topic === "string") && (msg.topic !== "")) { // topic must exist
+ if (chk.test(msg.topic)) { node.warn(RED._("mqtt.errors.invalid-topic")); }
this.brokerConn.publish(msg, done); // send the message
} else {
node.warn(RED._("mqtt.errors.invalid-topic"));
diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js
index fc3fafd28..6cf243cf8 100644
--- a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js
+++ b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js
@@ -228,7 +228,7 @@ module.exports = function(RED) {
var diff = process.hrtime(startAt);
var ms = diff[0] * 1e3 + diff[1] * 1e-6;
var metricResponseTime = ms.toFixed(3);
- var metricContentLength = res._headers["content-length"];
+ var metricContentLength = res.getHeader("content-length");
//assuming that _id has been set for res._metrics in HttpOut node!
node.metric("response.time.millis", {_msgid:res._msgid} , metricResponseTime);
node.metric("response.content-length.bytes", {_msgid:res._msgid} , metricContentLength);
diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html
index 91555fb5c..96330ef2b 100644
--- a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html
+++ b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html
@@ -22,6 +22,7 @@
POST
PUT
DELETE
+
HEAD
@@ -32,8 +33,12 @@
-
-
+
+
+
+
+
+
@@ -167,6 +172,13 @@
$(".node-input-paytoqs-row").hide();
}
});
+ if (this.paytoqs === true || this.paytoqs == "query") {
+ $("#node-input-paytoqs").val("query");
+ } else if (this.paytoqs === "body") {
+ $("#node-input-paytoqs").val("body");
+ } else {
+ $("#node-input-paytoqs").val("ignore");
+ }
if (this.authType) {
$('#node-input-useAuth').prop('checked', true);
$("#node-input-authType-select").val(this.authType);
diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js
index 4c39f9f8c..fcb6a29df 100644
--- a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js
+++ b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js
@@ -28,7 +28,8 @@ module.exports = function(RED) {
var nodeUrl = n.url;
var isTemplatedUrl = (nodeUrl||"").indexOf("{{") != -1;
var nodeMethod = n.method || "GET";
- var paytoqs = n.paytoqs;
+ var paytoqs = false;
+ var paytobody = false;
var nodeHTTPPersistent = n["persist"];
if (n.tls) {
var tlsNode = RED.nodes.getNode(n.tls);
@@ -38,6 +39,10 @@ module.exports = function(RED) {
if (RED.settings.httpRequestTimeout) { this.reqTimeout = parseInt(RED.settings.httpRequestTimeout) || 120000; }
else { this.reqTimeout = 120000; }
+ if (n.paytoqs === true || n.paytoqs === "query") { paytoqs = true; }
+ else if (n.paytoqs === "body") { paytobody = true; }
+
+
var prox, noprox;
if (process.env.http_proxy) { prox = process.env.http_proxy; }
if (process.env.HTTP_PROXY) { prox = process.env.HTTP_PROXY; }
@@ -277,6 +282,14 @@ module.exports = function(RED) {
node.error(RED._("httpin.errors.invalid-payload"),msg);
nodeDone();
return;
+ }
+ } else if ( method == "GET" && typeof msg.payload !== "undefined" && paytobody) {
+ if (typeof msg.payload === "object") {
+ opts.body = JSON.stringify(msg.payload);
+ } else if (typeof msg.payload == "number") {
+ opts.body = msg.payload+"";
+ } else if (typeof msg.payload === "string" || Buffer.isBuffer(msg.payload)) {
+ opts.body = msg.payload;
}
}
diff --git a/packages/node_modules/@node-red/nodes/core/network/22-websocket.html b/packages/node_modules/@node-red/nodes/core/network/22-websocket.html
index e3d75e11f..523dc2629 100644
--- a/packages/node_modules/@node-red/nodes/core/network/22-websocket.html
+++ b/packages/node_modules/@node-red/nodes/core/network/22-websocket.html
@@ -163,7 +163,7 @@
if (root === "") {
$("#node-config-ws-tip").hide();
} else {
- $("#node-config-ws-path").html(root);
+ $("#node-config-ws-path").html(RED._("node-red:websocket.tip.path2", { path: root }));
$("#node-config-ws-tip").show();
}
}
@@ -235,7 +235,7 @@
diff --git a/packages/node_modules/@node-red/nodes/core/network/31-tcpin.html b/packages/node_modules/@node-red/nodes/core/network/31-tcpin.html
index e2786b9da..ae0eaedda 100644
--- a/packages/node_modules/@node-red/nodes/core/network/31-tcpin.html
+++ b/packages/node_modules/@node-red/nodes/core/network/31-tcpin.html
@@ -170,15 +170,14 @@
$("#node-input-port-row").hide();
$("#node-input-host-row").hide();
$("#node-input-end-row").hide();
+ } else if (sockettype == "client"){
+ $("#node-input-port-row").show();
+ $("#node-input-host-row").show();
+ $("#node-input-end-row").show();
} else {
$("#node-input-port-row").show();
- $("#node-input-end-row").show();
- }
-
- if (sockettype == "client") {
- $("#node-input-host-row").show();
- } else {
$("#node-input-host-row").hide();
+ $("#node-input-end-row").show();
}
};
updateOptions();
diff --git a/packages/node_modules/@node-red/nodes/core/network/31-tcpin.js b/packages/node_modules/@node-red/nodes/core/network/31-tcpin.js
index e52e9c382..655e72c1e 100644
--- a/packages/node_modules/@node-red/nodes/core/network/31-tcpin.js
+++ b/packages/node_modules/@node-red/nodes/core/network/31-tcpin.js
@@ -74,7 +74,7 @@ module.exports = function(RED) {
buffer = (node.datatype == 'buffer') ? Buffer.alloc(0) : "";
node.connected = true;
node.log(RED._("tcpin.status.connected",{host:node.host,port:node.port}));
- node.status({fill:"green",shape:"dot",text:"common.status.connected"});
+ node.status({fill:"green",shape:"dot",text:"common.status.connected",_session:{type:"tcp",id:id}});
});
client.setKeepAlive(true,120000);
connectionPool[id] = client;
@@ -121,7 +121,7 @@ module.exports = function(RED) {
client.on('close', function() {
delete connectionPool[id];
node.connected = false;
- node.status({fill:"red",shape:"ring",text:"common.status.disconnected"});
+ node.status({fill:"red",shape:"ring",text:"common.status.disconnected",_session:{type:"tcp",id:id}});
if (!node.closing) {
if (end) { // if we were asked to close then try to reconnect once very quick.
end = false;
diff --git a/packages/node_modules/@node-red/nodes/core/network/32-udp.js b/packages/node_modules/@node-red/nodes/core/network/32-udp.js
index 60e9bec08..894c19d77 100644
--- a/packages/node_modules/@node-red/nodes/core/network/32-udp.js
+++ b/packages/node_modules/@node-red/nodes/core/network/32-udp.js
@@ -180,6 +180,10 @@ module.exports = function(RED) {
node.tout = setTimeout(function() {
if (udpInputPortsInUse[p]) {
sock = udpInputPortsInUse[p];
+ if (node.multicast != "false") {
+ sock.setBroadcast(true);
+ sock.setMulticastLoopback(false);
+ }
node.log(RED._("udp.status.re-use",{outport:node.outport,host:node.addr,port:node.port}));
if (node.iface) { node.status({text:n.iface+" : "+node.iface}); }
}
diff --git a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.html b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.html
index 2e4ca0dd7..80778ac1b 100644
--- a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.html
+++ b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.html
@@ -28,11 +28,15 @@
-
+
+
+
+
+
@@ -45,8 +49,13 @@
-
-
+
+
+
+
+
+
+
@@ -69,12 +78,14 @@
sep: {value:',',required:true,validate:RED.validators.regex(/^.{1,2}$/)},
//quo: {value:'"',required:true},
hdrin: {value:""},
- hdrout: {value:""},
+ hdrout: {value:"none"},
multi: {value:"one",required:true},
ret: {value:'\\n'},
temp: {value:""},
skip: {value:"0"},
- strings: {value:true}
+ strings: {value:true},
+ include_empty_strings: {value:""},
+ include_null_values: {value:""}
},
inputs:1,
outputs:1,
@@ -86,6 +97,8 @@
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
+ if (this.hdrout === false) { this.hdrout = "none"; $("#node-input-hdrout").val("none"); }
+ if (this.hdrout === true) { this.hdrout = "all"; $("#node-input-hdrout").val("all");}
if (this.strings === undefined) { this.strings = true; $("#node-input-strings").prop('checked', true); }
if (this.skip === undefined) { this.skip = 0; $("#node-input-skip").val("0");}
$("#node-input-skip").spinner({ min:0 });
diff --git a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js
index 290b159a9..15c16da13 100644
--- a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js
+++ b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js
@@ -26,12 +26,16 @@ module.exports = function(RED) {
this.lineend = "\n";
this.multi = n.multi || "one";
this.hdrin = n.hdrin || false;
- this.hdrout = n.hdrout || false;
+ this.hdrout = n.hdrout || "none";
this.goodtmpl = true;
this.skip = parseInt(n.skip || 0);
this.store = [];
this.parsestrings = n.strings;
+ this.include_empty_strings = n.include_empty_strings || false;
+ this.include_null_values = n.include_null_values || false;
if (this.parsestrings === undefined) { this.parsestrings = true; }
+ if (this.hdrout === false) { this.hdrout = "none"; }
+ if (this.hdrout === true) { this.hdrout = "all"; }
var tmpwarn = true;
var node = this;
@@ -49,14 +53,22 @@ module.exports = function(RED) {
return col;
}
node.template = clean(node.template);
+ node.hdrSent = false;
this.on("input", function(msg) {
+ if (msg.hasOwnProperty("reset")) {
+ node.hdrSent = false;
+ }
if (msg.hasOwnProperty("payload")) {
if (typeof msg.payload == "object") { // convert object to CSV string
try {
var ou = "";
- if (node.hdrout) {
+ if (node.hdrout !== "none" && node.hdrSent === false) {
+ if ((node.template.length === 1) && (node.template[0] === '') && (msg.hasOwnProperty("columns"))) {
+ node.template = clean((msg.columns || "").split(","));
+ }
ou += node.template.join(node.sep) + node.ret;
+ if (node.hdrout === "once") { node.hdrSent = true; }
}
if (!Array.isArray(msg.payload)) { msg.payload = [ msg.payload ]; }
for (var s = 0; s < msg.payload.length; s++) {
@@ -75,13 +87,15 @@ module.exports = function(RED) {
ou += msg.payload[s].join(node.sep) + node.ret;
}
else {
+ if ((node.template.length === 1) && (node.template[0] === '') && (msg.hasOwnProperty("columns"))) {
+ node.template = clean((msg.columns || "").split(","));
+ }
if ((node.template.length === 1) && (node.template[0] === '')) {
/* istanbul ignore else */
if (tmpwarn === true) { // just warn about missing template once
node.warn(RED._("csv.errors.obj_csv"));
tmpwarn = false;
}
- ou = "";
for (var p in msg.payload[0]) {
/* istanbul ignore else */
if (msg.payload[0].hasOwnProperty(p)) {
@@ -125,6 +139,7 @@ module.exports = function(RED) {
}
}
msg.payload = ou;
+ msg.columns = node.template.join(',');
if (msg.payload !== '') { node.send(msg); }
}
catch(e) { node.error(e,msg); }
@@ -173,20 +188,29 @@ module.exports = function(RED) {
}
else if ((line[i] === node.sep) && f) { // if it is the end of the line then finish
if (!node.goodtmpl) { node.template[j] = "col"+(j+1); }
- if ( node.template[j] && (node.template[j] !== "") && (k[j] !== "" ) ) {
- if ( (node.parsestrings === true) && reg.test(k[j]) ) { k[j] = parseFloat(k[j]); }
- o[node.template[j]] = k[j];
+ if ( node.template[j] && (node.template[j] !== "") ) {
+ // if no value between separators ('1,,"3"...') or if the line beings with separator (',1,"2"...') treat value as null
+ if (line[i-1] === node.sep || line[i-1].includes('\n','\r')) k[j] = null;
+ if ( (k[j] !== null && node.parsestrings === true) && reg.test(k[j]) ) { k[j] = parseFloat(k[j]); }
+ if (node.include_null_values && k[j] === null) o[node.template[j]] = k[j];
+ if (node.include_empty_strings && k[j] === "") o[node.template[j]] = k[j];
+ if (k[j] !== null && k[j] !== "") o[node.template[j]] = k[j];
}
j += 1;
- k[j] = "";
+ // if separator is last char in processing string line (without end of line), add null value at the end - example: '1,2,3\n3,"3",'
+ k[j] = line.length - 1 === i ? null : "";
}
- else if ((line[i] === "\n") || (line[i] === "\r")) { // handle multiple lines
+ else if (((line[i] === "\n") || (line[i] === "\r")) && f) { // handle multiple lines
//console.log(j,k,o,k[j]);
if (!node.goodtmpl) { node.template[j] = "col"+(j+1); }
- if ( node.template[j] && (node.template[j] !== "") && (k[j] !== "") ) {
- if ( (node.parsestrings === true) && reg.test(k[j]) ) { k[j] = parseFloat(k[j]); }
- else { k[j].replace(/\r$/,''); }
- o[node.template[j]] = k[j];
+ if ( node.template[j] && (node.template[j] !== "") ) {
+ // if separator before end of line, set null value ie. '1,2,"3"\n1,2,\n1,2,3'
+ if (line[i-1] === node.sep) k[j] = null;
+ if ( (k[j] !== null && node.parsestrings === true) && reg.test(k[j]) ) { k[j] = parseFloat(k[j]); }
+ else { if (k[j] !== null) k[j].replace(/\r$/,''); }
+ if (node.include_null_values && k[j] === null) o[node.template[j]] = k[j];
+ if (node.include_empty_strings && k[j] === "") o[node.template[j]] = k[j];
+ if (k[j] !== null && k[j] !== "") o[node.template[j]] = k[j];
}
if (JSON.stringify(o) !== "{}") { // don't send empty objects
a.push(o); // add to the array
@@ -202,17 +226,21 @@ module.exports = function(RED) {
}
}
// Finished so finalize and send anything left
- //console.log(j,k,o,k[j]);
+ if (f === false) { node.warn(RED._("csv.errors.bad_csv")); }
if (!node.goodtmpl) { node.template[j] = "col"+(j+1); }
- if ( node.template[j] && (node.template[j] !== "") && (k[j] !== "") ) {
- if ( (node.parsestrings === true) && reg.test(k[j]) ) { k[j] = parseFloat(k[j]); }
- else { k[j].replace(/\r$/,''); }
- o[node.template[j]] = k[j];
+
+ if ( node.template[j] && (node.template[j] !== "") ) {
+ if ( (k[j] !== null && node.parsestrings === true) && reg.test(k[j]) ) { k[j] = parseFloat(k[j]); }
+ else { if (k[j] !== null) k[j].replace(/\r$/,''); }
+ if (node.include_null_values && k[j] === null) o[node.template[j]] = k[j];
+ if (node.include_empty_strings && k[j] === "") o[node.template[j]] = k[j];
+ if (k[j] !== null && k[j] !== "") o[node.template[j]] = k[j];
}
if (JSON.stringify(o) !== "{}") { // don't send empty objects
a.push(o); // add to the array
}
var has_parts = msg.hasOwnProperty("parts");
+
if (node.multi !== "one") {
msg.payload = a;
if (has_parts) {
@@ -221,12 +249,14 @@ module.exports = function(RED) {
}
if (msg.parts.index + 1 === msg.parts.count) {
msg.payload = node.store;
+ msg.columns = node.template.filter(val => val).join(',');
delete msg.parts;
node.send(msg);
node.store = [];
}
}
else {
+ msg.columns = node.template.filter(val => val).join(',');
node.send(msg); // finally send the array
}
}
@@ -234,6 +264,7 @@ module.exports = function(RED) {
var len = a.length;
for (var i = 0; i < len; i++) {
var newMessage = RED.util.cloneMessage(msg);
+ newMessage.columns = node.template.filter(val => val).join(',');
newMessage.payload = a[i];
if (!has_parts) {
newMessage.parts = {
@@ -259,7 +290,11 @@ module.exports = function(RED) {
}
else { node.warn(RED._("csv.errors.csv_js")); }
}
- else { node.send(msg); } // If no payload - just pass it on.
+ else {
+ if (!msg.hasOwnProperty("reset")) {
+ node.send(msg); // If no payload and not reset - just pass it on.
+ }
+ }
});
}
RED.nodes.registerType("csv",CSVNode);
diff --git a/packages/node_modules/@node-red/nodes/core/parsers/70-HTML.html b/packages/node_modules/@node-red/nodes/core/parsers/70-HTML.html
index dd16d395f..4509dd054 100644
--- a/packages/node_modules/@node-red/nodes/core/parsers/70-HTML.html
+++ b/packages/node_modules/@node-red/nodes/core/parsers/70-HTML.html
@@ -1,7 +1,7 @@
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/examples/batch/1_number-mode.json b/packages/node_modules/@node-red/nodes/examples/batch/1_number-mode.json
new file mode 100644
index 000000000..b7b919c9e
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/examples/batch/1_number-mode.json
@@ -0,0 +1 @@
+[{"id":"bf16276d.2f1758","type":"tab","label":"Example: Number-based Group Mode","disabled":false,"info":"*Number-based Group mode* of batch node can be used to create new message sequences from incoming messages. Recently received *N*-messages are grouped to a sequence. Creating message sequences that has overwrap with adjacent message group is possible.\n"},{"id":"f5a82278.78d6c","type":"batch","z":"bf16276d.2f1758","name":"","mode":"count","count":"5","overlap":0,"interval":"5","allowEmptySequence":false,"topics":[],"x":370,"y":232,"wires":[["b1e514ed.44f328"]]},{"id":"43720065.2891d","type":"comment","z":"bf16276d.2f1758","name":"Group 5 consecutive messages","info":"","x":170,"y":60,"wires":[]},{"id":"b1e514ed.44f328","type":"join","z":"bf16276d.2f1758","name":"","mode":"auto","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":"false","timeout":"","count":"","reduceRight":false,"x":510,"y":232,"wires":[["457e5970.8ceaa8"]]},{"id":"457e5970.8ceaa8","type":"debug","z":"bf16276d.2f1758","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":670,"y":232,"wires":[]},{"id":"8e3d3ceb.bd0fe","type":"comment","z":"bf16276d.2f1758","name":"↑ create message sequence with 5 messages","info":"","x":490,"y":272,"wires":[]},{"id":"fbe20ae3.cbb6f8","type":"comment","z":"bf16276d.2f1758","name":"↓ join sequence to array","info":"","x":560,"y":192,"wires":[]},{"id":"7ebafe58.2a112","type":"inject","z":"bf16276d.2f1758","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":140,"y":152,"wires":[["589603c4.1cb5fc"]]},{"id":"589603c4.1cb5fc","type":"function","z":"bf16276d.2f1758","name":"send: 0-49","func":"for(var x = 0; x < 50; x++) {\n node.send({payload: x});\n}","outputs":1,"noerr":0,"x":310,"y":152,"wires":[["f5a82278.78d6c"]]},{"id":"9b59b72c.fcd8f8","type":"comment","z":"bf16276d.2f1758","name":"↓ send sequence: 0-49","info":"","x":340,"y":112,"wires":[]},{"id":"6421756f.6abd5c","type":"batch","z":"bf16276d.2f1758","name":"","mode":"count","count":"5","overlap":"1","interval":"5","allowEmptySequence":false,"topics":[],"x":370,"y":500,"wires":[["199cf232.743e7e"]]},{"id":"657b6a53.2a1fc4","type":"comment","z":"bf16276d.2f1758","name":"Group 5 consecutive messages with overlap of 1 msg","info":"","x":240,"y":328,"wires":[]},{"id":"199cf232.743e7e","type":"join","z":"bf16276d.2f1758","name":"","mode":"auto","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":"false","timeout":"","count":"","reduceRight":false,"x":510,"y":500,"wires":[["91d29dda.d8a4f"]]},{"id":"91d29dda.d8a4f","type":"debug","z":"bf16276d.2f1758","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":670,"y":500,"wires":[]},{"id":"8bd4d407.aa2af8","type":"comment","z":"bf16276d.2f1758","name":"↑ create message sequence with 5 messages with overlap of 1 msg","info":"","x":560,"y":540,"wires":[]},{"id":"a49ea57d.8d2458","type":"comment","z":"bf16276d.2f1758","name":"↓ join sequence to array","info":"","x":560,"y":460,"wires":[]},{"id":"f689d1b3.90e4b","type":"inject","z":"bf16276d.2f1758","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":140,"y":420,"wires":[["c021cf24.ad03e"]]},{"id":"c021cf24.ad03e","type":"function","z":"bf16276d.2f1758","name":"send: 0-49","func":"for(var x = 0; x < 50; x++) {\n node.send({payload: x});\n}","outputs":1,"noerr":0,"x":310,"y":420,"wires":[["6421756f.6abd5c"]]},{"id":"8da6e576.901a18","type":"comment","z":"bf16276d.2f1758","name":"↓ send sequence: 0-49","info":"","x":340,"y":380,"wires":[]}]
\ No newline at end of file
diff --git a/packages/node_modules/@node-red/nodes/examples/batch/2_time-mode.json b/packages/node_modules/@node-red/nodes/examples/batch/2_time-mode.json
new file mode 100644
index 000000000..87fe70f0a
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/examples/batch/2_time-mode.json
@@ -0,0 +1 @@
+[{"id":"82a01f29.86de","type":"tab","label":"Example: Time-based Group Mode","disabled":false,"info":"*Time-based Group mode* of batch node can be used to create new message sequences from incoming messages received within specified time range. \n"},{"id":"9a7f6539.6e36d8","type":"batch","z":"82a01f29.86de","name":"","mode":"interval","count":10,"overlap":0,"interval":"5","allowEmptySequence":false,"topics":[],"x":350,"y":260,"wires":[["e54a3b57.3677f8"]]},{"id":"71dc607e.98aab","type":"comment","z":"82a01f29.86de","name":"Group messages received within 5s","info":"","x":180,"y":80,"wires":[]},{"id":"e54a3b57.3677f8","type":"join","z":"82a01f29.86de","name":"","mode":"auto","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":"false","timeout":"","count":"","reduceRight":false,"x":490,"y":260,"wires":[["1e263cac.6641f3"]]},{"id":"1e263cac.6641f3","type":"debug","z":"82a01f29.86de","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":650,"y":260,"wires":[]},{"id":"324c7b93.0db734","type":"comment","z":"82a01f29.86de","name":"↑ create message sequence received within 5s","info":"","x":480,"y":300,"wires":[]},{"id":"fb112bae.fbe428","type":"comment","z":"82a01f29.86de","name":"↓ join sequence to array","info":"","x":540,"y":220,"wires":[]},{"id":"34f8dda5.2864c2","type":"inject","z":"82a01f29.86de","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":140,"y":160,"wires":[["c9e23ee4.ce535"]]},{"id":"c9e23ee4.ce535","type":"function","z":"82a01f29.86de","name":"send: 0-49","func":"for(var x = 0; x < 100; x++) {\n node.send({payload: x});\n}","outputs":1,"noerr":0,"x":310,"y":160,"wires":[["7026e0cc.4e3c3"]]},{"id":"7026e0cc.4e3c3","type":"delay","z":"82a01f29.86de","name":"","pauseType":"rate","timeout":"1","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":490,"y":160,"wires":[["9a7f6539.6e36d8"]]},{"id":"40f8c766.6ed198","type":"comment","z":"82a01f29.86de","name":"↓ send sequence: 0-49","info":"","x":340,"y":120,"wires":[]}]
\ No newline at end of file
diff --git a/packages/node_modules/@node-red/nodes/examples/batch/3_concatenate-mode.json b/packages/node_modules/@node-red/nodes/examples/batch/3_concatenate-mode.json
new file mode 100644
index 000000000..819ae7966
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/examples/batch/3_concatenate-mode.json
@@ -0,0 +1 @@
+[{"id":"845b226d.a4b18","type":"tab","label":"Example: Concatenate Mode","disabled":false,"info":"*Concatenate mode* of batch node can be used to combine input message sequences to create a new message sequence. Order of the sequences can be specified using message topic assigned to each message in a sequence. Message sequence can be specified multiple times.\n"},{"id":"72afe7b0.38b9d8","type":"inject","z":"845b226d.a4b18","name":"","topic":"SEQ","payload":"[1,2,3,4,5]","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":160,"y":100,"wires":[["6dea90dd.c442c"]]},{"id":"6dea90dd.c442c","type":"split","z":"845b226d.a4b18","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":330,"y":100,"wires":[["3ac93a4b.ddbbc6"]]},{"id":"3ac93a4b.ddbbc6","type":"batch","z":"845b226d.a4b18","name":"","mode":"concat","count":10,"overlap":0,"interval":10,"allowEmptySequence":false,"topics":[{"topic":"SEQ"},{"topic":"SEQ"}],"x":470,"y":100,"wires":[["48ec7040.56f5f"]]},{"id":"48ec7040.56f5f","type":"join","z":"845b226d.a4b18","name":"","mode":"auto","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":"false","timeout":"","count":"","reduceRight":false,"x":610,"y":100,"wires":[["902613c4.769b5"]]},{"id":"902613c4.769b5","type":"debug","z":"845b226d.a4b18","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":770,"y":100,"wires":[]},{"id":"a84cf2e1.65adc","type":"comment","z":"845b226d.a4b18","name":"Duplicate","info":"","x":100,"y":60,"wires":[]},{"id":"3256f015.45c36","type":"inject","z":"845b226d.a4b18","name":"","topic":"SEQ","payload":"[1,-6,-8,7,2,-3]","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":160,"y":220,"wires":[["c308dcb2.621da"]]},{"id":"c308dcb2.621da","type":"split","z":"845b226d.a4b18","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":330,"y":220,"wires":[["2222098b.7fd036"]]},{"id":"247a5fab.239cc","type":"comment","z":"845b226d.a4b18","name":"Filter & Concat","info":"","x":120,"y":180,"wires":[]},{"id":"2222098b.7fd036","type":"switch","z":"845b226d.a4b18","name":"","property":"payload","propertyType":"msg","rules":[{"t":"gt","v":"0","vt":"num"},{"t":"else"}],"checkall":"true","repair":true,"outputs":2,"x":390,"y":280,"wires":[["56e3a974.2bfde8"],["86a1b43a.ff4cb8"]]},{"id":"cd00a796.e4e478","type":"comment","z":"845b226d.a4b18","name":"↑ Duplicate SEQ","info":"","x":500,"y":140,"wires":[]},{"id":"56e3a974.2bfde8","type":"change","z":"845b226d.a4b18","name":"Topic←POS","rules":[{"t":"set","p":"topic","pt":"msg","to":"POS","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":550,"y":240,"wires":[["fe90d65d.a6b548"]]},{"id":"86a1b43a.ff4cb8","type":"change","z":"845b226d.a4b18","name":"Topic←NEG","rules":[{"t":"set","p":"topic","pt":"msg","to":"NEG","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":550,"y":300,"wires":[["fe90d65d.a6b548"]]},{"id":"fe90d65d.a6b548","type":"batch","z":"845b226d.a4b18","name":"","mode":"concat","count":10,"overlap":0,"interval":10,"allowEmptySequence":false,"topics":[{"topic":"NEG"},{"topic":"POS"}],"x":710,"y":280,"wires":[["5b089f16.62b96"]]},{"id":"2f46c0af.bff71","type":"debug","z":"845b226d.a4b18","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":930,"y":220,"wires":[]},{"id":"5b089f16.62b96","type":"join","z":"845b226d.a4b18","name":"","mode":"auto","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":"false","timeout":"","count":"","reduceRight":false,"x":770,"y":220,"wires":[["2f46c0af.bff71"]]},{"id":"e069eb28.6eb358","type":"comment","z":"845b226d.a4b18","name":"↑ Order sequence: negative→positive","info":"","x":810,"y":320,"wires":[]},{"id":"aeae162b.efd118","type":"comment","z":"845b226d.a4b18","name":"Filter pos/neg and make separate sequence↑ (but not a simple sort) ","info":"","x":320,"y":340,"wires":[]}]
\ No newline at end of file
diff --git a/packages/node_modules/@node-red/nodes/locales/de/messages.json b/packages/node_modules/@node-red/nodes/locales/de/messages.json
index c36c309f3..58fce3a92 100755
--- a/packages/node_modules/@node-red/nodes/locales/de/messages.json
+++ b/packages/node_modules/@node-red/nodes/locales/de/messages.json
@@ -397,7 +397,7 @@
"message" : "gesamte Nachricht",
"tip" : {
"path1" : "Standardmäßig enthält
Nutzdaten
die Daten, die über einen Websocket gesendet oder von einem Websocket empfangen werden. Der Listener kann so konfiguriert werden, dass er das gesamte Nachrichtenobjekt als eine JSON-formatierte Zeichenfolge sendet oder empfängt.",
- "path2" : "Dieser Pfad ist relativ zu ",
+ "path2" : "Dieser Pfad ist relativ zu
__path__
.",
"url1" : "URL sollte ws: / & #47; oder wss: / & #47; Schema verwenden und auf einen vorhandenen Websocket-Listener verweisen.",
"url2" : "Standardmäßig enthält
Nutzdaten
die Daten, die über einen Websocket gesendet oder von einem Websocket empfangen werden. Der Client kann so konfiguriert werden, dass er das gesamte Nachrichtenobjekt als eine JSON-formatierte Zeichenfolge sendet oder empfängt."
},
diff --git a/packages/node_modules/@node-red/nodes/locales/de/network/22-websocket.html b/packages/node_modules/@node-red/nodes/locales/de/network/22-websocket.html
index d69229820..0192c4e13 100755
--- a/packages/node_modules/@node-red/nodes/locales/de/network/22-websocket.html
+++ b/packages/node_modules/@node-red/nodes/locales/de/network/22-websocket.html
@@ -23,13 +23,13 @@
@@ -37,30 +37,6 @@
Dieser Konfigurations-Node erstellt einen WebSocket Server-Endpunkt unter Verwendung des angegebenen Pfades.
-
-
-
diff --git a/packages/node_modules/@node-red/nodes/locales/de/parsers/70-XML.html b/packages/node_modules/@node-red/nodes/locales/de/parsers/70-XML.html
index 4740ea204..1842ed135 100755
--- a/packages/node_modules/@node-red/nodes/locales/de/parsers/70-XML.html
+++ b/packages/node_modules/@node-red/nodes/locales/de/parsers/70-XML.html
@@ -20,6 +20,10 @@
payloadObjekt | String
Ein JavaScript Objekt oder ein XML String.
+ options Objekt
+ This optional property can be used to pass in any of the options supported by the underlying
+ library used to convert to and from XML. See the xml2js docs
+ for more information.
Ausgaben
@@ -30,10 +34,6 @@
Wenn die Eingabe ein JavaScript-Objekt ist, wird versucht ein XML-String zu erstellen.
-
Optionen Objekt
-
This optional property can be used to pass in any of the options supported by the underlying
- library used to convert to and from XML. See the xml2js docs
- for more information.
Details
Bei der Konvertierung zwischen XML und einem Objekt werden standardmäßig alle XML-Attribute als Eigenschaft namens $
hinzugefügt.
diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/function/10-function.html b/packages/node_modules/@node-red/nodes/locales/en-US/function/10-function.html
index 28bc76f3a..93a1d6f51 100644
--- a/packages/node_modules/@node-red/nodes/locales/en-US/function/10-function.html
+++ b/packages/node_modules/@node-red/nodes/locales/en-US/function/10-function.html
@@ -15,12 +15,14 @@
-->
diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json
index e1d7c6368..db3da0ff8 100755
--- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json
+++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json
@@ -37,6 +37,7 @@
"stopped": "stopped",
"failed": "Inject failed: __error__",
"label": {
+ "properties": "Properties",
"repeat": "Repeat",
"flow": "flow context",
"global": "global context",
@@ -51,7 +52,7 @@
"string": "string",
"boolean": "boolean",
"number": "number",
- "Array": "Array",
+ "Array": "array",
"invalid": "Invalid JSON Object"
},
"timestamp": "timestamp",
@@ -79,11 +80,11 @@
"on": "on",
"onstart": "Inject once after",
"onceDelay": "seconds, then",
- "tip": "Note: \"interval between times\" and \"at a specific time\" will use cron. \"interval\" should be 596 hours or less. See info box for details.",
"success": "Successfully injected: __label__",
"errors": {
"failed": "inject failed, see log for details",
- "toolong": "Interval too large"
+ "toolong": "Interval too large",
+ "invalid-expr": "Invalid JSONata expression: __error__"
}
},
"catch": {
@@ -117,10 +118,12 @@
},
"debug": {
"output": "Output",
+ "status": "status",
"none": "None",
"invalid-exp": "Invalid JSONata expression: __error__",
"msgprop": "message property",
"msgobj": "complete msg object",
+ "autostatus": "automatic",
"to": "To",
"debtab": "debug tab",
"tabcon": "debug tab and console",
@@ -207,8 +210,14 @@
"function": "",
"label": {
"function": "Function",
+ "initialize": "Setup",
+ "finalize": "Close",
"outputs": "Outputs"
},
+ "text": {
+ "initialize": "// Code added here will be run once\n// whenever the node is deployed.\n",
+ "finalize": "// Code added here will be run when the\n// node is being stopped or re-deployed.\n"
+ },
"error": {
"inputListener":"Cannot add listener to 'input' event within Function",
"non-message-returned":"Function tried to send a message of type __type__"
@@ -302,7 +311,7 @@
"wait-for": "wait for",
"wait-loop": "resend it every",
"for": "Handling",
- "bytopics": "each msg.topic independently",
+ "bytopics": "each",
"alltopics": "all messages",
"duration": {
"ms": "Milliseconds",
@@ -311,6 +320,7 @@
"h": "Hours"
},
"extend": " extend delay if new message arrives",
+ "second": " send second message to separate output",
"label": {
"trigger": "trigger",
"trigger-block": "trigger & block",
@@ -398,7 +408,11 @@
"status": "Status code",
"headers": "Headers",
"other": "other",
- "paytoqs" : "Append msg.payload as query string parameters",
+ "paytoqs" : {
+ "ignore": "Ignore",
+ "query": "Append to query-string parameters",
+ "body": "Send as request body"
+ },
"utf8String": "UTF8 string",
"binaryBuffer": "binary buffer",
"jsonObject": "parsed JSON object",
@@ -455,7 +469,7 @@
"message": "entire message",
"tip": {
"path1": "By default, payload
will contain the data to be sent over, or received from a websocket. The listener can be configured to send or receive the entire message object as a JSON formatted string.",
- "path2": "This path will be relative to ",
+ "path2": "This path will be relative to __path__
.",
"url1": "URL should use ws:// or wss:// scheme and point to an existing websocket listener.",
"url2": "By default, payload
will contain the data to be sent over, or received from a websocket. The client can be configured to send or receive the entire message object as a JSON formatted string."
},
@@ -657,7 +671,8 @@
"errors": {
"invalid-from": "Invalid 'from' property: __error__",
"invalid-json": "Invalid 'to' JSON property",
- "invalid-expr": "Invalid JSONata expression: __error__"
+ "invalid-expr": "Invalid JSONata expression: __error__",
+ "no-override": "Cannot set property of non-object type: __property__"
}
},
"range": {
@@ -698,7 +713,9 @@
"output": "Output",
"includerow": "include column name row",
"newline": "Newline",
- "usestrings": "parse numerical values"
+ "usestrings": "parse numerical values",
+ "include_empty_strings": "include empty strings",
+ "include_null_values": "include null values"
},
"placeholder": {
"columns": "comma-separated column names"
@@ -721,9 +738,15 @@
"mac": "Mac (\\r)",
"windows": "Windows (\\r\\n)"
},
+ "hdrout": {
+ "none": "never send column headers",
+ "all": "always send column headers",
+ "once": "send headers once, until msg.reset"
+ },
"errors": {
"csv_js": "This node only handles CSV strings or js objects.",
- "obj_csv": "No columns template specified for object -> CSV."
+ "obj_csv": "No columns template specified for object -> CSV.",
+ "bad_csv": "Malformed CSV data - output probably corrupt."
}
},
"html": {
@@ -894,7 +917,8 @@
"fixup": "Fix-up exp"
},
"errors": {
- "invalid-expr": "Invalid JSONata expression: __error__"
+ "invalid-expr": "Invalid JSONata expression: __error__",
+ "invalid-type": "Cannot join __error__ to buffer"
}
},
"sort" : {
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 31bdc4907..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
@@ -41,5 +41,5 @@
wait a fixed timeout from first reply and then return, sit and wait for data, or send then close the connection
immediately, without waiting for a reply.
The response will be output in msg.payload
as a buffer, so you may want to .toString() it.
-
If you leave tcp host or port blank they must be set by using the msg.host
and msg.port
properties.
+
If you leave tcp host or port blank they must be set by using the msg.host
and msg.port
properties in every message sent to the node.
diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-CSV.html b/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-CSV.html
index 5b0731cb1..6f16f5b72 100644
--- a/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-CSV.html
+++ b/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-CSV.html
@@ -38,8 +38,13 @@
The column template can contain an ordered list of column names. When converting CSV to an object, the column names
will be used as the property names. Alternatively, the column names can be taken from the first row of the CSV.
When converting to CSV, the column template is used to identify which properties to extract from the object and in what order.
+
If the template is blank then the node can use a simple comma separated list of properties supplied in msg.columns
to
+ determine what to extract. If that is not present then all the object properties are ouput in the order in which they are found.
If the input is an array then the columns template is only used to optionally generate a row of column titles.
-
The node can accept a multi-part input as long as the parts
property is set correctly.
+
If 'parse numerical values' option is checked, string numerical values will be returned as numbers, ie. middle value '1,"1.5",2'.
+
If 'include empty strings' option is checked, empty strings will be returned in result, ie. middle value '"1","",3'.
+
If 'include null values' option is checked, null values will be returned in result, ie. middle value '"1",,3'.
+
The node can accept a multi-part input as long as the parts
property is set correctly, for example from a file-in node or split node.
If outputting multiple messages they will have their parts
property set and form a complete message sequence.
Note: the column template must be comma separated - even if a different separator is chosen for the data.
diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-XML.html b/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-XML.html
index 07513907d..e378a6358 100644
--- a/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-XML.html
+++ b/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-XML.html
@@ -20,6 +20,10 @@
payloadobject | string
A JavaScript object or XML string.
+ options object
+ This optional property can be used to pass in any of the options supported by the underlying
+ library used to convert to and from XML. See the xml2js docs
+ for more information.
Outputs
@@ -30,10 +34,6 @@
If the input is a JavaScript object it tries to build an XML string.
-
options object
-
This optional property can be used to pass in any of the options supported by the underlying
- library used to convert to and from XML. See the xml2js docs
- for more information.
Details
When converting between XML and an object, any XML attributes are added as a property named $
by default.
diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/sequence/17-split.html b/packages/node_modules/@node-red/nodes/locales/en-US/sequence/17-split.html
index 118df70af..c9c3e3070 100644
--- a/packages/node_modules/@node-red/nodes/locales/en-US/sequence/17-split.html
+++ b/packages/node_modules/@node-red/nodes/locales/en-US/sequence/17-split.html
@@ -91,7 +91,8 @@
complete
-
If set, the node will send its output message in its current state.
+
If set, the node will append the payload, and then send the output message in its current state.
+ If you don't wish to append the payload, delete it from the msg.
Details
diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/sequence/19-batch.html b/packages/node_modules/@node-red/nodes/locales/en-US/sequence/19-batch.html
index 0564957fc..dbf00d287 100644
--- a/packages/node_modules/@node-red/nodes/locales/en-US/sequence/19-batch.html
+++ b/packages/node_modules/@node-red/nodes/locales/en-US/sequence/19-batch.html
@@ -39,4 +39,5 @@
This node will buffer messages internally in order to work across sequences. The
runtime setting nodeMessageBufferMaxLength
can be used to limit how many messages nodes
will buffer.
+
If a message is received with the msg.reset property set, the buffered messages are deleted and not sent.
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 5933f9c2d..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
@@ -49,12 +49,6 @@
The contents of the file as either a string or binary buffer.
filename string
If not configured in the node, this optional property sets the name of the file to be read.
-
error object
-
deprecated : If enabled in the node, when the node hits an error
- reading the file, it will send a message with no payload
- and this error
property set to the error details. This
- mode of behaviour is deprecated and not enabled by default for new
- instances of the node. See below for more information.
Details
The filename should be an absolute path, otherwise it will be relative to
@@ -65,11 +59,5 @@
When split into multiple messages, each message will have a parts
property set, forming a complete message sequence.
Encoding of input data can be specified from list of encodings if output format is string.
-
Legacy error handling
-
Before Node-RED 0.17, if this node hit an error whilst reading the file, it would
- send a message with no msg.payload
and msg.error
set to the
- details of the error. This is a deprecated mode of behaviour for the node that new
- instances will not do. If required, this mode can be re-enabled within the node
- configuration.
Errors should be caught and handled using a Catch node.
diff --git a/packages/node_modules/@node-red/nodes/locales/ja/function/10-function.html b/packages/node_modules/@node-red/nodes/locales/ja/function/10-function.html
index 2744cf53a..b348512df 100644
--- a/packages/node_modules/@node-red/nodes/locales/ja/function/10-function.html
+++ b/packages/node_modules/@node-red/nodes/locales/ja/function/10-function.html
@@ -15,10 +15,11 @@
-->
diff --git a/packages/node_modules/@node-red/nodes/locales/ja/messages.json b/packages/node_modules/@node-red/nodes/locales/ja/messages.json
old mode 100755
new mode 100644
index adc033390..66a2d5fb2
--- a/packages/node_modules/@node-red/nodes/locales/ja/messages.json
+++ b/packages/node_modules/@node-red/nodes/locales/ja/messages.json
@@ -37,6 +37,7 @@
"stopped": "stopped",
"failed": "inject失敗: __error__",
"label": {
+ "properties": "プロパティ",
"repeat": "繰り返し",
"flow": "フローコンテクスト",
"global": "グローバルコンテクスト",
@@ -79,11 +80,11 @@
"on": "曜日",
"onstart": "Node-RED起動の",
"onceDelay": "秒後、以下を行う",
- "tip": "
注釈: 「指定した時間間隔、日時」と「指定した日時」はcronを使用します。
「時間間隔」には596時間より小さな値を指定します。
詳細はノードの「情報」を確認してください。",
"success": "inject処理を実行しました: __label__",
"errors": {
"failed": "inject処理が失敗しました。詳細はログを確認してください。",
- "toolong": "時間間隔が大き過ぎます"
+ "toolong": "時間間隔が大き過ぎます",
+ "invalid-expr": "JSONata式が不正: __error__"
}
},
"catch": {
@@ -104,7 +105,7 @@
"status": "status: 全て",
"statusNodes": "status: __number__",
"label": {
- "source": "状態取得元",
+ "source": "ステータス取得元",
"sortByType": "型で並べ替え"
},
"scope": {
@@ -117,16 +118,18 @@
},
"debug": {
"output": "対象",
+ "status": "ステータス",
"none": "無し",
"invalid-exp": "JSONata式が不正: __error__",
"msgprop": "メッセージプロパティ",
"msgobj": "msgオブジェクト全体",
+ "autostatus": "自動",
"to": "出力先",
"debtab": "デバッグタブ",
"tabcon": "デバッグタブとコンソール",
"toSidebar": "デバッグウィンドウ",
"toConsole": "システムコンソール",
- "toStatus": "ノード状態 (32 文字)",
+ "toStatus": "ノードステータス(32 文字)",
"severity": "Level",
"notification": {
"activated": "有効化しました: __label__",
@@ -207,8 +210,14 @@
"function": "",
"label": {
"function": "コード",
+ "initialize": "初期化処理",
+ "finalize": "終了処理",
"outputs": "出力数"
},
+ "text": {
+ "initialize": "// ここに記述したコードは、ノードをデプロイした時に\n// 一度だけ実行されます。\n",
+ "finalize": "// ここに記述したコードは、ノードを停止したとき\n// もしくは再デプロイしたときに実行されます。\n"
+ },
"error": {
"inputListener": "コード内で'input'イベントのリスナを設定できません",
"non-message-returned": "Functionノードが __type__ 型のメッセージ送信を試みました"
@@ -302,7 +311,7 @@
"wait-for": "指定した時間待機",
"wait-loop": "指定した時間間隔毎に送信を繰り返す",
"for": "処理対象",
- "bytopics": "msg.topic毎",
+ "bytopics": "毎",
"alltopics": "全メッセージ",
"duration": {
"ms": "ミリ秒",
@@ -310,7 +319,8 @@
"m": "分",
"h": "時間"
},
- "extend": " 新たなメッセージを受け取った時に遅延を延長",
+ "extend": "新たなメッセージを受け取った時に遅延を延長",
+ "second": "2つ目の出力端子に2番目のメッセージを送信",
"label": {
"trigger": "trigger",
"trigger-block": "trigger & block",
@@ -395,10 +405,14 @@
"doc": "Docs",
"return": "出力形式",
"upload": "ファイルのアップロード",
- "status": "状態コード",
+ "status": "ステータスコード",
"headers": "ヘッダ",
"other": "その他",
- "paytoqs": "msg.payloadをクエリパラメータに追加",
+ "paytoqs" : {
+ "ignore": "無視",
+ "query": "クエリパラメータに追加",
+ "body": "リクエストボディとして送信"
+ },
"utf8String": "UTF8文字列",
"binaryBuffer": "バイナリバッファ",
"jsonObject": "JSONオブジェクト",
@@ -455,7 +469,7 @@
"message": "メッセージ全体を送信/受信",
"tip": {
"path1": "標準では
payload
がwebsocketから送信、受信されるデータを持ちます。クライアントはJSON形式の文字列としてメッセージ全体を送信、受信するよう設定できます。",
- "path2": "This path will be relative to ",
+ "path2": "このパスは
__path__
の相対パスになります。",
"url1": "URLには ws:// または wss:// スキーマを使用して、存在するwebsocketリスナを設定してください。",
"url2": "標準では
payload
がwebsocketから送信、受信されるデータを持ちます。クライアントはJSON形式の文字列としてメッセージ全体を送信、受信するよう設定できます。"
},
@@ -655,7 +669,8 @@
"errors": {
"invalid-from": "操作対象のプロパティが不正: __error__",
"invalid-json": "対象の値のJSONプロパティが不正",
- "invalid-expr": "JSONata式が不正: __error__"
+ "invalid-expr": "JSONata式が不正: __error__",
+ "no-override": "オブジェクト型でないプロパティを設定できません: __property__"
}
},
"range": {
@@ -696,7 +711,9 @@
"output": "出力",
"includerow": "1行目を列名とする",
"newline": "改行コード",
- "usestrings": "数値を変換する"
+ "usestrings": "数値を変換する",
+ "include_empty_strings": "空の文字を含む",
+ "include_null_values": "null値を含む"
},
"placeholder": {
"columns": "コンマ区切りで列名を入力"
@@ -719,9 +736,15 @@
"mac": "Mac (\\r)",
"windows": "Windows (\\r\\n)"
},
+ "hdrout": {
+ "none": "カラムヘッダを送信しない",
+ "all": "カラムヘッダを常に送信する",
+ "once": "ヘッダを一度だけ送信する(msg.resetの受け付けると再送)"
+ },
"errors": {
"csv_js": "本ノードが処理できる形式は、CSV文字列またはJSONのみです",
- "obj_csv": "オブジェクトをCSVへ変換する際の列名が設定されていません"
+ "obj_csv": "オブジェクトをCSVへ変換する際の列名が設定されていません",
+ "bad_csv": "不正なCSVデータ - 出力の修正を試みました"
}
},
"html": {
@@ -892,7 +915,8 @@
"fixup": "最終調整式"
},
"errors": {
- "invalid-expr": "JSONata式が不正: __error__"
+ "invalid-expr": "JSONata式が不正: __error__",
+ "invalid-type": "__error__ をバッファに連結できません"
}
},
"sort": {
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 1ab60b163..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
@@ -31,5 +31,5 @@
シンプルなTCPリクエストノード。msg.payload
をサーバのTCPポートに送信し、レスポンスを待ちます。
サーバに接続、"リクエスト"送信、"レスポンス"受信を行います。固定長の文字数、指定文字へのマッチ、最初のリプライの到着から指定した時間待つ、データの到着待ち、データ送信を行いリプライを待たず接続を即時解除、などから動作を選択できます。
レスポンスはバッファ形式でmsg.payload
に出力されます。文字列として扱いには、.toString()を使用してください。
-
TCPホストのポート番号設定を空にした場合、msg.host
およびmsg.port
プロパティを設定しなくてはなりません。
+
TCPホストのポート番号設定を空にした場合、本ノードに送信される全てのメッセージにおいてmsg.host
およびmsg.port
プロパティを設定しなくてはなりません。
diff --git a/packages/node_modules/@node-red/nodes/locales/ja/parsers/70-CSV.html b/packages/node_modules/@node-red/nodes/locales/ja/parsers/70-CSV.html
index 38286e438..f1dfaffcd 100644
--- a/packages/node_modules/@node-red/nodes/locales/ja/parsers/70-CSV.html
+++ b/packages/node_modules/@node-red/nodes/locales/ja/parsers/70-CSV.html
@@ -26,8 +26,7 @@
payloadオブジェクト | 配列 | 文字列
- 入力が文字列の場合、CSVとして解釈し、CSVの各行をキー/バリューとしたJavaScriptオブジェクトを生成します。
- 各行毎にメッセージを送信するかオブジェクトの配列からなる一つのメッセージを送信するかを選択できます。
+ 入力が文字列の場合、CSVとして解釈し、CSVの各行をキー/バリューとしたJavaScriptオブジェクトを生成します。各行毎にメッセージを送信するかオブジェクトの配列からなる一つのメッセージを送信するかを選択できます。
入力がJavaScriptオブジェクトの場合、CSV文字列への変換を行います。
入力が基本型の配列の場合、1行のCSV文字列へ変換します。
入力が配列の配列、もしくは、オブジェクトの配列の場合、複数行のCSV文字列へ変換します。
@@ -37,8 +36,12 @@
詳細
「列名」にカラム名のリストを指定することができます。CSVからオブジェクトに変換を行う際、カラム名をプロパティ名として使用します。「列名」の代わりに、CSVデータの1行目にカラム名を含めることもできます。
CSVへの変換を行う際には、オブジェクトから取り出すべきプロパティとその順序を「列名」を参照して決めます。
+ 列名がない場合、本ノードはmsg.columns
プロパティの単純なコンマ区切りリストを使用して、何を抽出するかを決定します。もしそれが存在しない場合、すべてのオブジェクトプロパティを見つけた順序で出力します。
入力が配列の場合には、「列名」はカラム名を表す行の出力指定がされた場合だけ用います。
- parts
プロパティが正しく設定されている場合、メッセージ列を入力として受け付けます。
+ 「数値を変換する」オプションがチェックされている場合、文字列型の数値が数値として返されます。つまり「1,"1.5",2」の真ん中の値が数値になります。
+ 「空の文字を含む」オプションがチェックされている場合、空の文字列が結果に返されます。つまり「"1","",3」の真ん中の値が空の文字列になります。
+ 「null値を含む」オプションがチェックされている場合、null値が結果に返されます。つまり「"1",,3」の真ん中の値がnullになります。
+ file-inノードやsplitノードが出力するメッセージの様に、parts
プロパティが正しく設定されている場合、メッセージ列を入力として受け付けます。
CSVを複数のメッセージに変換して出力する場合、出力がメッセージ列となるようparts
プロパティを設定します。
注: カンマ以外の区切り文字を設定した場合であっても、「列名」はカンマ区切りとしてください。
diff --git a/packages/node_modules/@node-red/nodes/locales/ja/parsers/70-XML.html b/packages/node_modules/@node-red/nodes/locales/ja/parsers/70-XML.html
index 5c882a2ec..1149a072a 100644
--- a/packages/node_modules/@node-red/nodes/locales/ja/parsers/70-XML.html
+++ b/packages/node_modules/@node-red/nodes/locales/ja/parsers/70-XML.html
@@ -20,6 +20,8 @@
payloadオブジェクト | 文字列
JavaScriptオブジェクトもしくはXML文字列
+ options オブジェクト
+ 内部で用いているXMLへの変換ライブラリに対してオプションを渡すことができます。詳しくはthe xml2js docs を参照してください。
出力
@@ -30,8 +32,6 @@
入力がJavaScriptオブジェクトの場合、XML文字列に変換します。
-
options オブジェクト
-
内部で用いているXMLへの変換ライブラリに対してオプションを渡すことができます。詳しくはthe xml2js docs を参照してください。
詳細
XMLとオブジェクトの間での変換を行う場合、デフォルトでは、XML属性は$
という名称のプロパティに追加します。
diff --git a/packages/node_modules/@node-red/nodes/locales/ja/sequence/17-split.html b/packages/node_modules/@node-red/nodes/locales/ja/sequence/17-split.html
index 7702704df..466960a39 100644
--- a/packages/node_modules/@node-red/nodes/locales/ja/sequence/17-split.html
+++ b/packages/node_modules/@node-red/nodes/locales/ja/sequence/17-split.html
@@ -79,7 +79,7 @@
complete
-
設定されている場合、保持しているメッセージを結合して送信します。
+
設定されている場合、本ノードはペイロードを追加し、保持しているメッセージを送信します。ペイロードを追加したくない場合は、msgから削除してください。
詳細
diff --git a/packages/node_modules/@node-red/nodes/locales/ja/sequence/19-batch.html b/packages/node_modules/@node-red/nodes/locales/ja/sequence/19-batch.html
index 03f2f3f17..1306bdfa7 100644
--- a/packages/node_modules/@node-red/nodes/locales/ja/sequence/19-batch.html
+++ b/packages/node_modules/@node-red/nodes/locales/ja/sequence/19-batch.html
@@ -31,4 +31,6 @@
メッセージの蓄積
このノードの処理ではメッセージ列の処理のためメッセージを内部に蓄積します。settings.js のnodeMessageBufferMaxLength
を指定することで蓄積するメッセージの最大値を制限することができます。
+
メッセージがmsg.reset プロパティを持つ場合、蓄積したメッセージを削除し送信を行いません。
+
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 bc971049e..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
@@ -44,8 +44,6 @@
ファイルの内容を文字列もしくはバッファで表現します
filename 文字列
読み出し対象のファイル名をノードに設定していない場合、このプロパティでファイルを指定します
-
error オブジェクト
-
非推奨 : 設定で有効にした場合、ファイルの読み込み時にエラーが発生するとpayload
を持たずerror
プロパティにエラーの詳細情報を設定したメッセージを送信します。この動作モードは非推奨であり、新しいノード実装ではデフォルトでは無効としています。詳細については、以下を参照してください。
詳細
ファイルネームは絶対パスでの指定を推奨します。絶対パスを指定しない場合は、Node-REDプロセスのワーキングディレクトリからの相対パスとして扱います。
@@ -53,7 +51,5 @@
テキストファイルの場合、行毎に分割して各々メッセージを送信することができます。また、バイナリファイルの場合、小さな塊のバッファに分割して送信できます。バッファの分割単位はオペレーティングシステム依存ですが、一般に64k(Linux/Mac)もしくは41k(Windows)です。
複数のメッセージに分割する場合、各メッセージにはparts
プロパティが設定され、メッセージ列を構成します。
出力形式が文字列の場合、入力データのエンコーディングをエンコーディングリストから選択できます。
-
旧式のエラー処理
-
Node-RED 0.17より前の版では、ファイルの読み込み時にエラーが発生するとpayload
を持たずerror
プロパティにエラーの詳細情報を設定したメッセージを送信します。この動作モードは非推奨であり、新しいノード実装ではデフォルトでは無効としています。ノードの設定により、必要に応じてこのモードを有効にできます。
エラーはcatchノードで補足して処理することを推奨します。
diff --git a/packages/node_modules/@node-red/nodes/locales/ko/messages.json b/packages/node_modules/@node-red/nodes/locales/ko/messages.json
index d87b2c0f5..8e3a4f325 100755
--- a/packages/node_modules/@node-red/nodes/locales/ko/messages.json
+++ b/packages/node_modules/@node-red/nodes/locales/ko/messages.json
@@ -446,7 +446,7 @@
"message": "메세지 전체를 송신/수신",
"tip": {
"path1": "표준으로는
payload
가 websocket에서 송신, 수신된 데이터를 기다립니다. 클라이언트는 JSON형식의 문자열로 메세지전체를 송신, 수신하도록 설정할 수 있습니다.",
- "path2": "This path will be relative to ",
+ "path2": "This path will be relative to
__path__
.",
"url1": "URL에는 ws:// 또는 wss:// 스키마를 사용하여, 존재하는 websocket리스너를 설정해 주세요.",
"url2": "표준으로는
payload
가 websocket에서 송신,수신될 데이터를 기다립니다.클라이언트는 JSON형식의 문자열로 메세지전체를 송신, 수신하도록 설정할 수 있습니다."
},
diff --git a/packages/node_modules/@node-red/nodes/locales/ko/parsers/70-XML.html b/packages/node_modules/@node-red/nodes/locales/ko/parsers/70-XML.html
index 7d601f6c1..cd7091049 100644
--- a/packages/node_modules/@node-red/nodes/locales/ko/parsers/70-XML.html
+++ b/packages/node_modules/@node-red/nodes/locales/ko/parsers/70-XML.html
@@ -20,6 +20,8 @@
payload오브젝트 | 문자열
JavaScript오브젝트 혹은 XML문자열
+ options 오브젝트
+ 내부에서 사용중인 XML로의 변환 라이브러리에 대해 옵션을 전달할 수 있습니다. 자세한 사항은 the xml2js docs 를 참조해 주세요.
출력
@@ -30,8 +32,6 @@
입력이 JavaScript오브젝트인 경우, XML문자열로 변환합니다.
-
options 오브젝트
-
내부에서 사용중인 XML로의 변환 라이브러리에 대해 옵션을 전달할 수 있습니다. 자세한 사항은 the xml2js docs 를 참조해 주세요.
상세
XML와 오브젝트의 사이에서의 변환을 수행할 경우, 기본값으로는 XML속성은 $
이라는 명칭의 프로퍼티에 추가합니다.
diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/common/20-inject.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/20-inject.html
new file mode 100644
index 000000000..78b218083
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/20-inject.html
@@ -0,0 +1,34 @@
+
+
+
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..461e7885f 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-beta.1",
"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 e70952832..22fb2a549 100644
--- a/packages/node_modules/@node-red/registry/lib/installer.js
+++ b/packages/node_modules/@node-red/registry/lib/installer.js
@@ -30,7 +30,7 @@ var npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
var paletteEditorEnabled = false;
var settings;
-var moduleRe = /^(@[^/]+?[/])?[^/]+?$/;
+var moduleRe = /^(@[^/@]+?[/])?[^/@]+?$/;
var slashRe = process.platform === "win32" ? /\\|[/]/ : /[/]/;
var pkgurlRe = /^(https?|git(|\+https?|\+ssh|\+file)):\/\//;
@@ -78,15 +78,24 @@ function checkExistingModule(module,version) {
return false;
}
function installModule(module,version,url) {
+ module = module || "";
activePromise = activePromise.then(() => {
//TODO: ensure module is 'safe'
return new Promise((resolve,reject) => {
var installName = module;
var isUpgrade = false;
try {
- if (url && pkgurlRe.test(url)) {
- // Git remote url or Tarball url - check the valid package url
- installName = url;
+ if (url) {
+ if (pkgurlRe.test(url)) {
+ // Git remote url or Tarball url - check the valid package url
+ installName = url;
+ } else {
+ log.warn(log._("server.install.install-failed-url",{name:module,url:url}));
+ e = new Error("Invalid url");
+ e.code = "invalid_module_url";
+ reject(e);
+ return;
+ }
} else if (moduleRe.test(module)) {
// Simple module name - assume it can be npm installed
if (version) {
@@ -96,6 +105,12 @@ function installModule(module,version,url) {
// A path - check if there's a valid package.json
installName = module;
module = checkModulePath(module);
+ } else {
+ log.warn(log._("server.install.install-failed-name",{name:module}));
+ e = new Error("Invalid module name");
+ e.code = "invalid_module_name";
+ reject(e);
+ return;
}
isUpgrade = checkExistingModule(module,version);
} catch(err) {
@@ -108,7 +123,7 @@ function installModule(module,version,url) {
}
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
@@ -201,7 +216,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..13bbf2924 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-beta.1",
"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-beta.1",
"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 fb16cc631..7624b0b76 100644
--- a/packages/node_modules/@node-red/runtime/lib/api/nodes.js
+++ b/packages/node_modules/@node-red/runtime/lib/api/nodes.js
@@ -62,7 +62,7 @@ var api = module.exports = {
return resolve(result);
} else {
runtime.log.audit({event: "nodes.info.get",id:id,error:"not_found"}, opts.req);
- var err = new Error();
+ var err = new Error("Node not found");
err.code = "not_found";
err.status = 404;
return reject(err);
@@ -105,7 +105,7 @@ var api = module.exports = {
return resolve(result);
} else {
runtime.log.audit({event: "nodes.config.get",id:id,error:"not_found"}, opts.req);
- var err = new Error();
+ var err = new Error("Node not found");
err.code = "not_found";
err.status = 404;
return reject(err);
@@ -145,7 +145,7 @@ var api = module.exports = {
return resolve(result);
} else {
runtime.log.audit({event: "nodes.module.get",id:opts.module,error:"not_found"}, opts.req);
- var err = new Error();
+ var err = new Error("Module not found");
err.code = "not_found";
err.status = 404;
return reject(err);
@@ -232,7 +232,7 @@ var api = module.exports = {
var module = runtime.nodes.getModuleInfo(opts.module);
if (!module) {
runtime.log.audit({event: "nodes.remove",module:opts.module,error:"not_found"}, opts.req);
- var err = new Error();
+ var err = new Error("Module not found");
err.code = "not_found";
err.status = 404;
return reject(err);
@@ -278,7 +278,7 @@ var api = module.exports = {
var module = runtime.nodes.getModuleInfo(mod);
if (!module) {
runtime.log.audit({event: "nodes.module.set",module:mod,error:"not_found"}, opts.req);
- var err = new Error();
+ var err = new Error("Module not found");
err.code = "not_found";
err.status = 404;
return reject(err);
@@ -329,7 +329,7 @@ var api = module.exports = {
var node = runtime.nodes.getNodeInfo(id);
if (!node) {
runtime.log.audit({event: "nodes.info.set",id:id,error:"not_found"}, opts.req);
- var err = new Error();
+ var err = new Error("Node not found");
err.code = "not_found";
err.status = 404;
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/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/lib/storage/localfilesystem/projects/index.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/index.js
index 82c1f8d39..fa4c294e7 100644
--- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/index.js
+++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/index.js
@@ -395,7 +395,7 @@ function createProject(user, metadata) {
}
function setActiveProject(user, projectName) {
return loadProject(projectName).then(function(project) {
- var globalProjectSettings = settings.get("projects");
+ var globalProjectSettings = settings.get("projects")||{};
globalProjectSettings.activeProject = project.name;
return settings.set("projects",globalProjectSettings).then(function() {
log.info(log._("storage.localfilesystem.projects.changing-project",{project:(activeProject&&activeProject.name)||"none"}));
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..3047295de 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
@@ -32,6 +32,8 @@
"install-failed": "Install failed",
"install-failed-long": "Installation of module __name__ failed:",
"install-failed-not-found": "$t(server.install.install-failed-long) module not found",
+ "install-failed-name": "$t(server.install.install-failed-long) invalid module name: __name__",
+ "install-failed-url": "$t(server.install.install-failed-long) invalid url: __url__",
"upgrading": "Upgrading module: __name__ to version: __version__",
"upgraded": "Upgraded module: __name__. Restart Node-RED to use the new version",
"upgrade-failed-not-found": "$t(server.install.install-failed-long) version not found",
@@ -47,7 +49,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/ja/runtime.json b/packages/node_modules/@node-red/runtime/locales/ja/runtime.json
old mode 100755
new mode 100644
index 3dc4d58ab..453e62019
--- a/packages/node_modules/@node-red/runtime/locales/ja/runtime.json
+++ b/packages/node_modules/@node-red/runtime/locales/ja/runtime.json
@@ -46,7 +46,14 @@
"now-running": "サーバは __listenpath__ で実行中です",
"failed-to-start": "サーバの起動に失敗しました:",
"headless-mode": "ヘッドレスモードで実行中です",
- "httpadminauth-deprecated": "httpAdminAuthは非推奨です。代わりに adminAuth を使用してください"
+ "httpadminauth-deprecated": "httpAdminAuthは非推奨です。代わりに adminAuth を使用してください",
+ "https": {
+ "refresh-interval": "__interval__ 時間毎にhttps設定を更新します",
+ "settings-refreshed": "サーバのhttps設定が更新されました",
+ "refresh-failed": "https設定の更新で失敗しました: __message__",
+ "nodejs-version": "httpsRefreshIntervalにはNode.js 11以降が必要です",
+ "function-required": "httpsRefreshIntervalでは、httpsプロパティはfunctionである必要があります"
+ }
},
"api": {
"flows": {
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..a8e171b1a 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-beta.1",
"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-beta.1",
+ "@node-red/util": "1.1.0-beta.1",
"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 ef43739e6..f69ca890a 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;
}
/**
@@ -372,6 +375,11 @@ function setObjectProperty(msg,prop,value,createMissing) {
key = msgPropParts[i];
if (typeof key === 'string' || (typeof key === 'number' && !Array.isArray(obj))) {
if (obj.hasOwnProperty(key)) {
+ if (length > 1 && ((typeof obj[key] !== "object" && typeof obj[key] !== "function") || obj[key] === null)) {
+ // Break out early as we cannot create a property beneath
+ // this type of value
+ return false;
+ }
obj = obj[key];
} else if (createMissing) {
if (typeof msgPropParts[i+1] === 'string') {
@@ -381,7 +389,7 @@ function setObjectProperty(msg,prop,value,createMissing) {
}
obj = obj[key];
} else {
- return null;
+ return false;
}
} else if (typeof key === 'number') {
// obj is an array
@@ -394,7 +402,7 @@ function setObjectProperty(msg,prop,value,createMissing) {
}
obj = obj[key];
} else {
- return null;
+ return false;
}
} else {
obj = obj[key];
@@ -409,8 +417,14 @@ function setObjectProperty(msg,prop,value,createMissing) {
delete obj[key]
}
} else {
- obj[key] = value;
+ if (typeof obj === "object" && obj !== null) {
+ obj[key] = value;
+ } else {
+ // Cannot set a property of a non-object/array
+ return false;
+ }
}
+ return true;
}
/*!
@@ -555,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..c40ce4faf 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-beta.1",
"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..5e1b5f2fe 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-beta.1",
"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-beta.1",
+ "@node-red/runtime": "1.1.0-beta.1",
+ "@node-red/util": "1.1.0-beta.1",
+ "@node-red/nodes": "1.1.0-beta.1",
"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 95a42d9fd..552856167 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,217 +175,274 @@ 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);
+ }
+ }
-function exitWhenStopped() {
- 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('SIGINT', exitWhenStopped);
-process.on('SIGTERM', exitWhenStopped);
-process.on('SIGHUP', exitWhenStopped);
-process.on('SIGUSR2', exitWhenStopped); // for nodemon restart
-process.on('SIGBREAK', exitWhenStopped); // for windows ctrl-break
-process.on('message', function(m) { // for PM2 under window with --shutdown-with-message
- if (m === 'shutdown') { exitWhenStopped }
+ 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 exitWhenStopped() {
+ RED.stop().then(function() {
+ process.exit();
+ });
+ }
+ process.on('SIGINT', exitWhenStopped);
+ process.on('SIGTERM', exitWhenStopped);
+ process.on('SIGHUP', exitWhenStopped);
+ process.on('SIGUSR2', exitWhenStopped); // for nodemon restart
+ process.on('SIGBREAK', exitWhenStopped); // for windows ctrl-break
+ process.on('message', function(m) { // for PM2 under window with --shutdown-with-message
+ if (m === 'shutdown') { exitWhenStopped() }
+ });
+
+}).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/context_spec.js b/test/unit/@node-red/editor-api/lib/admin/context_spec.js
index a9dc0f70d..8f3dbba55 100644
--- a/test/unit/@node-red/editor-api/lib/admin/context_spec.js
+++ b/test/unit/@node-red/editor-api/lib/admin/context_spec.js
@@ -127,7 +127,9 @@ describe("api/admin/context", function () {
});
it('should handle error which context.getValue causes', function (done) {
- stub.returns(Promise.reject('error'));
+ var stubbedResult = Promise.reject('error');
+ stubbedResult.catch(function() {});
+ stub.returns(stubbedResult);
request(app)
.get('/context/global')
.set('Accept', 'application/json')
@@ -213,7 +215,9 @@ describe("api/admin/context", function () {
});
it('should handle error which context.delete causes', function (done) {
- stub.returns(Promise.reject('error'));
+ var stubbedResult = Promise.reject('error');
+ stubbedResult.catch(function() {});
+ stub.returns(stubbedResult);
request(app)
.delete('/context/global/abc?store=default')
.expect(400)
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 045a81e0d..1eb79d723 100644
--- a/test/unit/@node-red/registry/lib/installer_spec.js
+++ b/test/unit/@node-red/registry/lib/installer_spec.js
@@ -77,6 +77,30 @@ describe('nodes/registry/installer', function() {
});
describe("installs module", function() {
+ it("rejects module name that includes version", function(done) {
+ installer.installModule("module@version",null,null).catch(function(err) {
+ err.code.should.be.eql('invalid_module_name');
+ done();
+ }).catch(done);
+ });
+ it("rejects missing module name", function(done) {
+ installer.installModule("",null,null).catch(function(err) {
+ err.code.should.be.eql('invalid_module_name');
+ done();
+ }).catch(done);
+ });
+ it("rejects null module name", function(done) {
+ installer.installModule(null,null,null).catch(function(err) {
+ err.code.should.be.eql('invalid_module_name');
+ done();
+ }).catch(done);
+ });
+ it("rejects invalid url", function(done) {
+ installer.installModule("module",null,"abc").catch(function(err) {
+ err.code.should.be.eql('invalid_module_url');
+ done();
+ });
+ });
it("rejects when npm returns a 404", function(done) {
var res = {
code: 1,
@@ -89,7 +113,7 @@ describe('nodes/registry/installer', function() {
installer.installModule("this_wont_exist").catch(function(err) {
err.should.have.property("code",404);
done();
- });
+ }).catch(done);
});
it("rejects when npm does not find specified version", function(done) {
var res = {
@@ -108,7 +132,7 @@ describe('nodes/registry/installer', function() {
installer.installModule("this_wont_exist","0.1.2").catch(function(err) {
err.code.should.be.eql(404);
done();
- });
+ }).catch(done);
});
it("rejects when update requested to existing version", function(done) {
sinon.stub(typeRegistry,"getModuleInfo", function() {
@@ -119,7 +143,7 @@ describe('nodes/registry/installer', function() {
installer.installModule("this_wont_exist","0.1.1").catch(function(err) {
err.code.should.be.eql('module_already_loaded');
done();
- });
+ }).catch(done);
});
it("rejects when update requested to existing version and url", function(done) {
sinon.stub(typeRegistry,"getModuleInfo", function() {
@@ -130,7 +154,7 @@ describe('nodes/registry/installer', function() {
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();
- });
+ }).catch(done);
});
it("rejects with generic error", function(done) {
var res = {
@@ -143,8 +167,9 @@ describe('nodes/registry/installer', function() {
initInstaller(p)
installer.installModule("this_wont_exist").then(function() {
done(new Error("Unexpected success"));
- }).catch(function(err) {
- done();
+ }).catch(err => {
+ // Expected result
+ done()
});
});
it("succeeds when module is found", function(done) {
@@ -169,9 +194,7 @@ describe('nodes/registry/installer', function() {
// commsMessages[0].topic.should.equal("node/added");
// commsMessages[0].msg.should.eql(nodeInfo.nodes);
done();
- }).catch(function(err) {
- done(err);
- });
+ }).catch(done);
});
it("rejects when non-existant path is provided", function(done) {
this.timeout(20000);
@@ -208,9 +231,7 @@ describe('nodes/registry/installer', function() {
installer.installModule(resourcesDir).then(function(info) {
info.should.eql(nodeInfo);
done();
- }).catch(function(err) {
- done(err);
- });
+ }).catch(done);
});
it("succeeds when url is valid node-red module", function(done) {
var nodeInfo = {nodes:{module:"foo",types:["a"]}};
@@ -231,9 +252,7 @@ describe('nodes/registry/installer', function() {
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);
- });
+ }).catch(done);
});
});
@@ -265,8 +284,9 @@ describe('nodes/registry/installer', function() {
installer.uninstallModule("this_wont_exist").then(function() {
done(new Error("Unexpected success"));
- }).catch(function(err) {
- done();
+ }).catch(err => {
+ // Expected result
+ done()
});
});
it("succeeds when module is found", function(done) {
@@ -294,9 +314,7 @@ describe('nodes/registry/installer', function() {
// commsMessages[0].topic.should.equal("node/removed");
// commsMessages[0].msg.should.eql(nodeInfo);
done();
- }).catch(function(err) {
- done(err);
- });
+ }).catch(done);
});
});
});
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..aa46bc0e1 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() {
@@ -185,7 +189,8 @@ describe("@node-red/util/util", function() {
// setMessageProperty strips off `msg.` prefixes.
// setObjectProperty does not
var obj = {};
- util.setObjectProperty(obj,"msg.a","bar");
+ var result = util.setObjectProperty(obj,"msg.a","bar");
+ result.should.be.true();
obj.should.have.property("msg");
obj.msg.should.have.property("a","bar");
})
@@ -193,59 +198,111 @@ describe("@node-red/util/util", function() {
describe('setMessageProperty', function() {
it('sets a property', function() {
var msg = {a:"foo"};
- util.setMessageProperty(msg,"msg.a","bar");
+ var result = util.setMessageProperty(msg,"msg.a","bar");
+ result.should.be.true();
msg.a.should.eql('bar');
});
it('sets a deep level property', function() {
var msg = {a:{b:{c:"foo"}}};
- util.setMessageProperty(msg,"msg.a.b.c","bar");
+ var result = util.setMessageProperty(msg,"msg.a.b.c","bar");
+ result.should.be.true();
msg.a.b.c.should.eql('bar');
});
it('creates missing parent properties by default', function() {
var msg = {a:{}};
- util.setMessageProperty(msg,"msg.a.b.c","bar");
+ var result = util.setMessageProperty(msg,"msg.a.b.c","bar");
+ result.should.be.true();
msg.a.b.c.should.eql('bar');
})
it('does not create missing parent properties', function() {
var msg = {a:{}};
- util.setMessageProperty(msg,"msg.a.b.c","bar",false);
+ var result = util.setMessageProperty(msg,"msg.a.b.c","bar",false);
+ result.should.be.false();
should.not.exist(msg.a.b);
})
- it('does not create missing parent properties with array', function () {
+ it('does not create missing parent properties of array', function () {
var msg = {a:{}};
- util.setMessageProperty(msg, "msg.a.b[1].c", "bar", false);
+ var result = util.setMessageProperty(msg, "msg.a.b[1].c", "bar", false);
+ result.should.be.false();
should.not.exist(msg.a.b);
})
+
+ it('does not create missing parent properties of string', function() {
+ var msg = {a:"foo"};
+ var result = util.setMessageProperty(msg, "msg.a.b.c", "bar", false);
+ result.should.be.false();
+ should.not.exist(msg.a.b);
+ })
+ it('does not set property of existing string property', function() {
+ var msg = {a:"foo"};
+ var result = util.setMessageProperty(msg, "msg.a.b", "bar", false);
+ result.should.be.false();
+ should.not.exist(msg.a.b);
+ })
+
+ it('does not set property of existing number property', function() {
+ var msg = {a:123};
+ var result = util.setMessageProperty(msg, "msg.a.b", "bar", false);
+ result.should.be.false();
+ should.not.exist(msg.a.b);
+ })
+ it('does not create missing parent properties of number', function() {
+ var msg = {a:123};
+ var result = util.setMessageProperty(msg, "msg.a.b.c", "bar", false);
+ result.should.be.false();
+ should.not.exist(msg.a.b);
+ })
+
+ it('does not set property of existing boolean property', function() {
+ var msg = {a:true};
+ var result = util.setMessageProperty(msg, "msg.a.b", "bar", false);
+ result.should.be.false();
+ should.not.exist(msg.a.b);
+ })
+ it('does not create missing parent properties of boolean', function() {
+ var msg = {a:true};
+ var result = util.setMessageProperty(msg, "msg.a.b.c", "bar", false);
+ result.should.be.false();
+ should.not.exist(msg.a.b);
+ })
+
+
it('deletes property if value is undefined', function() {
var msg = {a:{b:{c:"foo"}}};
- util.setMessageProperty(msg,"msg.a.b.c",undefined);
+ var result = util.setMessageProperty(msg,"msg.a.b.c",undefined);
+ result.should.be.true();
should.not.exist(msg.a.b.c);
})
it('does not create missing parent properties if value is undefined', function() {
var msg = {a:{}};
- util.setMessageProperty(msg,"msg.a.b.c",undefined);
+ var result = util.setMessageProperty(msg,"msg.a.b.c",undefined);
+ result.should.be.false();
should.not.exist(msg.a.b);
});
it('sets a property with array syntax', function() {
var msg = {a:{b:["foo",{c:["",""]}]}};
- util.setMessageProperty(msg,"msg.a.b[1].c[1]","bar");
+ var result = util.setMessageProperty(msg,"msg.a.b[1].c[1]","bar");
+ result.should.be.true();
msg.a.b[1].c[1].should.eql('bar');
});
it('creates missing array elements - final property', function() {
var msg = {a:[]};
- util.setMessageProperty(msg,"msg.a[2]","bar");
+ var result = util.setMessageProperty(msg,"msg.a[2]","bar");
+ result.should.be.true();
msg.a.should.have.length(3);
msg.a[2].should.eql("bar");
});
it('creates missing array elements - mid property', function() {
var msg = {};
- util.setMessageProperty(msg,"msg.a[2].b","bar");
+ var result = util.setMessageProperty(msg,"msg.a[2].b","bar");
+ result.should.be.true();
msg.a.should.have.length(3);
msg.a[2].b.should.eql("bar");
});
it('creates missing array elements - multi-arrays', function() {
var msg = {};
- util.setMessageProperty(msg,"msg.a[2][2]","bar");
+ var result = util.setMessageProperty(msg,"msg.a[2][2]","bar");
+ result.should.be.true();
msg.a.should.have.length(3);
msg.a.should.be.instanceOf(Array);
msg.a[2].should.have.length(3);
@@ -254,19 +311,22 @@ describe("@node-red/util/util", function() {
});
it('does not create missing array elements - mid property', function () {
var msg = {a:[]};
- util.setMessageProperty(msg, "msg.a[1][1]", "bar", false);
+ var result = util.setMessageProperty(msg, "msg.a[1][1]", "bar", false);
+ result.should.be.false();
msg.a.should.empty();
});
it('does not create missing array elements - final property', function() {
var msg = {a:{}};
- util.setMessageProperty(msg,"msg.a.b[2]","bar",false);
+ var result = util.setMessageProperty(msg,"msg.a.b[2]","bar",false);
+ result.should.be.false();
should.not.exist(msg.a.b);
// check it has not been misinterpreted
msg.a.should.not.have.property("b[2]");
});
it('deletes property inside array if value is undefined', function() {
var msg = {a:[1,2,3]};
- util.setMessageProperty(msg,"msg.a[1]",undefined);
+ var result = util.setMessageProperty(msg,"msg.a[1]",undefined);
+ result.should.be.true();
msg.a.should.have.length(2);
msg.a[0].should.eql(1);
msg.a[1].should.eql(3);
@@ -495,12 +555,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(7, "days").add(1, "months").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 +915,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 +937,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 +947,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);