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-config.js",
"editor/js/ui/editor.js",
"editor/js/ui/tray.js",
"editor/js/ui/clipboard.js",
"editor/js/ui/library.js",
"editor/js/ui/notifications.js",

View File

@ -273,6 +273,10 @@ RED.history = (function() {
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) {
var subflow = RED.nodes.subflow(id);

View File

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

View File

@ -23,7 +23,7 @@ RED.clipboard = (function() {
var importNodesDialog;
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")
.dialog({
modal: true,
@ -31,14 +31,6 @@ RED.clipboard = (function() {
width: 500,
resizable: false,
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",
text: RED._("common.label.cancel"),
@ -48,10 +40,20 @@ RED.clipboard = (function() {
},
{
id: "clipboard-dialog-close",
class: "primary",
text: RED._("common.label.close"),
click: function() {
$( 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) {

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -17,7 +17,7 @@ RED.sidebar.config = (function() {
var content = document.createElement("div");
content.className = "sidebar-node-config"
content.className = "sidebar-node-config";
$('<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>'+
@ -35,6 +35,9 @@ RED.sidebar.config = (function() {
var flowCategories = $("<div>").appendTo(content);
var subflowCategories = $("<div>").appendTo(content);
var shade = $('<div class="sidebar-node-config-shade hide"></div>').appendTo(content);
var showUnusedOnly = false;
var categories = {};
@ -288,6 +291,12 @@ RED.sidebar.config = (function() {
return {
init:init,
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");
* you may not use this file except in compliance with the License.
@ -24,7 +24,7 @@ RED.tabs = (function() {
var currentTabWidth;
var currentActiveTabWidth = 0;
var ul = $("#"+options.id)
var ul = $("#"+options.id);
ul.addClass("red-ui-tabs");
ul.children().first().addClass("active");
ul.children().addClass("red-ui-tab");
@ -118,6 +118,7 @@ RED.tabs = (function() {
addTab: function(tab) {
tabs[tab.id] = tab;
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);
if (tab.icon) {
$('<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) {
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,
activateTab: activateTab,
@ -158,6 +238,30 @@ RED.tabs = (function() {
tab.attr("title",label);
tab.find("span").text(label);
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._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");
* 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.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
});
@ -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() {
var workspace = $(this).dialog('option','workspace');
var label = $( "#node-input-workspace-name" ).val();
@ -146,13 +158,8 @@ RED.workspaces = (function() {
}
$( this ).dialog( "close" );
}
},
{
text: RED._("common.label.cancel"),
click: function() {
$( this ).dialog( "close" );
}
}
],
open: function(e) {
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 {
init: init,
add: addWorkspace,
remove: removeWorkspace,
order: setWorkspaceOrder,
edit: function(id) {
showRenameWorkspaceDialog(id||activeWorkspace);
},

View File

@ -14,6 +14,8 @@
* limitations under the License.
**/
$background-color: #f3f3f3;
$form-placeholder-color: #bbbbbb;
$form-text-color: #444;
$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-selected: #AAA;
$workspace-button-color-focus-outline: rgba(85,150,230,0.2);
$typedInput-button-background: #efefef;
$typedInput-button-background-hover: #ddd;
$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");
* you may not use this file except in compliance with the License.
@ -14,6 +14,143 @@
* limitations under the License.
**/
#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;
height: 100%;
@ -23,10 +160,6 @@
border-color: rgb(214, 97, 95) !important;
}
.ui-dialog .ui-dialog-buttonpane button.leftButton {
margin-right: 40px;
}
.form-row {
clear: both;
color: $form-text-color;
@ -83,6 +216,7 @@
cursor: auto;
float: right;
font-size: 12px !important;
line-height: 35px;
}
#node-config-dialog-scope-warning {
display: inline-block;
@ -94,13 +228,14 @@
margin: 1px 0 0 0;
padding: 0;
height: 22px;
width: 110px;
width: 150px;
}
#node-config-dialog-user-count {
vertical-align: middle;
display:inline-block;
margin-top: 10px;
margin-right: 20px;
float:left;
font-size: 12px;
line-height: 35px;
}

View File

@ -73,6 +73,32 @@
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset {
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 {
padding: .3em 1em .5em 1em;
}

View File

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

View File

@ -22,6 +22,7 @@
width: 315px;
background: #fff;
box-sizing: border-box;
z-index: 10;
@include component-border;
}
@ -46,13 +47,15 @@
right: 315px;
bottom:10px;
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;
}
.sidebar-closed > #sidebar { display: none; }
.sidebar-closed > #sidebar-separator { right: 0px !important; }
.sidebar-closed > #workspace { right: 7px !important; }
.sidebar-closed > #editor-stack { right: 8px !important; }
#sidebar .button {
@include workspace-button;

View File

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

View File

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

View File

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

View File

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

View File

@ -39,6 +39,7 @@
<ul class="header-toolbar hide">
<li><a id="btn-sidemenu" class="button" data-toggle="dropdown" href="#"><i class="fa fa-bars"></i></a></li>
</ul>
<div id="header-shade" class="hide"></div>
</div>
<div id="main-container" class="sidebar-closed hide">
<div id="palette">
@ -65,6 +66,8 @@
<a class="workspace-footer-button" id="btn-zoom-in" href="#"><i class="fa fa-plus"></i></a>
</div>
</div>
<div id="editor-shade" class="hide"></div>
<div id="editor-stack"></div>
<div id="sidebar">
<ul id="sidebar-tabs"></ul>
<div id="sidebar-content"></div>
@ -78,24 +81,6 @@
<div id="notifications"></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">
<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">
@ -166,6 +151,20 @@
</div>
</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/ace/ace.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>
<input type="text" id="node-config-input-port" data-i18n="[placeholder]mqtt.label.port" style="width:45px">
</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">
<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">
</div>
<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>
<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>
<input type="password" id="node-config-input-password">
</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 id="mqtt-broker-tab-birth" style="display:none">
<div class="form-row">
@ -240,6 +238,7 @@
defaults: {
broker: {value:"",required:true},
port: {value:1883,required:true,validate:RED.validators.number()},
tls: {type:"tls-config",required: false},
clientid: { value:"", validate: function(v) {
if ($("#node-config-input-clientid").length) {
// Currently editing the node
@ -303,10 +302,6 @@
this.usetls = 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'){
this.compatmode = true;
$("#node-config-input-compatmode").prop('checked', true);
@ -326,11 +321,9 @@
function updateTLSOptions() {
if ($("#node-config-input-usetls").is(':checked')) {
$("#node-config-input-verifyservercert").prop("disabled", false);
$("#node-config-input-verifyservercert").next().css("color","");
$("#node-config-row-tls").show();
} else {
$("#node-config-input-verifyservercert").prop("disabled", true);
$("#node-config-input-verifyservercert").next().css("color","#aaa");
$("#node-config-row-tls").hide();
}
}
updateTLSOptions();
@ -350,6 +343,11 @@
$("#node-config-input-cleansession").on("click",function() {
updateClientId();
});
},
oneditsave: function() {
if (!$("#node-config-input-usetls").is(':checked')) {
$("#node-config-input-tls").val("");
}
}
});
</script>

View File

@ -114,8 +114,18 @@ module.exports = function(RED) {
this.options.protocolId = 'MQIsdp';
this.options.protocolVersion = 3;
}
this.options.rejectUnauthorized = (this.verifyservercert == "true" || this.verifyservercert === true)
if (this.usetls && n.tls) {
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) {
this.options.will = {
@ -284,6 +294,9 @@ module.exports = function(RED) {
done();
});
this.client.end();
} if (this.connecting) {
node.client.end();
done();
} else {
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");
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>
<input type="text" id="node-input-url" placeholder="http://">
</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">
<label>&nbsp;</label>
<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>
</div>
<div class="form-row node-input-useAuth-row">
<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 node-input-useAuth-row">
<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 class="form-row">
<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%;">
@ -91,8 +103,7 @@
method:{value:"GET"},
ret: {value:"txt"},
url:{value:""},
//user -> credentials
//pass -> credentials
tls: {type:"tls-config",required: false}
},
credentials: {
user: {type:"text"},
@ -108,14 +119,6 @@
return this.name?"node_label_italic":"";
},
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() {
if ($(this).is(":checked")) {
$(".node-input-useAuth-row").show();
@ -125,7 +128,29 @@
$('#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() {
if ($("#node-input-ret").val() === "obj") {
$("#tip-json").show();
@ -133,6 +158,11 @@
$("#tip-json").hide();
}
});
},
oneditsave: function() {
if (!$("#node-input-usetls").is(':checked')) {
$("#node-input-tls").val("_ADD_");
}
}
});
</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");
* you may not use this file except in compliance with the License.
@ -24,13 +24,16 @@ module.exports = function(RED) {
function HTTPRequest(n) {
RED.nodes.createNode(this,n);
var node = this;
var nodeUrl = n.url;
var isTemplatedUrl = (nodeUrl||"").indexOf("{{") != -1;
var nodeMethod = n.method || "GET";
if (n.tls) {
var tlsNode = RED.nodes.getNode(n.tls);
}
this.ret = n.ret || "txt";
if (RED.settings.httpRequestTimeout) { this.reqTimeout = parseInt(RED.settings.httpRequestTimeout) || 120000; }
else { this.reqTimeout = 120000; }
var node = this;
var prox, noprox;
if (process.env.http_proxy != null) { prox = process.env.http_proxy; }
@ -54,8 +57,12 @@ module.exports = function(RED) {
}
// url must start http:// or https:// so assume http:// if not set
if (!((url.indexOf("http://") === 0) || (url.indexOf("https://") === 0))) {
if (tlsNode) {
url = "https://"+url;
} else {
url = "http://"+url;
}
}
var method = nodeMethod.toUpperCase() || "GET";
if (msg.method && n.method && (n.method !== "use")) { // warn if override option not set
@ -133,6 +140,9 @@ module.exports = function(RED) {
}
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) {
(node.ret === "bin") ? res.setEncoding('binary') : res.setEncoding('utf8');
msg.statusCode = res.statusCode;
@ -172,6 +182,7 @@ module.exports = function(RED) {
req.abort();
});
req.on('error',function(err) {
node.error(err,msg);
msg.payload = err.toString() + " : " + url;
msg.statusCode = err.code;
node.send(msg);

View File

@ -123,6 +123,23 @@
"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": {
"label": {
"command": "Command",
@ -240,6 +257,7 @@
"keepalive": "Keep alive time (s)",
"cleansession": "Use clean session",
"use-tls": "Enable secure (SSL/TLS) connection",
"tls-config":"TLS Configuration",
"verify-server-cert":"Verify server certificate",
"compatmode": "Use legacy MQTT 3.1 support"
},
@ -279,7 +297,9 @@
"return": "Return"
},
"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",
"binary": "a binary buffer",
"json": "a parsed JSON object",

View File

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

View File

@ -36,10 +36,32 @@ function Flow(global,flow) {
var id;
catchNodeMap = {};
statusNodeMap = {};
for (id in flow.configs) {
if (flow.configs.hasOwnProperty(id)) {
var configNodes = Object.keys(flow.configs);
var configNodeAttempts = {};
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);
if (newNode) {
activeNodes[id] = newNode;
@ -47,6 +69,7 @@ function Flow(global,flow) {
}
}
}
if (diff && diff.rewired) {
for (var j=0;j<diff.rewired.length;j++) {
var rewireNode = activeNodes[diff.rewired[j]];

View File

@ -230,6 +230,13 @@ module.exports = {
}
}
var madeChange;
// Loop through the nodes looking for references to changed config nodes
// Repeat the loop if anything is marked as changed as it may need to be
// propagated to parent nodes.
// TODO: looping through all nodes every time is a bit inefficient - could be more targeted
do {
madeChange = false;
for (id in newConfig.allNodes) {
if (newConfig.allNodes.hasOwnProperty(id)) {
node = newConfig.allNodes[id];
@ -240,21 +247,34 @@ module.exports = {
// changed.
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];
}
}
}
}
}
}
}
}
} 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
var changedSubflowStack = Object.keys(changedSubflows);

View File

@ -45,7 +45,7 @@ describe('Flow', function() {
var TestNode = function(n) {
Node.call(this,n);
createCount++;
this._index = createCount++;
this.scope = n.scope;
var node = this;
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) {
var config = flowUtils.parseConfig([
{id:"t1",type:"tab"},

View File

@ -308,7 +308,7 @@ describe('flows/util', function() {
diffResult.linked.sort().should.eql(["3"]);
});
it('identifies config nodes changes', function() {
it('identifies config nodes changes, node->config', function() {
var config = [
{id:"1",type:"test",foo:"configNode",wires:[["2"]]},
{id:"2",type:"test",bar:"b",wires:[["3"]]},
@ -329,7 +329,30 @@ describe('flows/util', function() {
diffResult.removed.should.have.length(0);
diffResult.rewired.should.have.length(0);
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() {