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

Merge branch 'config' into 0.14.0

explain why this merge is necessary,
This commit is contained in:
Nick O'Leary 2016-05-04 15:44:48 +01:00
commit 41445a1b48
34 changed files with 1887 additions and 917 deletions

View File

@ -120,6 +120,7 @@ module.exports = function(grunt) {
"editor/js/ui/tab-info.js", "editor/js/ui/tab-info.js",
"editor/js/ui/tab-config.js", "editor/js/ui/tab-config.js",
"editor/js/ui/editor.js", "editor/js/ui/editor.js",
"editor/js/ui/tray.js",
"editor/js/ui/clipboard.js", "editor/js/ui/clipboard.js",
"editor/js/ui/library.js", "editor/js/ui/library.js",
"editor/js/ui/notifications.js", "editor/js/ui/notifications.js",

View File

@ -273,6 +273,10 @@ RED.history = (function() {
RED.nodes.addLink(ev.removedLinks[i]); RED.nodes.addLink(ev.removedLinks[i]);
} }
} }
} else if (ev.t == "reorder") {
if (ev.order) {
RED.workspaces.order(ev.order);
}
} }
Object.keys(modifiedTabs).forEach(function(id) { Object.keys(modifiedTabs).forEach(function(id) {
var subflow = RED.nodes.subflow(id); var subflow = RED.nodes.subflow(id);

View File

@ -21,6 +21,7 @@ RED.nodes = (function() {
var links = []; var links = [];
var defaultWorkspace; var defaultWorkspace;
var workspaces = {}; var workspaces = {};
var workspacesOrder =[];
var subflows = {}; var subflows = {};
var dirty = false; var dirty = false;
@ -173,8 +174,10 @@ RED.nodes = (function() {
if (type && type.category == "config") { if (type && type.category == "config") {
var configNode = configNodes[n[d]]; var configNode = configNodes[n[d]];
if (configNode) { if (configNode) {
updatedConfigNode = true; if (configNode.users.indexOf(n) === -1) {
configNode.users.push(n); updatedConfigNode = true;
configNode.users.push(n);
}
} }
} }
} }
@ -269,12 +272,15 @@ RED.nodes = (function() {
function addWorkspace(ws) { function addWorkspace(ws) {
workspaces[ws.id] = ws; workspaces[ws.id] = ws;
workspacesOrder.push(ws.id);
} }
function getWorkspace(id) { function getWorkspace(id) {
return workspaces[id]; return workspaces[id];
} }
function removeWorkspace(id) { function removeWorkspace(id) {
delete workspaces[id]; delete workspaces[id];
workspacesOrder.splice(workspacesOrder.indexOf(id),1);
var removedNodes = []; var removedNodes = [];
var removedLinks = []; var removedLinks = [];
var n; var n;
@ -517,7 +523,7 @@ RED.nodes = (function() {
if ((exportable == null || exportable)) { if ((exportable == null || exportable)) {
if (!(node[d] in exportedConfigNodes)) { if (!(node[d] in exportedConfigNodes)) {
exportedConfigNodes[node[d]] = true; exportedConfigNodes[node[d]] = true;
nns.unshift(RED.nodes.convertNode(confNode)); set.push(confNode);
} }
} else { } else {
convertedNode[d] = ""; convertedNode[d] = "";
@ -537,11 +543,9 @@ RED.nodes = (function() {
function createCompleteNodeSet() { function createCompleteNodeSet() {
var nns = []; var nns = [];
var i; var i;
for (i in workspaces) { for (i=0;i<workspacesOrder.length;i++) {
if (workspaces.hasOwnProperty(i)) { if (workspaces[workspacesOrder[i]].type == "tab") {
if (workspaces[i].type == "tab") { nns.push(workspaces[workspacesOrder[i]]);
nns.push(workspaces[i]);
}
} }
} }
for (i in subflows) { for (i in subflows) {
@ -663,6 +667,9 @@ RED.nodes = (function() {
var new_links = []; var new_links = [];
var nid; var nid;
var def; var def;
var configNode;
// Find all tabs and subflow templates
for (i=0;i<newNodes.length;i++) { for (i=0;i<newNodes.length;i++) {
n = newNodes[i]; n = newNodes[i];
// TODO: remove workspace in next release+1 // TODO: remove workspace in next release+1
@ -706,6 +713,8 @@ RED.nodes = (function() {
addSubflow(n,createNewIds); addSubflow(n,createNewIds);
} }
} }
// Add a tab if there isn't one there already
if (defaultWorkspace == null) { if (defaultWorkspace == null) {
defaultWorkspace = { type:"tab", id:getID(), label:RED._('workspace.defaultName',{number:1})}; defaultWorkspace = { type:"tab", id:getID(), label:RED._('workspace.defaultName',{number:1})};
addWorkspace(defaultWorkspace); addWorkspace(defaultWorkspace);
@ -714,6 +723,7 @@ RED.nodes = (function() {
activeWorkspace = RED.workspaces.active(); activeWorkspace = RED.workspaces.active();
} }
// Find all config nodes and add them
for (i=0;i<newNodes.length;i++) { for (i=0;i<newNodes.length;i++) {
n = newNodes[i]; n = newNodes[i];
def = registry.getNodeType(n.type); def = registry.getNodeType(n.type);
@ -750,7 +760,7 @@ RED.nodes = (function() {
} }
if (!existingConfigNode) { //} || !compareNodes(existingConfigNode,n,true) || existingConfigNode._def.exclusive || existingConfigNode.z !== n.z) { if (!existingConfigNode) { //} || !compareNodes(existingConfigNode,n,true) || existingConfigNode._def.exclusive || existingConfigNode.z !== n.z) {
var configNode = {id:n.id, z:n.z, type:n.type, users:[]}; configNode = {id:n.id, z:n.z, type:n.type, users:[]};
for (var d in def.defaults) { for (var d in def.defaults) {
if (def.defaults.hasOwnProperty(d)) { if (def.defaults.hasOwnProperty(d)) {
configNode[d] = n[d]; configNode[d] = n[d];
@ -768,6 +778,7 @@ RED.nodes = (function() {
} }
} }
// Find regular flow nodes and subflow instances
for (i=0;i<newNodes.length;i++) { for (i=0;i<newNodes.length;i++) {
n = newNodes[i]; n = newNodes[i];
// TODO: remove workspace in next release+1 // TODO: remove workspace in next release+1
@ -838,15 +849,7 @@ RED.nodes = (function() {
node.outputs = n.outputs||node._def.outputs; node.outputs = n.outputs||node._def.outputs;
for (var d2 in node._def.defaults) { for (var d2 in node._def.defaults) {
if (node._def.defaults.hasOwnProperty(d2)) { if (node._def.defaults.hasOwnProperty(d2)) {
if (node._def.defaults[d2].type) { node[d2] = n[d2];
if (node_map[n[d2]]) {
node[d2] = node_map[n[d2]].id;
} else {
node[d2] = n[d2];
}
} else {
node[d2] = n[d2];
}
} }
} }
} }
@ -860,6 +863,7 @@ RED.nodes = (function() {
} }
} }
} }
// Remap all wires and config node references
for (i=0;i<new_nodes.length;i++) { for (i=0;i<new_nodes.length;i++) {
n = new_nodes[i]; n = new_nodes[i];
if (n.wires) { if (n.wires) {
@ -875,6 +879,19 @@ RED.nodes = (function() {
} }
delete n.wires; delete n.wires;
} }
for (var d3 in n._def.defaults) {
if (n._def.defaults.hasOwnProperty(d3)) {
if (n._def.defaults[d3].type && node_map[n[d3]]) {
n[d3] = node_map[n[d3]].id;
configNode = RED.nodes.node(n[d3]);
if (configNode && configNode.users.indexOf(n) === -1) {
configNode.users.push(n);
}
}
}
}
} }
for (i=0;i<new_subflows.length;i++) { for (i=0;i<new_subflows.length;i++) {
n = new_subflows[i]; n = new_subflows[i];
@ -972,6 +989,8 @@ RED.nodes = (function() {
addWorkspace: addWorkspace, addWorkspace: addWorkspace,
removeWorkspace: removeWorkspace, removeWorkspace: removeWorkspace,
getWorkspaceOrder: function() { return workspacesOrder },
setWorkspaceOrder: function(order) { workspacesOrder = order; },
workspace: getWorkspace, workspace: getWorkspace,
addSubflow: addSubflow, addSubflow: addSubflow,
@ -1004,10 +1023,8 @@ RED.nodes = (function() {
} }
}, },
eachWorkspace: function(cb) { eachWorkspace: function(cb) {
for (var id in workspaces) { for (var i=0;i<workspacesOrder.length;i++) {
if (workspaces.hasOwnProperty(id)) { cb(workspaces[workspacesOrder[i]]);
cb(workspaces[id]);
}
} }
}, },

View File

@ -23,7 +23,7 @@ RED.clipboard = (function() {
var importNodesDialog; var importNodesDialog;
function setupDialogs() { function setupDialogs() {
dialog = $('<div id="clipboard-dialog" class="hide"><form class="dialog-form form-horizontal"></form></div>') dialog = $('<div id="clipboard-dialog" class="hide node-red-dialog"><form class="dialog-form form-horizontal"></form></div>')
.appendTo("body") .appendTo("body")
.dialog({ .dialog({
modal: true, modal: true,
@ -31,14 +31,6 @@ RED.clipboard = (function() {
width: 500, width: 500,
resizable: false, resizable: false,
buttons: [ buttons: [
{
id: "clipboard-dialog-ok",
text: RED._("common.label.ok"),
click: function() {
RED.view.importNodes($("#clipboard-import").val());
$( this ).dialog( "close" );
}
},
{ {
id: "clipboard-dialog-cancel", id: "clipboard-dialog-cancel",
text: RED._("common.label.cancel"), text: RED._("common.label.cancel"),
@ -48,10 +40,20 @@ RED.clipboard = (function() {
}, },
{ {
id: "clipboard-dialog-close", id: "clipboard-dialog-close",
class: "primary",
text: RED._("common.label.close"), text: RED._("common.label.close"),
click: function() { click: function() {
$( this ).dialog( "close" ); $( this ).dialog( "close" );
} }
},
{
id: "clipboard-dialog-ok",
class: "primary",
text: RED._("common.label.import"),
click: function() {
RED.view.importNodes($("#clipboard-import").val());
$( this ).dialog( "close" );
}
} }
], ],
open: function(e) { open: function(e) {

File diff suppressed because it is too large Load Diff

View File

@ -254,7 +254,14 @@ RED.library = (function() {
height: 450, height: 450,
buttons: [ buttons: [
{ {
text: RED._("common.label.ok"), text: RED._("common.label.cancel"),
click: function() {
$( this ).dialog( "close" );
}
},
{
text: RED._("common.label.load"),
class: "primary",
click: function() { click: function() {
if (selectedLibraryItem) { if (selectedLibraryItem) {
for (var i=0;i<options.fields.length;i++) { for (var i=0;i<options.fields.length;i++) {
@ -265,12 +272,6 @@ RED.library = (function() {
} }
$( this ).dialog( "close" ); $( this ).dialog( "close" );
} }
},
{
text: RED._("common.label.cancel"),
click: function() {
$( this ).dialog( "close" );
}
} }
], ],
open: function(e) { open: function(e) {
@ -359,15 +360,16 @@ RED.library = (function() {
height: 230, height: 230,
buttons: [ buttons: [
{ {
text: RED._("common.label.ok"), text: RED._("common.label.cancel"),
click: function() { click: function() {
saveToLibrary(true);
$( this ).dialog( "close" ); $( this ).dialog( "close" );
} }
}, },
{ {
text: RED._("common.label.cancel"), text: RED._("common.label.save"),
class: "primary",
click: function() { click: function() {
saveToLibrary(true);
$( this ).dialog( "close" ); $( this ).dialog( "close" );
} }
} }
@ -381,15 +383,16 @@ RED.library = (function() {
height: 230, height: 230,
buttons: [ buttons: [
{ {
text: RED._("common.label.ok"), text: RED._("common.label.cancel"),
click: function() { click: function() {
saveToLibrary(false);
$( this ).dialog( "close" ); $( this ).dialog( "close" );
} }
}, },
{ {
text: RED._("common.label.cancel"), text: RED._("common.label.save"),
class: "primary",
click: function() { click: function() {
saveToLibrary(false);
$( this ).dialog( "close" ); $( this ).dialog( "close" );
} }
} }
@ -432,9 +435,17 @@ RED.library = (function() {
resizable: false, resizable: false,
title: RED._("library.exportToLibrary"), title: RED._("library.exportToLibrary"),
buttons: [ buttons: [
{
id: "library-dialog-cancel",
text: RED._("common.label.cancel"),
click: function() {
$( this ).dialog( "close" );
}
},
{ {
id: "library-dialog-ok", id: "library-dialog-ok",
text: RED._("common.label.ok"), class: "primary",
text: RED._("common.label.export"),
click: function() { click: function() {
//TODO: move this to RED.library //TODO: move this to RED.library
var flowName = $("#node-input-library-filename").val(); var flowName = $("#node-input-library-filename").val();
@ -457,13 +468,6 @@ RED.library = (function() {
} }
$( this ).dialog( "close" ); $( this ).dialog( "close" );
} }
},
{
id: "library-dialog-cancel",
text: RED._("common.label.cancel"),
click: function() {
$( this ).dialog( "close" );
}
} }
], ],
open: function(e) { open: function(e) {

View File

@ -102,13 +102,13 @@ RED.sidebar = (function() {
sidebarSeparator.start = ui.position.left; sidebarSeparator.start = ui.position.left;
sidebarSeparator.chartWidth = $("#workspace").width(); sidebarSeparator.chartWidth = $("#workspace").width();
sidebarSeparator.chartRight = winWidth-$("#workspace").width()-$("#workspace").offset().left-2; sidebarSeparator.chartRight = winWidth-$("#workspace").width()-$("#workspace").offset().left-2;
if (!RED.menu.isSelected("menu-item-sidebar")) { if (!RED.menu.isSelected("menu-item-sidebar")) {
sidebarSeparator.opening = true; sidebarSeparator.opening = true;
var newChartRight = 7; var newChartRight = 7;
$("#sidebar").addClass("closing"); $("#sidebar").addClass("closing");
$("#workspace").css("right",newChartRight); $("#workspace").css("right",newChartRight);
$("#editor-stack").css("right",newChartRight+1);
$("#sidebar").width(0); $("#sidebar").width(0);
RED.menu.setSelected("menu-item-sidebar",true); RED.menu.setSelected("menu-item-sidebar",true);
RED.events.emit("sidebar:resize"); RED.events.emit("sidebar:resize");
@ -147,6 +147,7 @@ RED.sidebar = (function() {
var newChartRight = sidebarSeparator.chartRight-d; var newChartRight = sidebarSeparator.chartRight-d;
$("#workspace").css("right",newChartRight); $("#workspace").css("right",newChartRight);
$("#editor-stack").css("right",newChartRight+1);
$("#sidebar").width(newSidebarWidth); $("#sidebar").width(newSidebarWidth);
sidebar_tabs.resize(); sidebar_tabs.resize();
@ -159,6 +160,7 @@ RED.sidebar = (function() {
if ($("#sidebar").width() < 180) { if ($("#sidebar").width() < 180) {
$("#sidebar").width(180); $("#sidebar").width(180);
$("#workspace").css("right",187); $("#workspace").css("right",187);
$("#editor-stack").css("right",188);
} }
} }
$("#sidebar-separator").css("left","auto"); $("#sidebar-separator").css("left","auto");

View File

@ -17,7 +17,7 @@ RED.sidebar.config = (function() {
var content = document.createElement("div"); var content = document.createElement("div");
content.className = "sidebar-node-config" content.className = "sidebar-node-config";
$('<div class="button-group sidebar-header">'+ $('<div class="button-group sidebar-header">'+
'<a class="sidebar-header-button-toggle selected" id="workspace-config-node-filter-all" href="#"><span data-i18n="sidebar.config.filterAll"></span></a>'+ '<a class="sidebar-header-button-toggle selected" id="workspace-config-node-filter-all" href="#"><span data-i18n="sidebar.config.filterAll"></span></a>'+
@ -35,6 +35,9 @@ RED.sidebar.config = (function() {
var flowCategories = $("<div>").appendTo(content); var flowCategories = $("<div>").appendTo(content);
var subflowCategories = $("<div>").appendTo(content); var subflowCategories = $("<div>").appendTo(content);
var shade = $('<div class="sidebar-node-config-shade hide"></div>').appendTo(content);
var showUnusedOnly = false; var showUnusedOnly = false;
var categories = {}; var categories = {};
@ -288,6 +291,12 @@ RED.sidebar.config = (function() {
return { return {
init:init, init:init,
show:show, show:show,
refresh:refreshConfigNodeList refresh:refreshConfigNodeList,
disable: function() {
shade.show();
},
enable: function() {
shade.hide();
}
} }
})(); })();

View File

@ -1,5 +1,5 @@
/** /**
* Copyright 2013, 2015 IBM Corp. * Copyright 2013, 2016 IBM Corp.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -24,7 +24,7 @@ RED.tabs = (function() {
var currentTabWidth; var currentTabWidth;
var currentActiveTabWidth = 0; var currentActiveTabWidth = 0;
var ul = $("#"+options.id) var ul = $("#"+options.id);
ul.addClass("red-ui-tabs"); ul.addClass("red-ui-tabs");
ul.children().first().addClass("active"); ul.children().first().addClass("active");
ul.children().addClass("red-ui-tab"); ul.children().addClass("red-ui-tab");
@ -118,6 +118,7 @@ RED.tabs = (function() {
addTab: function(tab) { addTab: function(tab) {
tabs[tab.id] = tab; tabs[tab.id] = tab;
var li = $("<li/>",{class:"red-ui-tab"}).appendTo(ul); var li = $("<li/>",{class:"red-ui-tab"}).appendTo(ul);
li.data("tabId",tab.id);
var link = $("<a/>",{href:"#"+tab.id, class:"red-ui-tab-label"}).appendTo(li); var link = $("<a/>",{href:"#"+tab.id, class:"red-ui-tab-label"}).appendTo(li);
if (tab.icon) { if (tab.icon) {
$('<img src="'+tab.icon+'" class="red-ui-tab-icon"/>').appendTo(link); $('<img src="'+tab.icon+'" class="red-ui-tab-icon"/>').appendTo(link);
@ -142,6 +143,85 @@ RED.tabs = (function() {
if (ul.find("li.red-ui-tab").size() == 1) { if (ul.find("li.red-ui-tab").size() == 1) {
activateTab(link); activateTab(link);
} }
if (options.onreorder) {
var originalTabOrder;
var tabDragIndex;
var tabElements = [];
var startDragIndex;
li.draggable({
axis:"x",
distance: 20,
start: function(event,ui) {
originalTabOrder = [];
tabElements = [];
ul.children().each(function(i) {
tabElements[i] = {
el:$(this),
text: $(this).text(),
left: $(this).position().left,
width: $(this).width()
};
if ($(this).is(li)) {
tabDragIndex = i;
startDragIndex = i;
}
originalTabOrder.push($(this).data("tabId"));
});
ul.children().each(function(i) {
if (i!==tabDragIndex) {
$(this).css({
position: 'absolute',
left: tabElements[i].left+"px",
width: tabElements[i].width+2,
transition: "left 0.3s"
});
}
})
if (!li.hasClass('active')) {
li.css({'zIndex':1});
}
},
drag: function(event,ui) {
ui.position.left += tabElements[tabDragIndex].left;
var tabCenter = ui.position.left + tabElements[tabDragIndex].width/2;
for (var i=0;i<tabElements.length;i++) {
if (i === tabDragIndex) {
continue;
}
if (tabCenter > tabElements[i].left && tabCenter < tabElements[i].left+tabElements[i].width) {
if (i < tabDragIndex) {
tabElements[i].left += tabElements[tabDragIndex].width+8;
tabElements[tabDragIndex].el.detach().insertBefore(tabElements[i].el);
} else {
tabElements[i].left -= tabElements[tabDragIndex].width+8;
tabElements[tabDragIndex].el.detach().insertAfter(tabElements[i].el);
}
tabElements[i].el.css({left:tabElements[i].left+"px"});
tabElements.splice(i, 0, tabElements.splice(tabDragIndex, 1)[0]);
tabDragIndex = i;
break;
}
}
// console.log(ui.position.left,ui.offset.left);
},
stop: function(event,ui) {
ul.children().css({position:"relative",left:"",transition:""});
if (!li.hasClass('active')) {
li.css({zIndex:""});
}
updateTabWidths();
if (startDragIndex !== tabDragIndex) {
options.onreorder(originalTabOrder, $.makeArray(ul.children().map(function() { return $(this).data('tabId');})));
}
activateTab(tabElements[tabDragIndex].el.data('tabId'));
}
})
}
}, },
removeTab: removeTab, removeTab: removeTab,
activateTab: activateTab, activateTab: activateTab,
@ -158,6 +238,30 @@ RED.tabs = (function() {
tab.attr("title",label); tab.attr("title",label);
tab.find("span").text(label); tab.find("span").text(label);
updateTabWidths(); updateTabWidths();
},
order: function(order) {
var existingTabOrder = $.makeArray(ul.children().map(function() { return $(this).data('tabId');}));
if (existingTabOrder.length !== order.length) {
return
}
var i;
var match = true;
for (i=0;i<order.length;i++) {
if (order[i] !== existingTabOrder[i]) {
match = false;
break;
}
}
if (match) {
return;
}
var existingTabMap = {};
var existingTabs = ul.children().detach().each(function() {
existingTabMap[$(this).data("tabId")] = $(this);
});
for (i=0;i<order.length;i++) {
existingTabMap[order[i]].appendTo(ul);
}
} }
} }

225
editor/js/ui/tray.js Normal file
View File

@ -0,0 +1,225 @@
/**
* Copyright 2016 IBM Corp.
*
* 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.
**/
RED.tray = (function() {
var stack = [];
function resize() {
}
function showTray(options) {
var el = $('<div class="editor-tray"></div>');
var header = $('<div class="editor-tray-header"></div>').appendTo(el);
var body = $('<div class="editor-tray-body"></div>').appendTo(el);
var footer = $('<div class="editor-tray-footer"></div>').appendTo(el);
var resizer = $('<div class="editor-tray-resize-handle"></div>').appendTo(el);
// var growButton = $('<a class="editor-tray-resize-button" style="cursor: w-resize;"><i class="fa fa-angle-left"></i></a>').appendTo(resizer);
// var shrinkButton = $('<a class="editor-tray-resize-button" style="cursor: e-resize;"><i style="margin-left: 1px;" class="fa fa-angle-right"></i></a>').appendTo(resizer);
if (options.title) {
$('<div class="editor-tray-titlebar">'+options.title+'</div>').appendTo(header);
}
var buttonBar = $('<div class="editor-tray-toolbar"></div>').appendTo(header);
if (options.buttons) {
for (var i=0;i<options.buttons.length;i++) {
var button = options.buttons[i];
var b = $('<button>').appendTo(buttonBar);
if (button.id) {
b.attr('id',button.id);
}
if (button.text) {
b.html(button.text);
}
if (button.click) {
b.click(button.click);
}
if (button.class) {
b.addClass(button.class);
}
}
}
el.appendTo("#editor-stack");
var tray = {
tray: el,
header: header,
body: body,
footer: footer,
options: options
};
stack.push(tray);
el.draggable({
handle: resizer,
axis: "x",
start:function(event,ui) {
el.width('auto');
},
drag: function(event,ui) {
if (ui.position.left > -tray.preferredWidth-1) {
ui.position.left = -tray.preferredWidth-1;
} else if (tray.options.resize) {
setTimeout(function() {
tray.options.resize({width: -ui.position.left});
},0);
}
tray.width = -ui.position.left;
},
stop:function(event,ui) {
el.width(-ui.position.left);
el.css({left:''});
if (tray.options.resize) {
tray.options.resize({width: -ui.position.left});
}
tray.width = -ui.position.left;
}
});
if (options.open) {
options.open(el);
}
$("#header-shade").show();
$("#editor-shade").show();
RED.sidebar.config.disable();
tray.preferredWidth = el.width();
if (options.width) {
if (options.width > $("#editor-stack").position().left-8) {
options.width = $("#editor-stack").position().left-8;
}
el.width(options.width);
}
tray.width = el.width();
el.css({
right: -(el.width()+10)+"px",
transition: "right 0.2s ease"
});
$("#workspace").scrollLeft(0);
var trayHeight = el.height()-header.outerHeight()-footer.outerHeight();
body.height(trayHeight-40);
setTimeout(function() {
setTimeout(function() {
if (!options.width) {
el.width(tray.preferredWidth);
}
if (options.resize) {
options.resize({width:el.width()});
}
if (options.show) {
options.show();
}
},150);
el.css({right:0});
},0);
// growButton.click(function(e) {
// e.preventDefault();
// tray.lastWidth = tray.width;
// tray.width = $("#editor-stack").position().left-8;
// el.width(tray.width);
// if (options.resize) {
// options.resize({width:tray.width});
// }
// });
// shrinkButton.click(function(e) {
// e.preventDefault();
// if (tray.lastWidth && tray.width > tray.lastWidth) {
// tray.width = tray.lastWidth;
// } else if (tray.width > tray.preferredWidth) {
// tray.width = tray.preferredWidth;
// }
// el.width(tray.width);
// if (options.resize) {
// options.resize({width:tray.width});
// }
// });
}
function handleWindowResize() {
if (stack.length > 0) {
var tray = stack[stack.length-1];
var trayHeight = tray.tray.height()-tray.header.outerHeight()-tray.footer.outerHeight();
tray.body.height(trayHeight-40);
if (tray.width > $("#editor-stack").position().left-8) {
tray.width = Math.max(tray.preferredWidth,$("#editor-stack").position().left-8);
tray.tray.width(tray.width);
}
if (tray.options.resize) {
tray.options.resize({width:tray.width});
}
}
}
return {
init: function init() {
$(window).resize(handleWindowResize);
RED.events.on("sidebar:resize",handleWindowResize);
},
show: function show(options) {
if (stack.length > 0) {
var oldTray = stack[stack.length-1];
oldTray.tray.css({
right: -(oldTray.tray.width()+10)+"px"
});
setTimeout(function() {
oldTray.tray.detach();
showTray(options);
},200)
} else {
showTray(options);
}
},
close: function close(done) {
if (stack.length > 0) {
var tray = stack.pop();
tray.tray.css({
right: -(tray.tray.width()+10)+"px"
});
setTimeout(function() {
if (tray.options.close) {
tray.options.close();
}
tray.tray.remove();
if (stack.length > 0) {
var oldTray = stack[stack.length-1];
oldTray.tray.appendTo("#editor-stack");
setTimeout(function() {
handleWindowResize();
oldTray.tray.css({right:0});
if (oldTray.options.show) {
oldTray.options.show();
}
},0);
}
if (done) {
done();
}
},200)
if (stack.length === 0) {
$("#header-shade").hide();
$("#editor-shade").hide();
RED.sidebar.config.enable();
}
}
}
}
})();

View File

@ -334,7 +334,7 @@ RED.view = (function() {
} }
} }
var nn = { id:(1+Math.random()*4294967295).toString(16),z:RED.workspaces.active()}; var nn = { id:RED.nodes.id(),z:RED.workspaces.active()};
nn.type = selected_tool; nn.type = selected_tool;
nn._def = RED.nodes.getType(nn.type); nn._def = RED.nodes.getType(nn.type);

View File

@ -1,5 +1,5 @@
/** /**
* Copyright 2015 IBM Corp. * Copyright 2015, 2016 IBM Corp.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -113,6 +113,11 @@ RED.workspaces = (function() {
RED.menu.setDisabled("menu-item-workspace-delete",workspace_tabs.count() == 1); RED.menu.setDisabled("menu-item-workspace-delete",workspace_tabs.count() == 1);
RED.menu.removeItem("menu-item-workspace-menu-"+tab.id.replace(".","-")); RED.menu.removeItem("menu-item-workspace-menu-"+tab.id.replace(".","-"));
}, },
onreorder: function(oldOrder, newOrder) {
RED.history.push({t:'reorder',order:oldOrder,dirty:RED.nodes.dirty()});
RED.nodes.dirty(true);
setWorkspaceOrder(newOrder);
},
minimumActiveTabWidth: 150 minimumActiveTabWidth: 150
}); });
@ -134,7 +139,14 @@ RED.workspaces = (function() {
} }
}, },
{ {
text: RED._("common.label.ok"), text: RED._("common.label.cancel"),
click: function() {
$( this ).dialog( "close" );
}
},
{
text: RED._("common.label.done"),
class: "primary",
click: function() { click: function() {
var workspace = $(this).dialog('option','workspace'); var workspace = $(this).dialog('option','workspace');
var label = $( "#node-input-workspace-name" ).val(); var label = $( "#node-input-workspace-name" ).val();
@ -146,13 +158,8 @@ RED.workspaces = (function() {
} }
$( this ).dialog( "close" ); $( this ).dialog( "close" );
} }
},
{
text: RED._("common.label.cancel"),
click: function() {
$( this ).dialog( "close" );
}
} }
], ],
open: function(e) { open: function(e) {
RED.keyboard.disable(); RED.keyboard.disable();
@ -216,11 +223,18 @@ RED.workspaces = (function() {
} }
} }
function setWorkspaceOrder(order) {
RED.nodes.setWorkspaceOrder(order.filter(function(id) {
return RED.nodes.workspace(id) !== undefined;
}));
workspace_tabs.order(order);
}
return { return {
init: init, init: init,
add: addWorkspace, add: addWorkspace,
remove: removeWorkspace, remove: removeWorkspace,
order: setWorkspaceOrder,
edit: function(id) { edit: function(id) {
showRenameWorkspaceDialog(id||activeWorkspace); showRenameWorkspaceDialog(id||activeWorkspace);
}, },

View File

@ -14,6 +14,8 @@
* limitations under the License. * limitations under the License.
**/ **/
$background-color: #f3f3f3;
$form-placeholder-color: #bbbbbb; $form-placeholder-color: #bbbbbb;
$form-text-color: #444; $form-text-color: #444;
$form-input-focus-color: rgba(85,150,230,0.8); $form-input-focus-color: rgba(85,150,230,0.8);
@ -45,6 +47,13 @@ $workspace-button-color-hover: #666;
$workspace-button-color-active: #666; $workspace-button-color-active: #666;
$workspace-button-color-selected: #AAA; $workspace-button-color-selected: #AAA;
$workspace-button-color-focus-outline: rgba(85,150,230,0.2);
$typedInput-button-background: #efefef; $typedInput-button-background: #efefef;
$typedInput-button-background-hover: #ddd; $typedInput-button-background-hover: #ddd;
$typedInput-button-background-active: #e3e3e3; $typedInput-button-background-active: #e3e3e3;
$editor-button-color-primary: #666;
$editor-button-color-secondary: #999;
$shade-color: rgba(220,220,220,0.5);

View File

@ -1,5 +1,5 @@
/** /**
* Copyright 2015 IBM Corp. * Copyright 2015, 2016 IBM Corp.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,7 +14,144 @@
* limitations under the License. * limitations under the License.
**/ **/
.dialog-form, #dialog-form, #dialog-config-form {
#editor-stack {
position: absolute;
margin: 0;
top: 0;
bottom: 0px;
right: 323px;
width: 0;
z-index: 5;
}
.editor-tray {
position:absolute;
margin: 0;
top: 0;
min-width: 500px;
width: auto;
right: -1000px;
bottom: 0;
background: #fff;
border-left: 1px solid $secondary-border-color;
border-bottom: 1px solid $primary-border-color;
box-sizing: content-box;
}
.editor-tray.open {
right: 0;
}
.editor-tray-body {
width: auto;
padding: 20px;
min-width: 500px;
box-sizing: border-box;
overflow-y: scroll;
}
.editor-tray-header {
@include disable-selection;
position: relative;
box-sizing: border-box;
font-weight: bold;
border-bottom: 1px solid $secondary-border-color;
background: $palette-header-background;
&:after {
content: "";
display: table;
clear: both;
}
}
.editor-tray-footer {
@include component-footer;
height: 35px;
}
.editor-tray-toolbar {
text-align: right;
padding: 8px;
button {
@include workspace-button;
font-size: 14px;
padding: 6px 14px;
margin-right: 8px;
color: $editor-button-color-primary;
&.leftButton {
float: left;
margin-top: 1px;
}
&:not(.leftButton):not(:last-child) {
margin-right: 16px;
}
&:not(.primary) {
background: none;
&:hover {
color: $editor-button-color-primary;
}
}
}
}
.editor-tray-titlebar {
border-bottom: 1px solid $secondary-border-color;
padding: 8px;
}
.editor-tray-breadcrumbs {
list-style-type: none;
margin: 0;
padding:0;
li {
display: inline-block;
padding:0;
margin:0;
&:not(:last-child) {
color: $editor-button-color-secondary;
font-weight: normal;
&:after {
display: inline-block;
content: '>';
margin: 0 5px;
}
}
}
}
.editor-tray-resize-handle {
position: absolute;
top: 0px;
bottom: 0px;
width: 7px;
left: -9px;
background: $background-color url(images/grip.png) no-repeat 50% 50%;
cursor: col-resize;
border-left: 1px solid $primary-border-color;
box-shadow: -1px 0 6px rgba(0,0,0,0.1);
}
.editor-tray-resize-button {
@include workspace-button;
display: block;
height: 37px;
line-height: 35px;
border: none;
border-bottom: 1px solid $secondary-border-color;
margin: 0;
background: $background-color;
color: $workspace-button-color;
}
#editor-shade, #header-shade {
position: absolute;
top:0;
bottom:0;
left:0;
right:0;
background: $shade-color;
}
.dialog-form,#dialog-form, #dialog-config-form {
margin: 0; margin: 0;
height: 100%; height: 100%;
} }
@ -23,10 +160,6 @@
border-color: rgb(214, 97, 95) !important; border-color: rgb(214, 97, 95) !important;
} }
.ui-dialog .ui-dialog-buttonpane button.leftButton {
margin-right: 40px;
}
.form-row { .form-row {
clear: both; clear: both;
color: $form-text-color; color: $form-text-color;
@ -83,6 +216,7 @@
cursor: auto; cursor: auto;
float: right; float: right;
font-size: 12px !important; font-size: 12px !important;
line-height: 35px;
} }
#node-config-dialog-scope-warning { #node-config-dialog-scope-warning {
display: inline-block; display: inline-block;
@ -94,13 +228,14 @@
margin: 1px 0 0 0; margin: 1px 0 0 0;
padding: 0; padding: 0;
height: 22px; height: 22px;
width: 110px; width: 150px;
} }
#node-config-dialog-user-count { #node-config-dialog-user-count {
vertical-align: middle; vertical-align: middle;
display:inline-block; display:inline-block;
margin-top: 10px;
margin-right: 20px; margin-right: 20px;
float:left; float:left;
font-size: 12px; font-size: 12px;
line-height: 35px;
} }

View File

@ -73,6 +73,32 @@
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { .ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset {
float: none; float: none;
} }
.ui-dialog-buttonset button {
@include workspace-button;
font-size: 14px;
padding: 6px 14px;
margin-right: 8px;
color: $editor-button-color-primary;
&.leftButton {
float: left;
margin-top: 7px;
}
&:not(.leftButton):not(:last-child) {
margin-right: 16px;
}
&:not(.primary) {
background: none;
&:hover {
color: $editor-button-color-primary;
}
}
.ui-button-text {
padding: 0;
}
}
.ui-dialog .ui-dialog-buttonpane { .ui-dialog .ui-dialog-buttonpane {
padding: .3em 1em .5em 1em; padding: .3em 1em .5em 1em;
} }

View File

@ -62,10 +62,13 @@
background: $workspace-button-background-active; background: $workspace-button-background-active;
cursor: default; cursor: default;
} }
.button-group &:not(:first-child) { .button-group &:not(:first-child) {
border-left: none; border-left: none;
} }
&:focus {
outline: 1px solid $workspace-button-color-focus-outline;
}
} }
@mixin workspace-button-toggle { @mixin workspace-button-toggle {
@include workspace-button; @include workspace-button;

View File

@ -22,6 +22,7 @@
width: 315px; width: 315px;
background: #fff; background: #fff;
box-sizing: border-box; box-sizing: border-box;
z-index: 10;
@include component-border; @include component-border;
} }
@ -46,13 +47,15 @@
right: 315px; right: 315px;
bottom:10px; bottom:10px;
width: 7px; width: 7px;
background: url(images/grip.png) no-repeat 50% 50%; z-index: 11;
background: $background-color url(images/grip.png) no-repeat 50% 50%;
cursor: col-resize; cursor: col-resize;
} }
.sidebar-closed > #sidebar { display: none; } .sidebar-closed > #sidebar { display: none; }
.sidebar-closed > #sidebar-separator { right: 0px !important; } .sidebar-closed > #sidebar-separator { right: 0px !important; }
.sidebar-closed > #workspace { right: 7px !important; } .sidebar-closed > #workspace { right: 7px !important; }
.sidebar-closed > #editor-stack { right: 8px !important; }
#sidebar .button { #sidebar .button {
@include workspace-button; @include workspace-button;

View File

@ -52,7 +52,7 @@ body {
font-size: 14px; font-size: 14px;
font-family: 'Helvetica Neue', Arial, Helvetica, sans-serif; font-family: 'Helvetica Neue', Arial, Helvetica, sans-serif;
padding-top: 100px; padding-top: 100px;
background: #f3f3f3; background: $background-color;
} }
#main-container { #main-container {

View File

@ -15,11 +15,21 @@
**/ **/
.sidebar-node-config { .sidebar-node-config {
position: relative;
background: #f3f3f3; background: #f3f3f3;
height: 100%; height: 100%;
overflow-y:auto; overflow-y:auto;
@include disable-selection; @include disable-selection;
} }
.sidebar-node-config-shade {
position: absolute;
top:0;
bottom:0;
left:0;
right:0;
background: $shade-color;
}
.config-node-list { .config-node-list {
margin: 0; margin: 0;

View File

@ -104,6 +104,7 @@ ul.red-ui-tabs li.active {
background: $tab-background-active; background: $tab-background-active;
font-weight: bold; font-weight: bold;
border-bottom: 1px solid #fff; border-bottom: 1px solid #fff;
z-index: 2;
} }
ul.red-ui-tabs li.active a { ul.red-ui-tabs li.active a {
color: #333; color: #333;

View File

@ -50,6 +50,8 @@
margin-right: 35px; margin-right: 35px;
} }
#workspace-add-tab { #workspace-add-tab {
position: absolute; position: absolute;
box-sizing: border-box; box-sizing: border-box;

View File

@ -39,6 +39,7 @@
<ul class="header-toolbar hide"> <ul class="header-toolbar hide">
<li><a id="btn-sidemenu" class="button" data-toggle="dropdown" href="#"><i class="fa fa-bars"></i></a></li> <li><a id="btn-sidemenu" class="button" data-toggle="dropdown" href="#"><i class="fa fa-bars"></i></a></li>
</ul> </ul>
<div id="header-shade" class="hide"></div>
</div> </div>
<div id="main-container" class="sidebar-closed hide"> <div id="main-container" class="sidebar-closed hide">
<div id="palette"> <div id="palette">
@ -65,6 +66,8 @@
<a class="workspace-footer-button" id="btn-zoom-in" href="#"><i class="fa fa-plus"></i></a> <a class="workspace-footer-button" id="btn-zoom-in" href="#"><i class="fa fa-plus"></i></a>
</div> </div>
</div> </div>
<div id="editor-shade" class="hide"></div>
<div id="editor-stack"></div>
<div id="sidebar"> <div id="sidebar">
<ul id="sidebar-tabs"></ul> <ul id="sidebar-tabs"></ul>
<div id="sidebar-content"></div> <div id="sidebar-content"></div>
@ -78,24 +81,6 @@
<div id="notifications"></div> <div id="notifications"></div>
<div id="dropTarget"><div data-i18n="[append]workspace.dropFlowHere"><br/><i class="fa fa-download"></i></div></div> <div id="dropTarget"><div data-i18n="[append]workspace.dropFlowHere"><br/><i class="fa fa-download"></i></div></div>
<div id="dialog" class="hide"><form id="dialog-form" class="form-horizontal"></form></div>
<div id="node-config-dialog" class="hide"><form id="dialog-config-form" class="form-horizontal"><div id="node-config-dialog-edit-form"></div><!--<div id="node-config-dialog-toolbar" class="form-row"><label><span>Node scope</span></label><select id="node-config-dialog-scope"></select></div>--></form></div>
<div id="subflow-dialog" class="hide">
<form class="form-horizontal">
<div class="form-row">
<label for="subflow-input-name" data-i18n="common.label.name"></label><input type="text" id="subflow-input-name">
</div>
<div class="form-row" style="margin-bottom: 0px;">
<label for="subflow-input-info" data-i18n="subflow.info"></label>
<a href="https://help.github.com/articles/markdown-basics/" style="font-size: 0.8em; float: right;" data-i18n="[html]subflow.format"></a>
</div>
<div class="form-row node-text-editor-row">
<div style="height: 250px;" class="node-text-editor" id="subflow-input-info-editor"></div>
</div>
<div class="form-row form-tips" id="subflow-dialog-user-count"></div>
</form>
</div>
<div id="node-dialog-confirm-deploy" class="hide"> <div id="node-dialog-confirm-deploy" class="hide">
<form class="form-horizontal"> <form class="form-horizontal">
<div id="node-dialog-confirm-deploy-config" style="text-align: left; padding-top: 30px;" data-i18n="[prepend]deploy.confirm.improperlyConfigured;[append]deploy.confirm.confirm"> <div id="node-dialog-confirm-deploy-config" style="text-align: left; padding-top: 30px;" data-i18n="[prepend]deploy.confirm.improperlyConfigured;[append]deploy.confirm.confirm">
@ -166,6 +151,20 @@
</div> </div>
</script> </script>
<script type="text/x-red" data-template-name="subflow-template">
<div class="form-row">
<label for="subflow-input-name" data-i18n="common.label.name"></label><input type="text" id="subflow-input-name">
</div>
<div class="form-row" style="margin-bottom: 0px;">
<label for="subflow-input-info" data-i18n="subflow.info"></label>
<a href="https://help.github.com/articles/markdown-basics/" style="font-size: 0.8em; float: right;" data-i18n="[html]subflow.format"></a>
</div>
<div class="form-row node-text-editor-row">
<div style="height: 250px;" class="node-text-editor" id="subflow-input-info-editor"></div>
</div>
<div class="form-row form-tips" id="subflow-dialog-user-count"></div>
</script>
<script src="vendor/vendor.js"></script> <script src="vendor/vendor.js"></script>
<script src="vendor/ace/ace.js"></script> <script src="vendor/ace/ace.js"></script>
<script src="vendor/ace/ext-language_tools.js"></script> <script src="vendor/ace/ext-language_tools.js"></script>

73
nodes/core/io/05-tls.html Normal file
View File

@ -0,0 +1,73 @@
<!--
Copyright 2016 IBM Corp.
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.
-->
<script type="text/x-red" data-template-name="tls-config">
<div class="form-row">
<label style="width: 120px;" for="node-config-input-cert"><i class="fa fa-file-text-o"></i> <span data-i18n="tls.label.cert"></span></label>
<input style="width: 60%;" type="text" id="node-config-input-cert" data-i18n="[placeholder]tls.placeholder.cert">
</div>
<div class="form-row">
<label style="width: 120px;" for="node-config-input-key"><i class="fa fa-file-text-o"></i> <span data-i18n="tls.label.key"></span></label>
<input style="width: 60%;" type="text" id="node-config-input-key" data-i18n="[placeholder]tls.placeholder.key">
</div>
<div class="form-row">
<label style="width: 120px;" for="node-config-input-ca"><i class="fa fa-file-text-o"></i> <span data-i18n="tls.label.ca"></span></label>
<input style="width: 60%;" type="text" id="node-config-input-ca" data-i18n="[placeholder]tls.placeholder.ca">
</div>
<div class="form-row">
<input type="checkbox" id="node-config-input-verifyservercert" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-config-input-verifyservercert" style="width: 70%;" data-i18n="tls.label.verify-server-cert"></label>
</div>
<div class="form-row">
<label style="width: 120px;" for="node-config-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input style="width: 60%;" type="text" id="node-config-input-name" data-i18n="[placeholder]common.label.name">
</div>
</script>
<script type="text/x-red" data-help-name="tls-config">
<p>Configuration options for TLS connections.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('tls-config',{
category: 'config',
defaults: {
name: {value:""},
cert: {value:"", validate: function(v) {
var currentKey = $("#node-config-input-key").val();
if (currentKey === undefined) {
currentKey = this.key;
}
return currentKey === '' || v != '';
}},
key: {value:"", validate: function(v) {
var currentCert = $("#node-config-input-cert").val();
if (currentCert === undefined) {
currentCert = this.cert;
}
return currentCert === '' || v != '';
}},
ca: {value:""},
verifyservercert: {value: true}
},
label: function() {
return this.name || this._("tls.tls");
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>

69
nodes/core/io/05-tls.js Normal file
View File

@ -0,0 +1,69 @@
/**
* Copyright 2016 IBM Corp.
*
* 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 fs = require('fs');
module.exports = function(RED) {
"use strict";
function TLSConfig(n) {
RED.nodes.createNode(this,n);
this.valid = true;
var certPath = n.cert.trim();
var keyPath = n.key.trim();
var caPath = n.ca.trim();
if ( (certPath.length > 0) !== (keyPath.length > 0)) {
this.valid = false;
this.error(RED._("tls.error.missing-file"));
return;
}
this.verifyservercert = n.verifyservercert;
try {
if (certPath) {
this.cert = fs.readFileSync(certPath);
}
if (keyPath) {
this.key = fs.readFileSync(keyPath);
}
if (caPath) {
this.ca = fs.readFileSync(caPath);
}
} catch(err) {
this.valid = false;
this.error(err.toString());
return;
}
}
RED.nodes.registerType("tls-config",TLSConfig);
TLSConfig.prototype.addTLSOptions = function(opts) {
if (this.valid) {
if (this.key) {
opts.key = this.key;
}
if (this.cert) {
opts.cert = this.cert;
}
if (this.ca) {
opts.ca = this.ca;
}
opts.rejectUnauthorized = this.verifyservercert;
}
return opts;
}
}

View File

@ -150,11 +150,17 @@
<label for="node-config-input-port" style="margin-left: 10px; width: 35px; "> <span data-i18n="mqtt.label.port"></span></label> <label for="node-config-input-port" style="margin-left: 10px; width: 35px; "> <span data-i18n="mqtt.label.port"></span></label>
<input type="text" id="node-config-input-port" data-i18n="[placeholder]mqtt.label.port" style="width:45px"> <input type="text" id="node-config-input-port" data-i18n="[placeholder]mqtt.label.port" style="width:45px">
</div> </div>
<div class="form-row">
<input type="checkbox" id="node-config-input-usetls" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-config-input-usetls" style="width: auto" data-i18n="mqtt.label.use-tls"></label>
<div id="node-config-row-tls" class="hide">
<label style="width: auto; margin-left: 20px; margin-right: 10px;" for="node-config-input-tls"><span data-i18n="mqtt.label.tls-config"></span></label><input style="width: 300px;" type="text" id="node-config-input-tls">
</div>
</div>
<div class="form-row"> <div class="form-row">
<label for="node-config-input-clientid"><i class="fa fa-tag"></i> <span data-i18n="mqtt.label.clientid"></span></label> <label for="node-config-input-clientid"><i class="fa fa-tag"></i> <span data-i18n="mqtt.label.clientid"></span></label>
<input type="text" id="node-config-input-clientid" data-i18n="[placeholder]mqtt.placeholder.clientid"> <input type="text" id="node-config-input-clientid" data-i18n="[placeholder]mqtt.placeholder.clientid">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-config-input-keepalive" style="width: auto"><i class="fa fa-clock-o"></i> <span data-i18n="mqtt.label.keepalive"></span></label> <label for="node-config-input-keepalive" style="width: auto"><i class="fa fa-clock-o"></i> <span data-i18n="mqtt.label.keepalive"></span></label>
<input type="text" id="node-config-input-keepalive" style="width: 50px"> <input type="text" id="node-config-input-keepalive" style="width: 50px">
@ -175,14 +181,6 @@
<label for="node-config-input-password"><i class="fa fa-lock"></i> <span data-i18n="common.label.password"></span></label> <label for="node-config-input-password"><i class="fa fa-lock"></i> <span data-i18n="common.label.password"></span></label>
<input type="password" id="node-config-input-password"> <input type="password" id="node-config-input-password">
</div> </div>
<div class="form-row">
<input type="checkbox" id="node-config-input-usetls" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-config-input-usetls" style="width: 70%;" data-i18n="mqtt.label.use-tls"></label>
</div>
<div class="form-row">
<input type="checkbox" id="node-config-input-verifyservercert" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-config-input-verifyservercert" style="width: 70%;" data-i18n="mqtt.label.verify-server-cert"></label>
</div>
</div> </div>
<div id="mqtt-broker-tab-birth" style="display:none"> <div id="mqtt-broker-tab-birth" style="display:none">
<div class="form-row"> <div class="form-row">
@ -240,6 +238,7 @@
defaults: { defaults: {
broker: {value:"",required:true}, broker: {value:"",required:true},
port: {value:1883,required:true,validate:RED.validators.number()}, port: {value:1883,required:true,validate:RED.validators.number()},
tls: {type:"tls-config",required: false},
clientid: { value:"", validate: function(v) { clientid: { value:"", validate: function(v) {
if ($("#node-config-input-clientid").length) { if ($("#node-config-input-clientid").length) {
// Currently editing the node // Currently editing the node
@ -303,10 +302,6 @@
this.usetls = false; this.usetls = false;
$("#node-config-input-usetls").prop("checked",false); $("#node-config-input-usetls").prop("checked",false);
} }
if (typeof this.verifyservercert === 'undefined'){
this.verifyservercert = true;
$("#node-config-input-verifyservercert").prop("checked",true);
}
if (typeof this.compatmode === 'undefined'){ if (typeof this.compatmode === 'undefined'){
this.compatmode = true; this.compatmode = true;
$("#node-config-input-compatmode").prop('checked', true); $("#node-config-input-compatmode").prop('checked', true);
@ -326,11 +321,9 @@
function updateTLSOptions() { function updateTLSOptions() {
if ($("#node-config-input-usetls").is(':checked')) { if ($("#node-config-input-usetls").is(':checked')) {
$("#node-config-input-verifyservercert").prop("disabled", false); $("#node-config-row-tls").show();
$("#node-config-input-verifyservercert").next().css("color","");
} else { } else {
$("#node-config-input-verifyservercert").prop("disabled", true); $("#node-config-row-tls").hide();
$("#node-config-input-verifyservercert").next().css("color","#aaa");
} }
} }
updateTLSOptions(); updateTLSOptions();
@ -350,6 +343,11 @@
$("#node-config-input-cleansession").on("click",function() { $("#node-config-input-cleansession").on("click",function() {
updateClientId(); updateClientId();
}); });
},
oneditsave: function() {
if (!$("#node-config-input-usetls").is(':checked')) {
$("#node-config-input-tls").val("");
}
} }
}); });
</script> </script>

View File

@ -114,8 +114,18 @@ module.exports = function(RED) {
this.options.protocolId = 'MQIsdp'; this.options.protocolId = 'MQIsdp';
this.options.protocolVersion = 3; this.options.protocolVersion = 3;
} }
if (this.usetls && n.tls) {
this.options.rejectUnauthorized = (this.verifyservercert == "true" || this.verifyservercert === true) var tlsNode = RED.nodes.getNode(n.tls);
if (tlsNode) {
tlsNode.addTLSOptions(this.options);
}
}
// If there's no rejectUnauthorized already, then this could be an
// old config where this option was provided on the broker node and
// not the tls node
if (typeof this.options.rejectUnauthorized === 'undefined') {
this.options.rejectUnauthorized = (this.verifyservercert == "true" || this.verifyservercert === true);
}
if (n.willTopic) { if (n.willTopic) {
this.options.will = { this.options.will = {
@ -284,6 +294,9 @@ module.exports = function(RED) {
done(); done();
}); });
this.client.end(); this.client.end();
} if (this.connecting) {
node.client.end();
done();
} else { } else {
done(); done();
} }

View File

@ -1,5 +1,5 @@
<!-- <!--
Copyright 2013, 2015 IBM Corp. Copyright 2013, 2016 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -29,19 +29,31 @@
<label for="node-input-url"><i class="fa fa-globe"></i> <span data-i18n="httpin.label.url"></span></label> <label for="node-input-url"><i class="fa fa-globe"></i> <span data-i18n="httpin.label.url"></span></label>
<input type="text" id="node-input-url" placeholder="http://"> <input type="text" id="node-input-url" placeholder="http://">
</div> </div>
<div class="form-row">
<input type="checkbox" id="node-input-usetls" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-usetls" style="width: auto" data-i18n="httpin.use-tls"></label>
<div id="node-row-tls" class="hide">
<label style="width: auto; margin-left: 20px; margin-right: 10px;" for="node-input-tls"><span data-i18n="httpin.tls-config"></span></label><input type="text" style="width: 300px" id="node-input-tls">
</div>
</div>
<div class="form-row"> <div class="form-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-useAuth" style="display: inline-block; width: auto; vertical-align: top;"> <input type="checkbox" id="node-input-useAuth" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-useAuth" style="width: 70%;"><span data-i18n="httpin.basicauth"></span></label> <label for="node-input-useAuth" style="width: 70%;"><span data-i18n="httpin.basicauth"></span></label>
<div style="margin-left: 20px" class="node-input-useAuth-row hide">
<div class="form-row">
<label for="node-input-user"><i class="fa fa-user"></i> <span data-i18n="common.label.username"></span></label>
<input type="text" id="node-input-user">
</div>
<div class="form-row">
<label for="node-input-password"><i class="fa fa-lock"></i> <span data-i18n="common.label.password"></span></label>
<input type="password" id="node-input-password">
</div>
</div>
</div> </div>
<div class="form-row node-input-useAuth-row">
<label for="node-input-user"><i class="fa fa-user"></i> <span data-i18n="common.label.username"></span></label>
<input type="text" id="node-input-user">
</div>
<div class="form-row node-input-useAuth-row">
<label for="node-input-password"><i class="fa fa-lock"></i> <span data-i18n="common.label.password"></span></label>
<input type="password" id="node-input-password">
</div>
<div class="form-row"> <div class="form-row">
<label for="node-input-ret"><i class="fa fa-arrow-left"></i> <span data-i18n="httpin.label.return"></span></label> <label for="node-input-ret"><i class="fa fa-arrow-left"></i> <span data-i18n="httpin.label.return"></span></label>
<select type="text" id="node-input-ret" style="width:72%;"> <select type="text" id="node-input-ret" style="width:72%;">
@ -91,8 +103,7 @@
method:{value:"GET"}, method:{value:"GET"},
ret: {value:"txt"}, ret: {value:"txt"},
url:{value:""}, url:{value:""},
//user -> credentials tls: {type:"tls-config",required: false}
//pass -> credentials
}, },
credentials: { credentials: {
user: {type:"text"}, user: {type:"text"},
@ -108,14 +119,6 @@
return this.name?"node_label_italic":""; return this.name?"node_label_italic":"";
}, },
oneditprepare: function() { oneditprepare: function() {
if (this.credentials.user || this.credentials.has_password) {
$('#node-input-useAuth').prop('checked', true);
$(".node-input-useAuth-row").show();
} else {
$('#node-input-useAuth').prop('checked', false);
$(".node-input-useAuth-row").hide();
}
$("#node-input-useAuth").change(function() { $("#node-input-useAuth").change(function() {
if ($(this).is(":checked")) { if ($(this).is(":checked")) {
$(".node-input-useAuth-row").show(); $(".node-input-useAuth-row").show();
@ -125,7 +128,29 @@
$('#node-input-password').val(''); $('#node-input-password').val('');
} }
}); });
if (this.credentials.user || this.credentials.has_password) {
$('#node-input-useAuth').prop('checked', true);
} else {
$('#node-input-useAuth').prop('checked', false);
}
$("#node-input-useAuth").change();
function updateTLSOptions() {
if ($("#node-input-usetls").is(':checked')) {
$("#node-row-tls").show();
} else {
$("#node-row-tls").hide();
}
}
if (this.tls) {
$('#node-input-usetls').prop('checked', true);
} else {
$('#node-input-usetls').prop('checked', false);
}
updateTLSOptions();
$("#node-input-usetls").on("click",function() {
updateTLSOptions();
});
$("#node-input-ret").change(function() { $("#node-input-ret").change(function() {
if ($("#node-input-ret").val() === "obj") { if ($("#node-input-ret").val() === "obj") {
$("#tip-json").show(); $("#tip-json").show();
@ -133,6 +158,11 @@
$("#tip-json").hide(); $("#tip-json").hide();
} }
}); });
},
oneditsave: function() {
if (!$("#node-input-usetls").is(':checked')) {
$("#node-input-tls").val("_ADD_");
}
} }
}); });
</script> </script>

View File

@ -1,5 +1,5 @@
/** /**
* Copyright 2013, 2015 IBM Corp. * Copyright 2013, 2016 IBM Corp.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -24,13 +24,16 @@ module.exports = function(RED) {
function HTTPRequest(n) { function HTTPRequest(n) {
RED.nodes.createNode(this,n); RED.nodes.createNode(this,n);
var node = this;
var nodeUrl = n.url; var nodeUrl = n.url;
var isTemplatedUrl = (nodeUrl||"").indexOf("{{") != -1; var isTemplatedUrl = (nodeUrl||"").indexOf("{{") != -1;
var nodeMethod = n.method || "GET"; var nodeMethod = n.method || "GET";
if (n.tls) {
var tlsNode = RED.nodes.getNode(n.tls);
}
this.ret = n.ret || "txt"; this.ret = n.ret || "txt";
if (RED.settings.httpRequestTimeout) { this.reqTimeout = parseInt(RED.settings.httpRequestTimeout) || 120000; } if (RED.settings.httpRequestTimeout) { this.reqTimeout = parseInt(RED.settings.httpRequestTimeout) || 120000; }
else { this.reqTimeout = 120000; } else { this.reqTimeout = 120000; }
var node = this;
var prox, noprox; var prox, noprox;
if (process.env.http_proxy != null) { prox = process.env.http_proxy; } if (process.env.http_proxy != null) { prox = process.env.http_proxy; }
@ -54,7 +57,11 @@ module.exports = function(RED) {
} }
// url must start http:// or https:// so assume http:// if not set // url must start http:// or https:// so assume http:// if not set
if (!((url.indexOf("http://") === 0) || (url.indexOf("https://") === 0))) { if (!((url.indexOf("http://") === 0) || (url.indexOf("https://") === 0))) {
url = "http://"+url; if (tlsNode) {
url = "https://"+url;
} else {
url = "http://"+url;
}
} }
var method = nodeMethod.toUpperCase() || "GET"; var method = nodeMethod.toUpperCase() || "GET";
@ -133,6 +140,9 @@ module.exports = function(RED) {
} }
else { node.warn("Bad proxy url: "+process.env.http_proxy); } else { node.warn("Bad proxy url: "+process.env.http_proxy); }
} }
if (tlsNode) {
tlsNode.addTLSOptions(opts);
}
var req = ((/^https/.test(urltotest))?https:http).request(opts,function(res) { var req = ((/^https/.test(urltotest))?https:http).request(opts,function(res) {
(node.ret === "bin") ? res.setEncoding('binary') : res.setEncoding('utf8'); (node.ret === "bin") ? res.setEncoding('binary') : res.setEncoding('utf8');
msg.statusCode = res.statusCode; msg.statusCode = res.statusCode;
@ -172,6 +182,7 @@ module.exports = function(RED) {
req.abort(); req.abort();
}); });
req.on('error',function(err) { req.on('error',function(err) {
node.error(err,msg);
msg.payload = err.toString() + " : " + url; msg.payload = err.toString() + " : " + url;
msg.statusCode = err.code; msg.statusCode = err.code;
node.send(msg); node.send(msg);

View File

@ -123,6 +123,23 @@
"event": "Event name" "event": "Event name"
} }
}, },
"tls": {
"tls": "TLS configuration",
"label": {
"cert": "Certificate",
"key": "Private Key",
"ca": "CA Certificate",
"verify-server-cert":"Verify server certificate"
},
"placeholder": {
"cert":"path to certificate (PEM format)",
"key":"path to private key (PEM format)",
"ca":"path to CA certificate (PEM format)"
},
"error": {
"missing-file": "No certificate/key file provided"
}
},
"exec": { "exec": {
"label": { "label": {
"command": "Command", "command": "Command",
@ -240,6 +257,7 @@
"keepalive": "Keep alive time (s)", "keepalive": "Keep alive time (s)",
"cleansession": "Use clean session", "cleansession": "Use clean session",
"use-tls": "Enable secure (SSL/TLS) connection", "use-tls": "Enable secure (SSL/TLS) connection",
"tls-config":"TLS Configuration",
"verify-server-cert":"Verify server certificate", "verify-server-cert":"Verify server certificate",
"compatmode": "Use legacy MQTT 3.1 support" "compatmode": "Use legacy MQTT 3.1 support"
}, },
@ -279,7 +297,9 @@
"return": "Return" "return": "Return"
}, },
"setby": "- set by msg.method -", "setby": "- set by msg.method -",
"basicauth": "Use basic authentication?", "basicauth": "Use basic authentication",
"use-tls": "Enable secure (SSL/TLS) connection",
"tls-config":"TLS Configuration",
"utf8": "a UTF-8 string", "utf8": "a UTF-8 string",
"binary": "a binary buffer", "binary": "a binary buffer",
"json": "a parsed JSON object", "json": "a parsed JSON object",

View File

@ -3,9 +3,14 @@
"label": { "label": {
"name": "Name", "name": "Name",
"ok": "Ok", "ok": "Ok",
"done":"Done",
"cancel": "Cancel", "cancel": "Cancel",
"delete": "Delete", "delete": "Delete",
"close": "Close" "close": "Close",
"load": "Load",
"save": "Save",
"import": "Import",
"export": "Export"
} }
}, },
"workspace": { "workspace": {
@ -105,10 +110,10 @@
} }
}, },
"subflow": { "subflow": {
"editSubflow": "Edit flow __name__", "editSubflow": "Edit flow template: __name__",
"edit": "Edit flow", "edit": "Edit flow template",
"subflowInstances": "There is __count__ instance of this subflow", "subflowInstances": "There is __count__ instance of this subflow template",
"subflowInstances_plural": "There are __count__ instances of this subflow", "subflowInstances_plural": "There are __count__ instances of this subflow template",
"editSubflowProperties": "edit properties", "editSubflowProperties": "edit properties",
"input": "inputs:", "input": "inputs:",
"output": "outputs:", "output": "outputs:",
@ -121,12 +126,14 @@
} }
}, },
"editor": { "editor": {
"configEdit": "edit", "configEdit": "Edit",
"configAdd": "add", "configAdd": "Add",
"configUpdate": "Update",
"configDelete": "Delete", "configDelete": "Delete",
"nodesUse": "__count__ node uses this config", "nodesUse": "__count__ node uses this config",
"nodesUse_plural": "__count__ nodes use this config", "nodesUse_plural": "__count__ nodes use this config",
"addNewConfig": "Add new __type__ config node", "addNewConfig": "Add new __type__ config node",
"editNode": "Edit __type__ node",
"editConfig": "Edit __type__ config node", "editConfig": "Edit __type__ config node",
"addNewType": "Add new __type__...", "addNewType": "Add new __type__...",
"errors": { "errors": {
@ -193,7 +200,7 @@
}, },
"sidebar": { "sidebar": {
"info": { "info": {
"name": "Information", "name": "Node information",
"label": "info", "label": "info",
"node": "Node", "node": "Node",
"type": "Type", "type": "Type",

View File

@ -36,10 +36,32 @@ function Flow(global,flow) {
var id; var id;
catchNodeMap = {}; catchNodeMap = {};
statusNodeMap = {}; statusNodeMap = {};
for (id in flow.configs) {
if (flow.configs.hasOwnProperty(id)) { var configNodes = Object.keys(flow.configs);
node = flow.configs[id]; var configNodeAttempts = {};
if (!activeNodes[id]) { while(configNodes.length > 0) {
id = configNodes.shift();
node = flow.configs[id];
if (!activeNodes[id]) {
var readyToCreate = true;
// This node doesn't exist.
// Check it doesn't reference another non-existent config node
for (var prop in node) {
if (node.hasOwnProperty(prop) && prop !== 'id' && prop !== 'wires' && prop !== '_users' && flow.configs[node[prop]]) {
if (!activeNodes[node[prop]]) {
// References a non-existent config node
// Add it to the back of the list to try again later
configNodes.push(id);
configNodeAttempts[id] = (configNodeAttempts[id]||0)+1;
if (configNodeAttempts[id] === 100) {
throw new Error("Circular config node dependency detected: "+id);
}
readyToCreate = false;
break;
}
}
}
if (readyToCreate) {
newNode = createNode(node.type,node); newNode = createNode(node.type,node);
if (newNode) { if (newNode) {
activeNodes[id] = newNode; activeNodes[id] = newNode;
@ -47,6 +69,7 @@ function Flow(global,flow) {
} }
} }
} }
if (diff && diff.rewired) { if (diff && diff.rewired) {
for (var j=0;j<diff.rewired.length;j++) { for (var j=0;j<diff.rewired.length;j++) {
var rewireNode = activeNodes[diff.rewired[j]]; var rewireNode = activeNodes[diff.rewired[j]];

View File

@ -230,22 +230,32 @@ module.exports = {
} }
} }
for (id in newConfig.allNodes) { var madeChange;
if (newConfig.allNodes.hasOwnProperty(id)) { // Loop through the nodes looking for references to changed config nodes
node = newConfig.allNodes[id]; // Repeat the loop if anything is marked as changed as it may need to be
for (var prop in node) { // propagated to parent nodes.
if (node.hasOwnProperty(prop) && prop != "z" && prop != "id" && prop != "wires") { // TODO: looping through all nodes every time is a bit inefficient - could be more targeted
// This node has a property that references a changed/removed node do {
// Assume it is a config node change and mark this node as madeChange = false;
// changed. for (id in newConfig.allNodes) {
if (changed[node[prop]] || removed[node[prop]]) { if (newConfig.allNodes.hasOwnProperty(id)) {
if (!changed[node.id]) { node = newConfig.allNodes[id];
changed[node.id] = node; for (var prop in node) {
if (newConfig.allNodes[node.z]) { if (node.hasOwnProperty(prop) && prop != "z" && prop != "id" && prop != "wires") {
changed[node.z] = newConfig.allNodes[node.z]; // This node has a property that references a changed/removed node
if (changed[node.z].type === "subflow") { // Assume it is a config node change and mark this node as
changedSubflows[node.z] = changed[node.z]; // changed.
delete changed[node.id]; if (changed[node[prop]] || removed[node[prop]]) {
if (!changed[node.id]) {
madeChange = true;
changed[node.id] = node;
// This node exists within subflow template
// Mark the template as having changed
if (newConfig.allNodes[node.z]) {
changed[node.z] = newConfig.allNodes[node.z];
if (changed[node.z].type === "subflow") {
changedSubflows[node.z] = changed[node.z];
}
} }
} }
} }
@ -253,8 +263,18 @@ module.exports = {
} }
} }
} }
} } while(madeChange===true)
// Find any nodes that exist on a subflow template and remove from changed
// list as the parent subflow will now be marked as containing a change
for (id in newConfig.allNodes) {
if (newConfig.allNodes.hasOwnProperty(id)) {
node = newConfig.allNodes[id];
if (newConfig.allNodes[node.z] && newConfig.allNodes[node.z].type === "subflow") {
delete changed[node.id];
}
}
}
// Recursively mark all instances of changed subflows as changed // Recursively mark all instances of changed subflows as changed
var changedSubflowStack = Object.keys(changedSubflows); var changedSubflowStack = Object.keys(changedSubflows);

View File

@ -45,7 +45,7 @@ describe('Flow', function() {
var TestNode = function(n) { var TestNode = function(n) {
Node.call(this,n); Node.call(this,n);
createCount++; this._index = createCount++;
this.scope = n.scope; this.scope = n.scope;
var node = this; var node = this;
this.foo = n.foo; this.foo = n.foo;
@ -173,6 +173,61 @@ describe('Flow', function() {
}); });
it("instantiates config nodes in the right order",function(done) {
var config = flowUtils.parseConfig([
{id:"t1",type:"tab"},
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]},
{id:"2",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["3"]},
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
{id:"4",z:"t1",type:"test",foo:"5"}, // This node depends on #5
{id:"5",z:"t1",type:"test"}
]);
var flow = Flow.create(config,config.flows["t1"]);
flow.start();
Object.keys(flow.getActiveNodes()).should.have.length(5);
currentNodes.should.have.a.property("1");
currentNodes.should.have.a.property("2");
currentNodes.should.have.a.property("3");
currentNodes.should.have.a.property("4");
currentNodes.should.have.a.property("5");
currentNodes["1"].should.have.a.property("_index",2);
currentNodes["2"].should.have.a.property("_index",3);
currentNodes["3"].should.have.a.property("_index",4);
currentNodes["4"].should.have.a.property("_index",1);
currentNodes["5"].should.have.a.property("_index",0);
flow.stop().then(function() {
currentNodes.should.not.have.a.property("1");
currentNodes.should.not.have.a.property("2");
currentNodes.should.not.have.a.property("3");
currentNodes.should.not.have.a.property("4");
currentNodes.should.not.have.a.property("5");
stoppedNodes.should.have.a.property("1");
stoppedNodes.should.have.a.property("2");
stoppedNodes.should.have.a.property("3");
stoppedNodes.should.have.a.property("4");
stoppedNodes.should.have.a.property("5");
done();
});
});
it("detects dependency loops in config nodes",function() {
var config = flowUtils.parseConfig([
{id:"t1",type:"tab"},
{id:"node1",z:"t1",type:"test",foo:"node2"}, // This node depends on #5
{id:"node2",z:"t1",type:"test",foo:"node1"}
]);
var flow = Flow.create(config,config.flows["t1"]);
/*jshint immed: false */
(function(){
flow.start();
}).should.throw("Circular config node dependency detected: node1");
});
it("instantiates a subflow and stops it",function(done) { it("instantiates a subflow and stops it",function(done) {
var config = flowUtils.parseConfig([ var config = flowUtils.parseConfig([
{id:"t1",type:"tab"}, {id:"t1",type:"tab"},

View File

@ -308,7 +308,7 @@ describe('flows/util', function() {
diffResult.linked.sort().should.eql(["3"]); diffResult.linked.sort().should.eql(["3"]);
}); });
it('identifies config nodes changes', function() { it('identifies config nodes changes, node->config', function() {
var config = [ var config = [
{id:"1",type:"test",foo:"configNode",wires:[["2"]]}, {id:"1",type:"test",foo:"configNode",wires:[["2"]]},
{id:"2",type:"test",bar:"b",wires:[["3"]]}, {id:"2",type:"test",bar:"b",wires:[["3"]]},
@ -329,7 +329,30 @@ describe('flows/util', function() {
diffResult.removed.should.have.length(0); diffResult.removed.should.have.length(0);
diffResult.rewired.should.have.length(0); diffResult.rewired.should.have.length(0);
diffResult.linked.sort().should.eql(["2","3"]); diffResult.linked.sort().should.eql(["2","3"]);
});
it('identifies config nodes changes, node->config->config', function() {
var config = [
{id:"1",type:"test",foo:"configNode1",wires:[["2"]]},
{id:"2",type:"test",bar:"b",wires:[["3"]]},
{id:"3",type:"test",foo:"a",wires:[]},
{id:"configNode1",foo:"configNode2",type:"testConfig"},
{id:"configNode2",type:"testConfig"}
];
var newConfig = clone(config);
newConfig[4].foo = "bar";
var originalConfig = flowUtil.parseConfig(config);
var changedConfig = flowUtil.parseConfig(newConfig);
originalConfig.missingTypes.should.have.length(0);
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
diffResult.added.should.have.length(0);
diffResult.changed.sort().should.eql(["1","configNode1","configNode2"]);
diffResult.removed.should.have.length(0);
diffResult.rewired.should.have.length(0);
diffResult.linked.sort().should.eql(["2","3"]);
}); });
it('marks a parent subflow as changed for an internal property change', function() { it('marks a parent subflow as changed for an internal property change', function() {