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:
commit
41445a1b48
@ -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",
|
||||
|
@ -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);
|
||||
|
@ -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]]);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -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
@ -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) {
|
||||
|
@ -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");
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
@ -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
225
editor/js/ui/tray.js
Normal 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();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
@ -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);
|
||||
|
@ -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);
|
||||
},
|
||||
|
@ -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);
|
||||
|
@ -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,7 +14,144 @@
|
||||
* 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;
|
||||
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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -50,6 +50,8 @@
|
||||
margin-right: 35px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#workspace-add-tab {
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
|
@ -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
73
nodes/core/io/05-tls.html
Normal 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
69
nodes/core/io/05-tls.js
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -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>
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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> </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>
|
||||
|
@ -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);
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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]];
|
||||
|
@ -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);
|
||||
|
@ -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"},
|
||||
|
@ -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() {
|
||||
|
Loading…
Reference in New Issue
Block a user