mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Merge pull request #533 from knolleary/deploy
Add smarter deployment options
This commit is contained in:
commit
8d5f99640e
BIN
public/images/deploy-flows-o.png
Normal file
BIN
public/images/deploy-flows-o.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 291 B |
BIN
public/images/deploy-flows.png
Normal file
BIN
public/images/deploy-flows.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 386 B |
BIN
public/images/deploy-full-o.png
Normal file
BIN
public/images/deploy-full-o.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 289 B |
BIN
public/images/deploy-full.png
Normal file
BIN
public/images/deploy-full.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 368 B |
BIN
public/images/deploy-nodes-o.png
Normal file
BIN
public/images/deploy-nodes-o.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 290 B |
BIN
public/images/deploy-nodes.png
Normal file
BIN
public/images/deploy-nodes.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 392 B |
@ -31,10 +31,13 @@
|
|||||||
<body spellcheck="false">
|
<body spellcheck="false">
|
||||||
<div id="header">
|
<div id="header">
|
||||||
<span class="logo"><img src="node-red.png"> <span>Node-RED</span></span>
|
<span class="logo"><img src="node-red.png"> <span>Node-RED</span></span>
|
||||||
<div class="pull-right">
|
<ul class="header-toolbar">
|
||||||
<a id="btn-deploy" class="button action-deploy disabled" href="#"><i id="btn-icn-deploy" class="fa fa-download"></i> Deploy</a>
|
<li><span class="deploy-button-group button-group">
|
||||||
<a id="btn-sidemenu" class="button dropdown-toggle" data-toggle="dropdown" href="#"><i class="fa fa-bars"></i></a>
|
<a id="btn-deploy" class="action-deploy disabled" href="#"><img id="btn-icn-deploy" src="images/deploy-full-o.png"> <span>Deploy</span></a>
|
||||||
</div>
|
<a id="btn-deploy-options" data-toggle="dropdown" class="" href="#"><i class="fa fa-caret-down"></i></a>
|
||||||
|
</span></li>
|
||||||
|
<li><a id="btn-sidemenu" class="button" data-toggle="dropdown" href="#"><i class="fa fa-bars"></i></a></li>
|
||||||
|
<ul>
|
||||||
</div>
|
</div>
|
||||||
<div id="main-container" class="sidebar-closed">
|
<div id="main-container" class="sidebar-closed">
|
||||||
<div id="palette">
|
<div id="palette">
|
||||||
|
@ -15,6 +15,14 @@
|
|||||||
**/
|
**/
|
||||||
var RED = (function() {
|
var RED = (function() {
|
||||||
|
|
||||||
|
var deploymentTypes = {
|
||||||
|
"full":{label:"Deploy",img:"images/deploy-full-o.png"},
|
||||||
|
"nodes":{label:"Deploy modified nodes",img:"images/deploy-nodes-o.png"},
|
||||||
|
"flows":{label:"Deploy modified flows",img:"images/deploy-flows-o.png"}
|
||||||
|
}
|
||||||
|
var deploymentType = "full";
|
||||||
|
|
||||||
|
|
||||||
function hideDropTarget() {
|
function hideDropTarget() {
|
||||||
$("#dropTarget").hide();
|
$("#dropTarget").hide();
|
||||||
RED.keyboard.remove(/* ESCAPE */ 27);
|
RED.keyboard.remove(/* ESCAPE */ 27);
|
||||||
@ -82,7 +90,10 @@ var RED = (function() {
|
|||||||
url:"flows",
|
url:"flows",
|
||||||
type: "POST",
|
type: "POST",
|
||||||
data: JSON.stringify(nns),
|
data: JSON.stringify(nns),
|
||||||
contentType: "application/json; charset=utf-8"
|
contentType: "application/json; charset=utf-8",
|
||||||
|
headers: {
|
||||||
|
"Node-RED-Deployment-Type":deploymentType
|
||||||
|
}
|
||||||
}).done(function(data,textStatus,xhr) {
|
}).done(function(data,textStatus,xhr) {
|
||||||
RED.notify("Successfully deployed","success");
|
RED.notify("Successfully deployed","success");
|
||||||
RED.nodes.eachNode(function(node) {
|
RED.nodes.eachNode(function(node) {
|
||||||
@ -280,36 +291,50 @@ var RED = (function() {
|
|||||||
dialog.modal();
|
dialog.modal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function changeDeploymentType(type) {
|
||||||
|
deploymentType = type;
|
||||||
|
$("#btn-deploy img").attr("src",deploymentTypes[type].img);
|
||||||
|
//$("#btn-deploy span").text(deploymentTypes[type].label);
|
||||||
|
}
|
||||||
|
|
||||||
$(function() {
|
$(function() {
|
||||||
RED.menu.init({id:"btn-sidemenu",
|
RED.menu.init({id:"btn-sidemenu",
|
||||||
options: [
|
options: [
|
||||||
{id:"btn-sidebar",icon:"fa fa-columns",label:"Sidebar",toggle:true,onselect:RED.sidebar.toggleSidebar, selected: true},
|
{id:"btn-sidebar",_icon:"fa fa-columns",label:"Sidebar",toggle:true,onselect:RED.sidebar.toggleSidebar, selected: true},
|
||||||
|
{id:"btn-node-status",_icon:"fa fa-info",label:"Node Status",toggle:true,onselect:toggleStatus},
|
||||||
null,
|
null,
|
||||||
{id:"btn-node-status",icon:"fa fa-info",label:"Node Status",toggle:true,onselect:toggleStatus},
|
{id:"btn-import-menu",_icon:"fa fa-sign-in",label:"Import...",options:[
|
||||||
null,
|
{id:"btn-import-clipboard",_icon:"fa fa-clipboard",label:"Clipboard...",onselect:RED.view.showImportNodesDialog},
|
||||||
{id:"btn-import-menu",icon:"fa fa-sign-in",label:"Import...",options:[
|
{id:"btn-import-library",_icon:"fa fa-book",label:"Library",options:[]}
|
||||||
{id:"btn-import-clipboard",icon:"fa fa-clipboard",label:"Clipboard...",onselect:RED.view.showImportNodesDialog},
|
|
||||||
{id:"btn-import-library",icon:"fa fa-book",label:"Library",options:[]}
|
|
||||||
]},
|
]},
|
||||||
{id:"btn-export-menu",icon:"fa fa-sign-out",label:"Export...",disabled:true,options:[
|
{id:"btn-export-menu",_icon:"fa fa-sign-out",label:"Export...",disabled:true,options:[
|
||||||
{id:"btn-export-clipboard",icon:"fa fa-clipboard",label:"Clipboard...",disabled:true,onselect:RED.view.showExportNodesDialog},
|
{id:"btn-export-clipboard",_icon:"fa fa-clipboard",label:"Clipboard...",disabled:true,onselect:RED.view.showExportNodesDialog},
|
||||||
{id:"btn-export-library",icon:"fa fa-book",label:"Library...",disabled:true,onselect:RED.view.showExportNodesLibraryDialog}
|
{id:"btn-export-library",_icon:"fa fa-book",label:"Library...",disabled:true,onselect:RED.view.showExportNodesLibraryDialog}
|
||||||
]},
|
]},
|
||||||
null,
|
null,
|
||||||
{id:"btn-config-nodes",icon:"fa fa-th-list",label:"Configuration nodes...",onselect:RED.sidebar.config.show},
|
{id:"btn-config-nodes",_icon:"fa fa-th-list",label:"Configuration nodes...",onselect:RED.sidebar.config.show},
|
||||||
null,
|
null,
|
||||||
{id:"btn-create-subflow",icon:"fa fa-share-alt",label:"Create subflow",onselect:RED.view.createSubflow},
|
{id:"btn-create-subflow",_icon:"fa fa-share-alt",label:"Create subflow",onselect:RED.view.createSubflow},
|
||||||
{id:"btn-convert-subflow",icon:"fa fa-share-alt",label:"Convert to subflow",disabled:true,onselect:RED.view.convertToSubflow},
|
{id:"btn-convert-subflow",_icon:"fa fa-share-alt",label:"Convert to subflow",disabled:true,onselect:RED.view.convertToSubflow},
|
||||||
null,
|
null,
|
||||||
{id:"btn-workspace-menu",icon:"fa fa-th-large",label:"Workspaces",options:[
|
{id:"btn-workspace-menu",_icon:"fa fa-th-large",label:"Workspaces",options:[
|
||||||
{id:"btn-workspace-add",icon:"fa fa-plus",label:"Add"},
|
{id:"btn-workspace-add",_icon:"fa fa-plus",label:"Add"},
|
||||||
{id:"btn-workspace-edit",icon:"fa fa-pencil",label:"Rename"},
|
{id:"btn-workspace-edit",_icon:"fa fa-pencil",label:"Rename"},
|
||||||
{id:"btn-workspace-delete",icon:"fa fa-minus",label:"Delete"},
|
{id:"btn-workspace-delete",_icon:"fa fa-minus",label:"Delete"},
|
||||||
null
|
null
|
||||||
]},
|
]},
|
||||||
null,
|
null,
|
||||||
{id:"btn-keyboard-shortcuts",icon:"fa fa-keyboard-o",label:"Keyboard Shortcuts",onselect:showHelp},
|
{id:"btn-keyboard-shortcuts",_icon:"fa fa-keyboard-o",label:"Keyboard Shortcuts",onselect:showHelp},
|
||||||
{id:"btn-help",icon:"fa fa-question",label:"Help...", href:"http://nodered.org/docs"}
|
{id:"btn-help",_icon:"fa fa-question",label:"Help...", href:"http://nodered.org/docs"}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
RED.menu.init({id:"btn-deploy-options",
|
||||||
|
options: [
|
||||||
|
{id:"btn-deploy-full",toggle:"deploy-type",icon:"images/deploy-full.png",label:"Full",sublabel:"Deploys everything in the workspace",onselect:function(s) { if(s){changeDeploymentType("full")}}},
|
||||||
|
{id:"btn-deploy-flow",toggle:"deploy-type",icon:"images/deploy-flows.png",label:"Modified Flows",sublabel:"Only deploys flows that contain changed nodes", onselect:function(s) {if(s){changeDeploymentType("flows")}}},
|
||||||
|
{id:"btn-deploy-node",toggle:"deploy-type",icon:"images/deploy-nodes.png",label:"Modified Nodes",sublabel:"Only deploys nodes that have changed",onselect:function(s) { if(s){changeDeploymentType("nodes")}}}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -343,14 +343,23 @@ RED.nodes = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(exportCreds && n.credentials) {
|
if(exportCreds && n.credentials) {
|
||||||
|
var credentialSet = {};
|
||||||
node.credentials = {};
|
node.credentials = {};
|
||||||
for (var cred in n._def.credentials) {
|
for (var cred in n._def.credentials) {
|
||||||
if (n._def.credentials.hasOwnProperty(cred)) {
|
if (n._def.credentials.hasOwnProperty(cred)) {
|
||||||
if (n.credentials[cred] != null) {
|
if (n._def.credentials[cred].type == 'password') {
|
||||||
node.credentials[cred] = n.credentials[cred];
|
if (n.credentials["has_"+cred] != n.credentials._["has_"+cred] ||
|
||||||
|
(n.credentials["has_"+cred] && n.credentials[cred])) {
|
||||||
|
credentialSet[cred] = n.credentials[cred];
|
||||||
|
}
|
||||||
|
} else if (n.credentials[cred] != null && n.credentials[cred] != n.credentials._[cred]) {
|
||||||
|
credentialSet[cred] = n.credentials[cred];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (Object.keys(credentialSet).length > 0) {
|
||||||
|
node.credentials = credentialSet;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (n._def.category != "config") {
|
if (n._def.category != "config") {
|
||||||
@ -385,7 +394,7 @@ RED.nodes = (function() {
|
|||||||
var wires = links.filter(function(d) { return d.source === p });
|
var wires = links.filter(function(d) { return d.source === p });
|
||||||
for (var i=0;i<wires.length;i++) {
|
for (var i=0;i<wires.length;i++) {
|
||||||
var w = wires[i];
|
var w = wires[i];
|
||||||
if (w.target.id != p.id) {
|
if (w.target.type != "subflow") {
|
||||||
nIn.wires.push({id:w.target.id})
|
nIn.wires.push({id:w.target.id})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,11 +45,31 @@ RED.menu = (function() {
|
|||||||
item = $('<li class="divider"></li>');
|
item = $('<li class="divider"></li>');
|
||||||
} else {
|
} else {
|
||||||
item = $('<li></li>');
|
item = $('<li></li>');
|
||||||
var link = $('<a '+(opt.id?'id="'+opt.id+'" ':'')+'tabindex="-1" href="#">'+
|
|
||||||
(opt.toggle?'<i class="fa fa-check pull-right"></i>':'')+
|
var linkContent = '<a '+(opt.id?'id="'+opt.id+'" ':'')+'tabindex="-1" href="#">';
|
||||||
(opt.icon?'<i class="'+opt.icon+'"></i> ':'')+
|
if (opt.toggle) {
|
||||||
opt.label+
|
linkContent += '<i class="fa fa-square pull-left"></i>';
|
||||||
'</a>').appendTo(item);
|
linkContent += '<i class="fa fa-check-square pull-left"></i>';
|
||||||
|
|
||||||
|
}
|
||||||
|
if (opt.icon !== undefined) {
|
||||||
|
if (/\.png/.test(opt.icon)) {
|
||||||
|
linkContent += '<img src="'+opt.icon+'"/> ';
|
||||||
|
} else {
|
||||||
|
linkContent += '<i class="'+(opt.icon?opt.icon:'" style="display: inline-block;"')+'"></i> ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt.sublabel) {
|
||||||
|
linkContent += '<span class="menu-label-container"><span class="menu-label">'+opt.label+'</span>'+
|
||||||
|
'<span class="menu-sublabel">'+opt.sublabel+'</span></span>'
|
||||||
|
} else {
|
||||||
|
linkContent += '<span class="menu-label">'+opt.label+'</span>'
|
||||||
|
}
|
||||||
|
|
||||||
|
linkContent += '</a>';
|
||||||
|
|
||||||
|
var link = $(linkContent).appendTo(item);
|
||||||
|
|
||||||
menuItems[opt.id] = opt;
|
menuItems[opt.id] = opt;
|
||||||
|
|
||||||
@ -59,7 +79,22 @@ RED.menu = (function() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (opt.toggle) {
|
if (opt.toggle) {
|
||||||
setSelected(opt.id, !isSelected(opt.id));
|
var selected = isSelected(opt.id);
|
||||||
|
if (typeof opt.toggle === "string") {
|
||||||
|
if (!selected) {
|
||||||
|
for (var m in menuItems) {
|
||||||
|
if (menuItems.hasOwnProperty(m)) {
|
||||||
|
var mi = menuItems[m];
|
||||||
|
if (mi.id != opt.id && opt.toggle == mi.toggle) {
|
||||||
|
setSelected(mi.id,false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setSelected(opt.id,true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setSelected(opt.id, !selected);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
opt.onselect.call(opt);
|
opt.onselect.call(opt);
|
||||||
}
|
}
|
||||||
@ -67,6 +102,11 @@ RED.menu = (function() {
|
|||||||
setState();
|
setState();
|
||||||
} else if (opt.href) {
|
} else if (opt.href) {
|
||||||
link.attr("target","_blank").attr("href",opt.href);
|
link.attr("target","_blank").attr("href",opt.href);
|
||||||
|
} else if (!opt.options) {
|
||||||
|
item.addClass("disabled");
|
||||||
|
link.click(function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (opt.options) {
|
if (opt.options) {
|
||||||
item.addClass("dropdown-submenu pull-left");
|
item.addClass("dropdown-submenu pull-left");
|
||||||
@ -79,6 +119,17 @@ RED.menu = (function() {
|
|||||||
if (opt.disabled) {
|
if (opt.disabled) {
|
||||||
item.addClass("disabled");
|
item.addClass("disabled");
|
||||||
}
|
}
|
||||||
|
if (opt.tip) {
|
||||||
|
item.popover({
|
||||||
|
placement:"left",
|
||||||
|
trigger: "hover",
|
||||||
|
delay: { show: 350, hide: 20 },
|
||||||
|
html: true,
|
||||||
|
container:'body',
|
||||||
|
content: opt.tip
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -88,8 +139,8 @@ RED.menu = (function() {
|
|||||||
function createMenu(options) {
|
function createMenu(options) {
|
||||||
|
|
||||||
var button = $("#"+options.id);
|
var button = $("#"+options.id);
|
||||||
|
|
||||||
var topMenu = $("<ul/>",{class:"dropdown-menu"}).insertAfter(button);
|
var topMenu = $("<ul/>",{id:options.id+"-submenu", class:"dropdown-menu pull-right"}).insertAfter(button);
|
||||||
|
|
||||||
for (var i=0;i<options.options.length;i++) {
|
for (var i=0;i<options.options.length;i++) {
|
||||||
var opt = options.options[i];
|
var opt = options.options[i];
|
||||||
|
267
public/style.css
267
public/style.css
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright 2013, 2014 IBM Corp.
|
* Copyright 2013, 2015 IBM Corp.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -28,7 +28,7 @@ body {
|
|||||||
height: 40px;
|
height: 40px;
|
||||||
background: #000;
|
background: #000;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 5px 0px 5px 20px;
|
padding: 0px 0px 0px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#dropTarget {
|
#dropTarget {
|
||||||
@ -52,7 +52,7 @@ body {
|
|||||||
font-size: 80px;
|
font-size: 80px;
|
||||||
}
|
}
|
||||||
div.btn-group, a.btn {
|
div.btn-group, a.btn {
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
-khtml-user-select: none;
|
-khtml-user-select: none;
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
-ms-user-select: none;
|
-ms-user-select: none;
|
||||||
@ -62,10 +62,11 @@ div.btn-group, a.btn {
|
|||||||
span.logo {
|
span.logo {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
float: left;
|
float: left;
|
||||||
|
margin-top: 5px;
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #ccc;
|
color: #C7C7C7;
|
||||||
}
|
}
|
||||||
span.logo span {
|
span.logo span {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
@ -74,6 +75,20 @@ span.logo span {
|
|||||||
span.logo img {
|
span.logo img {
|
||||||
height: 18px;
|
height: 18px;
|
||||||
}
|
}
|
||||||
|
#header ul.header-toolbar {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
list-style: none;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header ul.header-toolbar > li {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
-khtml-user-select: none;
|
-khtml-user-select: none;
|
||||||
@ -83,48 +98,120 @@ span.logo img {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#header .button {
|
#header .button {
|
||||||
line-height: 22px;
|
min-width: 20px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 40px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
padding: 4px 12px;
|
padding: 0px 12px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
border-radius: 3px;
|
color: #C7C7C7;
|
||||||
color: #ccc;
|
margin: auto 5px;
|
||||||
margin: auto 10px;
|
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
border-left: 2px solid #000;
|
||||||
|
border-right: 2px solid #000;
|
||||||
}
|
}
|
||||||
#header .button:not(.disabled):hover {
|
#header .button:not(.disabled):hover {
|
||||||
box-shadow: 0 0 2px #fff;
|
border-color: #323232;
|
||||||
}
|
|
||||||
#btn-deploy:not(.disabled):hover {
|
|
||||||
background: #ca3f39;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#btn-deploy {
|
#btn-deploy {
|
||||||
color: #fff !important;
|
background: #8C101C; /*#d24741;*/
|
||||||
background: #d24741;
|
color: #eee !important;
|
||||||
box-shadow: 0 0 2px #fff;
|
}
|
||||||
|
#btn-deploy + a {
|
||||||
|
background: #8C101C; /*#BA403B;*/
|
||||||
|
color: #eee;
|
||||||
|
}
|
||||||
|
#btn-deploy + a:hover {
|
||||||
|
background: #6E0A1E; /*#AD3C38;*/
|
||||||
|
color: #eee;
|
||||||
|
}
|
||||||
|
#btn-deploy + a:active {
|
||||||
|
background: #4C0A17; /*#aa1f19;*/
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
span.deploy-button-group.open > #btn-deploy + a {
|
||||||
|
background: #121212 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#btn-deploy:not(.disabled):hover {
|
||||||
|
background: #6E0A1E; /*#ca3f39;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#btn-deploy:not(.disabled):active {
|
||||||
|
background: #4C0A17 /*#aa1f19*/ !important;
|
||||||
}
|
}
|
||||||
#btn-deploy:not(.disabled):active {
|
#btn-deploy:not(.disabled):active {
|
||||||
background: #aa1f19 !important;
|
|
||||||
color: #ccc !important;
|
color: #ccc !important;
|
||||||
box-shadow: 0 0 2px #999;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#btn-deploy.disabled {
|
#btn-deploy.disabled {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
background: #444 ;
|
background: #444;
|
||||||
color: #999 !important;
|
color: #999 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#btn-deploy.disabled + a {
|
||||||
|
background: #444;
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
#btn-deploy.disabled + a:hover {
|
||||||
|
background: #555;
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
#btn-deploy.disabled + a:active {
|
||||||
|
background: #444;
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
span.deploy-button-group.open > #btn-deploy.disabled + a {
|
||||||
|
background: #121212 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#btn-deploy img {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
#btn-deploy.disabled img {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: inline-block;
|
||||||
|
margin: auto 10px;
|
||||||
|
vertical-align: middle;
|
||||||
|
background: #555;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
.button-group > a {
|
||||||
|
float: left;
|
||||||
|
line-height: 22px;
|
||||||
|
font-size: 14px;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 8px;
|
||||||
|
color: #ccc;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.button-group > a:last-child {
|
||||||
|
}
|
||||||
|
|
||||||
#btn-sidemenu {
|
#btn-sidemenu {
|
||||||
font-size: 20px !important;
|
font-size: 20px !important;
|
||||||
}
|
}
|
||||||
#btn-sidemenu:active, #btn-sidemenu.active {
|
#btn-sidemenu:active, #btn-sidemenu.active {
|
||||||
background: #333 ;
|
background: #121212;
|
||||||
}
|
}
|
||||||
#header .button:focus {
|
#header .button:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
li.open #btn-sidemenu {
|
||||||
|
background: #121212;
|
||||||
|
border-color: #121212;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#workspace-toolbar .button {
|
#workspace-toolbar .button {
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
@ -739,47 +826,39 @@ div.node-info {
|
|||||||
.hidden {
|
.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
.dropdown-menu {
|
|
||||||
font-size: 14px;
|
|
||||||
background: #000;
|
|
||||||
}
|
|
||||||
.dropdown-menu .divider {
|
|
||||||
background: #666;
|
|
||||||
border-bottom: #666;
|
|
||||||
}
|
|
||||||
.dropdown-menu>li>a {
|
|
||||||
color: #ddd;
|
|
||||||
}
|
|
||||||
.dropdown-submenu>ul {
|
|
||||||
border: 1px solid white;
|
|
||||||
border-radius: 6px !important;
|
|
||||||
}
|
|
||||||
.dropdown-menu li.disabled a {
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
.dropdown-menu li.disabled a:hover {
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
.dropdown-menu>li>a:hover, .dropdown-menu>li>a:focus, .dropdown-submenu:hover>a, .dropdown-submenu:focus>a {
|
.dropdown-menu>li>a:hover, .dropdown-menu>li>a:focus, .dropdown-submenu:hover>a, .dropdown-submenu:focus>a {
|
||||||
background: #999;
|
background: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-menu * .fa-check {
|
.dropdown-menu * .fa-check-square {
|
||||||
display: none;
|
display: none;
|
||||||
margin-right: -15px;
|
color: #e0e0e0;
|
||||||
|
margin-left: -25px;
|
||||||
margin-top: 3px;
|
margin-top: 3px;
|
||||||
}
|
}
|
||||||
.dropdown-menu * a.active > .fa-check {
|
.dropdown-menu * a.active > .fa-check-square {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
.dropdown-menu * .fa-square {
|
||||||
|
display: inline-block;
|
||||||
|
color: #e0e0e0;
|
||||||
|
margin-left: -25px;
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
.dropdown-menu * a.active > .fa-square {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.dropdown-menu>li.disabled>a:hover>[class^="icon-"] {
|
.dropdown-menu>li.disabled>a:hover>[class^="icon-"] {
|
||||||
background-image: url("bootstrap/img/glyphicons-halflings.png") !important;
|
background-image: url("bootstrap/img/glyphicons-halflings.png") !important;
|
||||||
}
|
}
|
||||||
/** Fix for unreachable dropdown menu **/
|
/** Fix for unreachable dropdown menu **/
|
||||||
.dropdown-menu {
|
.dropdown-menu {
|
||||||
|
border-radius: 0;
|
||||||
width: 200px !important;
|
width: 200px !important;
|
||||||
|
margin-left: 0px !important;
|
||||||
}
|
}
|
||||||
.dropdown-menu > li > a > i {
|
.dropdown-menu > li > a > i {
|
||||||
width: 10px;
|
width: 10px;
|
||||||
@ -792,8 +871,32 @@ div.node-info {
|
|||||||
white-space: normal !important;
|
white-space: normal !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popover-title { display: none; }
|
.dropdown-submenu>a:after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.dropdown-submenu>a:before {
|
||||||
|
display: block;
|
||||||
|
float: left;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-left: -30px;
|
||||||
|
border-color: transparent;
|
||||||
|
border-right-color: #e0e0e0;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 5px 5px 5px 0;
|
||||||
|
content: " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-submenu.disabled > a:before {
|
||||||
|
border-right-color: #444;
|
||||||
|
}
|
||||||
|
.dropdown-submenu.pull-left>.dropdown-menu {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.popover-title { display: none; }
|
||||||
|
|
||||||
.leftButton {
|
.leftButton {
|
||||||
margin-right: 200px !important;
|
margin-right: 200px !important;
|
||||||
@ -1070,3 +1173,73 @@ i.spinner {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#header ul.dropdown-menu {
|
||||||
|
background: #121212;
|
||||||
|
width: 250px !important;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header ul.dropdown-menu li a {
|
||||||
|
color: #C7C7C7;
|
||||||
|
padding: 3px 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header ul.dropdown-menu li a img {
|
||||||
|
margin-right: 10px;
|
||||||
|
padding: 4px;
|
||||||
|
border: 3px solid rgba(0,0,0,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#header ul.dropdown-menu li a.active img {
|
||||||
|
border: 3px solid #777677;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header ul.dropdown-menu li a span.menu-label-container {
|
||||||
|
width: 180px;
|
||||||
|
vertical-align: top;
|
||||||
|
display: inline-block;
|
||||||
|
text-indent: 0px;
|
||||||
|
}
|
||||||
|
#header ul.dropdown-menu li a span.menu-label {
|
||||||
|
font-size: 14px;
|
||||||
|
display: inline-block;
|
||||||
|
text-indent: 0px;
|
||||||
|
}
|
||||||
|
#header ul.dropdown-menu li a span.menu-sublabel {
|
||||||
|
color: #aeaeae;
|
||||||
|
font-size: 13px;
|
||||||
|
display: inline-block;
|
||||||
|
text-indent: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header ul.dropdown-menu > li:hover > a,
|
||||||
|
#header ul.dropdown-menu > li:focus > a {
|
||||||
|
background: #323232 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header ul.dropdown-menu li.divider {
|
||||||
|
background: #464646;
|
||||||
|
border-bottom-color: #323232;
|
||||||
|
}
|
||||||
|
#header ul.dropdown-menu li.disabled a {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Deploy menu customisations */
|
||||||
|
#header ul#btn-deploy-options-submenu {
|
||||||
|
width: 300px !important;
|
||||||
|
}
|
||||||
|
#header ul#btn-deploy-options-submenu li a span.menu-label {
|
||||||
|
font-size: 16px;
|
||||||
|
display: inline-block;
|
||||||
|
text-indent: 0px;
|
||||||
|
}
|
||||||
|
#header ul#btn-deploy-options-submenu li a {
|
||||||
|
padding: 10px 30px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
#header ul#btn-deploy-options-submenu li a > i.fa {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,11 +28,13 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
post: function(req,res) {
|
post: function(req,res) {
|
||||||
var flows = req.body;
|
var flows = req.body;
|
||||||
redNodes.setFlows(flows).then(function() {
|
var deploymentType = req.get("Node-RED-Deployment-Type")||"full";
|
||||||
|
redNodes.setFlows(flows,deploymentType).then(function() {
|
||||||
res.send(204);
|
res.send(204);
|
||||||
}).otherwise(function(err) {
|
}).otherwise(function(err) {
|
||||||
util.log("[red] Error saving flows : "+err);
|
util.log("[red] Error saving flows : "+err);
|
||||||
res.send(500,err.message);
|
res.send(500,err.message);
|
||||||
|
console.log(err.stack);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
654
red/nodes/Flow.js
Normal file
654
red/nodes/Flow.js
Normal file
@ -0,0 +1,654 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2015 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 util = require("util");
|
||||||
|
var when = require("when");
|
||||||
|
var clone = require("clone");
|
||||||
|
|
||||||
|
var typeRegistry = require("./registry");
|
||||||
|
var credentials = require("./credentials");
|
||||||
|
var redUtil = require("../util");
|
||||||
|
var events = require("../events");
|
||||||
|
|
||||||
|
function getID() {
|
||||||
|
return (1+Math.random()*4294967295).toString(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createNode(type,config) {
|
||||||
|
var nn = null;
|
||||||
|
var nt = typeRegistry.get(type);
|
||||||
|
if (nt) {
|
||||||
|
try {
|
||||||
|
nn = new nt(clone(config));
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
util.log("[red] "+type+" : "+err);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
util.log("[red] unknown type: "+type);
|
||||||
|
}
|
||||||
|
return nn;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function createSubflow(sf,sfn,subflows) {
|
||||||
|
//console.log("CREATE SUBFLOW",sf.config.id,sfn.id);
|
||||||
|
var nodes = [];
|
||||||
|
var node_map = {};
|
||||||
|
var newNodes = [];
|
||||||
|
var node;
|
||||||
|
var wires;
|
||||||
|
var i,j,k;
|
||||||
|
|
||||||
|
// Clone all of the subflow node definitions and give them new IDs
|
||||||
|
for (i=0;i<sf.nodes.length;i++) {
|
||||||
|
node = clone(sf.nodes[i].config);
|
||||||
|
var nid = getID();
|
||||||
|
node_map[node.id] = node;
|
||||||
|
node._alias = node.id;
|
||||||
|
node.id = nid;
|
||||||
|
newNodes.push(node);
|
||||||
|
}
|
||||||
|
// Update all subflow interior wiring to reflect new node IDs
|
||||||
|
for (i=0;i<newNodes.length;i++) {
|
||||||
|
node = newNodes[i];
|
||||||
|
var outputs = node.wires;
|
||||||
|
|
||||||
|
for (j=0;j<outputs.length;j++) {
|
||||||
|
wires = outputs[j];
|
||||||
|
for (k=0;k<wires.length;k++) {
|
||||||
|
outputs[j][k] = node_map[outputs[j][k]].id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a subflow node to accept inbound messages and route appropriately
|
||||||
|
var Node = require("./Node");
|
||||||
|
var subflowInstance = {
|
||||||
|
id: sfn.id,
|
||||||
|
type: sfn.type,
|
||||||
|
name: sfn.name,
|
||||||
|
wires: []
|
||||||
|
}
|
||||||
|
if (sf.config.in) {
|
||||||
|
subflowInstance.wires = sf.config.in.map(function(n) { return n.wires.map(function(w) { return node_map[w.id].id;})})
|
||||||
|
subflowInstance._originalWires = clone(subflowInstance.wires);
|
||||||
|
}
|
||||||
|
var subflowNode = new Node(subflowInstance);
|
||||||
|
|
||||||
|
subflowNode.on("input", function(msg) { this.send(msg);});
|
||||||
|
|
||||||
|
|
||||||
|
subflowNode._updateWires = subflowNode.updateWires;
|
||||||
|
|
||||||
|
subflowNode.updateWires = function(newWires) {
|
||||||
|
// Wire the subflow outputs
|
||||||
|
if (sf.config.out) {
|
||||||
|
var node,wires,i,j;
|
||||||
|
// Restore the original wiring to the internal nodes
|
||||||
|
|
||||||
|
subflowInstance.wires = clone(subflowInstance._originalWires);
|
||||||
|
|
||||||
|
for (i=0;i<sf.config.out.length;i++) {
|
||||||
|
wires = sf.config.out[i].wires;
|
||||||
|
for (j=0;j<wires.length;j++) {
|
||||||
|
if (wires[j].id != sf.config.id) {
|
||||||
|
node = node_map[wires[j].id];
|
||||||
|
if (node._originalWires) {
|
||||||
|
node.wires = clone(node._originalWires);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var modifiedNodes = {};
|
||||||
|
var subflowInstanceModified = false;
|
||||||
|
|
||||||
|
for (i=0;i<sf.config.out.length;i++) {
|
||||||
|
wires = sf.config.out[i].wires;
|
||||||
|
for (j=0;j<wires.length;j++) {
|
||||||
|
if (wires[j].id === sf.config.id) {
|
||||||
|
subflowInstance.wires[wires[j].port] = subflowInstance.wires[wires[j].port].concat(newWires[i]);
|
||||||
|
subflowInstanceModified = true;
|
||||||
|
} else {
|
||||||
|
node = node_map[wires[j].id];
|
||||||
|
node.wires[wires[j].port] = node.wires[wires[j].port].concat(newWires[i]);
|
||||||
|
modifiedNodes[node.id] = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Object.keys(modifiedNodes).forEach(function(id) {
|
||||||
|
var node = modifiedNodes[id];
|
||||||
|
subflowNode.instanceNodes[id].updateWires(node.wires);
|
||||||
|
});
|
||||||
|
if (subflowInstanceModified) {
|
||||||
|
subflowNode._updateWires(subflowInstance.wires);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes.push(subflowNode);
|
||||||
|
|
||||||
|
// Wire the subflow outputs
|
||||||
|
if (sf.config.out) {
|
||||||
|
var modifiedNodes = {};
|
||||||
|
for (i=0;i<sf.config.out.length;i++) {
|
||||||
|
wires = sf.config.out[i].wires;
|
||||||
|
for (j=0;j<wires.length;j++) {
|
||||||
|
if (wires[j].id === sf.config.id) {
|
||||||
|
// A subflow input wired straight to a subflow output
|
||||||
|
subflowInstance.wires[wires[j].port] = subflowInstance.wires[wires[j].port].concat(sfn.wires[i])
|
||||||
|
subflowNode._updateWires(subflowInstance.wires);
|
||||||
|
} else {
|
||||||
|
node = node_map[wires[j].id];
|
||||||
|
modifiedNodes[node.id] = node;
|
||||||
|
if (!node._originalWires) {
|
||||||
|
node._originalWires = clone(node.wires);
|
||||||
|
}
|
||||||
|
node.wires[wires[j].port] = node.wires[wires[j].port].concat(sfn.wires[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instantiate the nodes
|
||||||
|
for (i=0;i<newNodes.length;i++) {
|
||||||
|
node = newNodes[i];
|
||||||
|
var type = node.type;
|
||||||
|
|
||||||
|
var m = /^subflow:(.+)$/.exec(type);
|
||||||
|
if (!m) {
|
||||||
|
nodes.push(createNode(type,node));
|
||||||
|
} else {
|
||||||
|
var subflowId = m[1];
|
||||||
|
nodes = nodes.concat(createSubflow(subflows[subflowId],node,subflows));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subflowNode.instanceNodes = {};
|
||||||
|
|
||||||
|
nodes.forEach(function(node) {
|
||||||
|
subflowNode.instanceNodes[node.id] = node;
|
||||||
|
});
|
||||||
|
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function diffNodeConfigs(oldNode,newNode) {
|
||||||
|
if (oldNode == null) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
for (var p in newNode) {
|
||||||
|
if (newNode.hasOwnProperty(p) && p != "x" && p != "y" && p != "wires") {
|
||||||
|
if (!redUtil.compareObjects(oldNode[p],newNode[p])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var subflowInstanceRE = /^subflow:(.+)$/;
|
||||||
|
|
||||||
|
function Flow(config) {
|
||||||
|
|
||||||
|
this.activeNodes = {};
|
||||||
|
this.subflowInstanceNodes = {};
|
||||||
|
this.started = false;
|
||||||
|
|
||||||
|
this.parseConfig(config);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Flow.prototype.parseConfig = function(config) {
|
||||||
|
var i;
|
||||||
|
var nodeConfig;
|
||||||
|
var nodeType;
|
||||||
|
|
||||||
|
this.config = config;
|
||||||
|
|
||||||
|
this.allNodes = {};
|
||||||
|
|
||||||
|
this.nodes = {};
|
||||||
|
this.subflows = {};
|
||||||
|
|
||||||
|
var unknownTypes = {};
|
||||||
|
|
||||||
|
for (i=0;i<this.config.length;i++) {
|
||||||
|
nodeConfig = this.config[i];
|
||||||
|
nodeType = nodeConfig.type;
|
||||||
|
if (nodeType == "subflow") {
|
||||||
|
this.subflows[nodeConfig.id] = {
|
||||||
|
type: "subflow",
|
||||||
|
config: nodeConfig,
|
||||||
|
nodes: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
//console.log("Known subflows:",Object.keys(this.subflows));
|
||||||
|
|
||||||
|
for (i=0;i<this.config.length;i++) {
|
||||||
|
nodeConfig = this.config[i];
|
||||||
|
|
||||||
|
this.allNodes[nodeConfig.id] = nodeConfig;
|
||||||
|
|
||||||
|
nodeType = nodeConfig.type;
|
||||||
|
|
||||||
|
if (nodeConfig.credentials) {
|
||||||
|
delete nodeConfig.credentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodeType != "tab" && nodeType != "subflow") {
|
||||||
|
var m = subflowInstanceRE.exec(nodeType);
|
||||||
|
if ((m && !this.subflows[m[1]]) || (!m && !typeRegistry.get(nodeType))) {
|
||||||
|
// This is an unknown subflow or an unknown type
|
||||||
|
unknownTypes[nodeType] = true;
|
||||||
|
} else {
|
||||||
|
var nodeInfo = {
|
||||||
|
type: nodeType,
|
||||||
|
config:nodeConfig
|
||||||
|
}
|
||||||
|
if (m) {
|
||||||
|
nodeInfo.subflow = m[1];
|
||||||
|
}
|
||||||
|
if (this.subflows[nodeConfig.z]) {
|
||||||
|
this.subflows[nodeConfig.z].nodes.push(nodeInfo);
|
||||||
|
} else {
|
||||||
|
this.nodes[nodeConfig.id] = nodeInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log("NODES");
|
||||||
|
//for (i in this.nodes) {
|
||||||
|
// if (this.nodes.hasOwnProperty(i)) {
|
||||||
|
// console.log(" ",i,this.nodes[i].type,this.nodes[i].config.name||"");
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//console.log("SUBFLOWS");
|
||||||
|
//for (i in this.subflows) {
|
||||||
|
// if (this.subflows.hasOwnProperty(i)) {
|
||||||
|
// console.log(" ",i,this.subflows[i].type,this.subflows[i].config.name||"");
|
||||||
|
// for (var j=0;j<this.subflows[i].nodes.length;j++) {
|
||||||
|
// console.log(" ",this.subflows[i].nodes[j].config.id,this.subflows[i].nodes[j].type,this.subflows[i].nodes[j].config.name||"");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
this.missingTypes = Object.keys(unknownTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
Flow.prototype.start = function() {
|
||||||
|
this.started = true;
|
||||||
|
if (this.missingTypes.length > 0) {
|
||||||
|
throw new Error("missing types");
|
||||||
|
}
|
||||||
|
|
||||||
|
events.emit("nodes-starting");
|
||||||
|
|
||||||
|
for (var id in this.nodes) {
|
||||||
|
if (this.nodes.hasOwnProperty(id)) {
|
||||||
|
var node = this.nodes[id];
|
||||||
|
if (!node.subflow) {
|
||||||
|
if (!this.activeNodes[id]) {
|
||||||
|
this.activeNodes[id] = createNode(node.type,node.config);
|
||||||
|
//console.log(id,"created");
|
||||||
|
} else {
|
||||||
|
//console.log(id,"already running");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!this.subflowInstanceNodes[id]) {
|
||||||
|
var nodes = createSubflow(this.subflows[node.subflow],node.config,this.subflows);
|
||||||
|
this.subflowInstanceNodes[id] = nodes.map(function(n) { return n.id});
|
||||||
|
for (var i=0;i<nodes.length;i++) {
|
||||||
|
this.activeNodes[nodes[i].id] = nodes[i];
|
||||||
|
}
|
||||||
|
//console.log(id,"(sf) created");
|
||||||
|
} else {
|
||||||
|
//console.log(id,"(sf) already running");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
credentials.clean(this.config);
|
||||||
|
events.emit("nodes-started");
|
||||||
|
}
|
||||||
|
|
||||||
|
Flow.prototype.stop = function(nodeList) {
|
||||||
|
nodeList = nodeList || Object.keys(this.activeNodes);
|
||||||
|
var flow = this;
|
||||||
|
return when.promise(function(resolve) {
|
||||||
|
events.emit("nodes-stopping");
|
||||||
|
var promises = [];
|
||||||
|
for (var i=0;i<nodeList.length;i++) {
|
||||||
|
var node = flow.activeNodes[nodeList[i]];
|
||||||
|
if (node) {
|
||||||
|
try {
|
||||||
|
var p = node.close();
|
||||||
|
if (p) {
|
||||||
|
promises.push(p);
|
||||||
|
}
|
||||||
|
} catch(err) {
|
||||||
|
node.error(err);
|
||||||
|
}
|
||||||
|
delete flow.subflowInstanceNodes[nodeList[i]];
|
||||||
|
delete flow.activeNodes[nodeList[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
when.settle(promises).then(function() {
|
||||||
|
events.emit("nodes-stopped");
|
||||||
|
flow.started = false;
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Flow.prototype.getMissingTypes = function() {
|
||||||
|
return this.missingTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
Flow.prototype.typeRegistered = function(type) {
|
||||||
|
if (this.missingTypes.length > 0) {
|
||||||
|
var i = this.missingTypes.indexOf(type);
|
||||||
|
if (i != -1) {
|
||||||
|
this.missingTypes.splice(i,1);
|
||||||
|
if (this.missingTypes.length === 0 && this.started) {
|
||||||
|
this.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Flow.prototype.getNode = function(id) {
|
||||||
|
return this.activeNodes[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
Flow.prototype.getFlow = function() {
|
||||||
|
//console.log(this.config);
|
||||||
|
return this.config;
|
||||||
|
}
|
||||||
|
|
||||||
|
Flow.prototype.eachNode = function(callback) {
|
||||||
|
for (var id in this.activeNodes) {
|
||||||
|
if (this.activeNodes.hasOwnProperty(id)) {
|
||||||
|
callback(this.activeNodes[id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Flow.prototype.applyConfig = function(config,type) {
|
||||||
|
|
||||||
|
var activeNodesToStop = [];
|
||||||
|
var nodesToRewire = [];
|
||||||
|
|
||||||
|
if (type && type!="full") {
|
||||||
|
var diff = this.diffFlow(config);
|
||||||
|
//console.log(diff);
|
||||||
|
//var diff = {
|
||||||
|
// deleted:[]
|
||||||
|
// changed:[]
|
||||||
|
// linked:[]
|
||||||
|
// wiringChanged: []
|
||||||
|
//}
|
||||||
|
|
||||||
|
var nodesToStop = [];
|
||||||
|
var nodesToCreate = [];
|
||||||
|
nodesToRewire = diff.wiringChanged;
|
||||||
|
|
||||||
|
if (type == "nodes") {
|
||||||
|
nodesToStop = diff.deleted.concat(diff.changed);
|
||||||
|
nodesToCreate = diff.changed;
|
||||||
|
} else if (type == "flows") {
|
||||||
|
nodesToStop = diff.deleted.concat(diff.changed).concat(diff.linked);
|
||||||
|
nodesToCreate = diff.changed.concat(diff.linked);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i=0;i<nodesToStop.length;i++) {
|
||||||
|
var id = nodesToStop[i];
|
||||||
|
if (this.subflowInstanceNodes[id]) {
|
||||||
|
activeNodesToStop = activeNodesToStop.concat(this.subflowInstanceNodes[id]);
|
||||||
|
} else if (this.activeNodes[id]) {
|
||||||
|
activeNodesToStop.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
activeNodesToStop = Object.keys(this.activeNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
var flow = this;
|
||||||
|
return this.stop(activeNodesToStop).then(function() {
|
||||||
|
flow.parseConfig(config);
|
||||||
|
for (var i=0;i<nodesToRewire.length;i++) {
|
||||||
|
var node = flow.activeNodes[nodesToRewire[i]];
|
||||||
|
if (node) {
|
||||||
|
node.updateWires(flow.allNodes[node.id].wires);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flow.start();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Flow.prototype.diffFlow = function(config) {
|
||||||
|
var flow = this;
|
||||||
|
var configNodes = {};
|
||||||
|
var changedNodes = {};
|
||||||
|
var deletedNodes = {};
|
||||||
|
var linkChangedNodes = {};
|
||||||
|
|
||||||
|
var activeLinks = {};
|
||||||
|
var newLinks = {};
|
||||||
|
|
||||||
|
var changedSubflowStack = [];
|
||||||
|
var changedSubflows = {};
|
||||||
|
|
||||||
|
var buildNodeLinks = function(nodeLinks,n,nodes) {
|
||||||
|
nodeLinks[n.id] = nodeLinks[n.id] || [];
|
||||||
|
if (n.wires) {
|
||||||
|
for (var j=0;j<n.wires.length;j++) {
|
||||||
|
var wires = n.wires[j];
|
||||||
|
for (var k=0;k<wires.length;k++) {
|
||||||
|
nodeLinks[n.id].push(wires[k]);
|
||||||
|
var nn = nodes[wires[k]];
|
||||||
|
if (nn) {
|
||||||
|
nodeLinks[nn.id] = nodeLinks[nn.id] || [];
|
||||||
|
nodeLinks[nn.id].push(n.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.forEach(function(node) {
|
||||||
|
configNodes[node.id] = node;
|
||||||
|
});
|
||||||
|
|
||||||
|
config.forEach(function(node) {
|
||||||
|
var changed = false;
|
||||||
|
if (node.credentials) {
|
||||||
|
changed = true;
|
||||||
|
delete node.credentials;
|
||||||
|
} else {
|
||||||
|
changed = diffNodeConfigs(flow.allNodes[node.id],node);
|
||||||
|
if (!changed) {
|
||||||
|
if (configNodes[node.z] && configNodes[node.z].type == "subflow") {
|
||||||
|
var originalNode = flow.allNodes[node.id];
|
||||||
|
if (originalNode && !redUtil.compareObjects(originalNode.wires,node.wires)) {
|
||||||
|
// This is a node in a subflow whose wiring has changed. Mark subflow as changed
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
changedNodes[node.id] = node;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.config.forEach(function(node) {
|
||||||
|
if (!configNodes[node.id]) {
|
||||||
|
deletedNodes[node.id] = node;
|
||||||
|
}
|
||||||
|
buildNodeLinks(activeLinks,node,flow.allNodes);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.config.forEach(function(node) {
|
||||||
|
for (var prop in node) {
|
||||||
|
if (node.hasOwnProperty(prop) && prop != "z" && prop != "id" && prop != "wires") {
|
||||||
|
// This node has a property that references a changed node
|
||||||
|
// Assume it is a config node change and mark this node as
|
||||||
|
// changed.
|
||||||
|
if (changedNodes[node[prop]]) {
|
||||||
|
changedNodes[node.id] = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
var checkSubflowMembership = function(nodes,id) {
|
||||||
|
var node = nodes[id];
|
||||||
|
if (node) {
|
||||||
|
if (node.type == "subflow") {
|
||||||
|
changedSubflows[id] = node;
|
||||||
|
changedSubflowStack.push(id);
|
||||||
|
} else if (nodes[node.z] && nodes[node.z].type == "subflow") {
|
||||||
|
if (!changedSubflows[node.z]) {
|
||||||
|
changedSubflows[node.z] = nodes[node.z];
|
||||||
|
changedSubflowStack.push(node.z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.keys(changedNodes).forEach(function(n) { checkSubflowMembership(configNodes,n)});
|
||||||
|
Object.keys(deletedNodes).forEach(function(n) { checkSubflowMembership(flow.allNodes,n)});
|
||||||
|
|
||||||
|
while (changedSubflowStack.length > 0) {
|
||||||
|
var subflowId = changedSubflowStack.pop();
|
||||||
|
|
||||||
|
config.forEach(function(node) {
|
||||||
|
if (node.type == "subflow:"+subflowId) {
|
||||||
|
if (!changedNodes[node.id]) {
|
||||||
|
changedNodes[node.id] = node;
|
||||||
|
checkSubflowMembership(configNodes,node.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
config.forEach(function(node) {
|
||||||
|
buildNodeLinks(newLinks,node,configNodes);
|
||||||
|
});
|
||||||
|
|
||||||
|
var markLinkedNodes = function(linkChanged,otherChangedNodes,linkMap,allNodes) {
|
||||||
|
var stack = Object.keys(changedNodes).concat(Object.keys(otherChangedNodes));
|
||||||
|
var visited = {};
|
||||||
|
|
||||||
|
while(stack.length > 0) {
|
||||||
|
var id = stack.pop();
|
||||||
|
var linkedNodes = linkMap[id];
|
||||||
|
if (linkedNodes) {
|
||||||
|
for (var i=0;i<linkedNodes.length;i++) {
|
||||||
|
var linkedNodeId = linkedNodes[i];
|
||||||
|
if (changedNodes[linkedNodeId] || deletedNodes[linkedNodeId] || otherChangedNodes[linkedNodeId] || linkChanged[linkedNodeId]) {
|
||||||
|
// Do nothing - this linked node is already marked as changed, so will get done
|
||||||
|
} else {
|
||||||
|
linkChanged[linkedNodeId] = true;
|
||||||
|
stack.push(linkedNodeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
markLinkedNodes(linkChangedNodes,{},newLinks,configNodes);
|
||||||
|
markLinkedNodes(linkChangedNodes,{},activeLinks,flow.allNodes);
|
||||||
|
|
||||||
|
var modifiedLinkNodes = {};
|
||||||
|
|
||||||
|
config.forEach(function(node) {
|
||||||
|
if (!changedNodes[node.id]) {
|
||||||
|
// only concerned about unchanged nodes whose wiring may have changed
|
||||||
|
var newNodeLinks = newLinks[node.id];
|
||||||
|
var oldNodeLinks = activeLinks[node.id];
|
||||||
|
|
||||||
|
var newLinkMap = {};
|
||||||
|
newNodeLinks.forEach(function(l) { newLinkMap[l] = (newLinkMap[l]||0)+1;});
|
||||||
|
|
||||||
|
var oldLinkMap = {};
|
||||||
|
oldNodeLinks.forEach(function(l) { oldLinkMap[l] = (oldLinkMap[l]||0)+1;});
|
||||||
|
|
||||||
|
newNodeLinks.forEach(function(link) {
|
||||||
|
if (newLinkMap[link] != oldLinkMap[link]) {
|
||||||
|
modifiedLinkNodes[node.id] = node;
|
||||||
|
linkChangedNodes[node.id] = node;
|
||||||
|
if (!changedNodes[link] && !deletedNodes[link]) {
|
||||||
|
modifiedLinkNodes[link] = configNodes[link];
|
||||||
|
linkChangedNodes[link] = configNodes[link];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
oldNodeLinks.forEach(function(link) {
|
||||||
|
if (newLinkMap[link] != oldLinkMap[link]) {
|
||||||
|
modifiedLinkNodes[node.id] = node;
|
||||||
|
linkChangedNodes[node.id] = node;
|
||||||
|
if (!changedNodes[link] && !deletedNodes[link]) {
|
||||||
|
modifiedLinkNodes[link] = configNodes[link];
|
||||||
|
linkChangedNodes[link] = configNodes[link];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
markLinkedNodes(linkChangedNodes,modifiedLinkNodes,newLinks,configNodes);
|
||||||
|
|
||||||
|
//config.forEach(function(n) {
|
||||||
|
// console.log((changedNodes[n.id]!=null)?"[C]":"[ ]",(linkChangedNodes[n.id]!=null)?"[L]":"[ ]","[ ]",n.id,n.type,n.name);
|
||||||
|
//});
|
||||||
|
|
||||||
|
//Object.keys(deletedNodes).forEach(function(id) {
|
||||||
|
// var n = flow.allNodes[id];
|
||||||
|
// console.log("[ ] [ ] [D]",n.id,n.type);
|
||||||
|
//});
|
||||||
|
|
||||||
|
var diff = {
|
||||||
|
deleted: Object.keys(deletedNodes).filter(function(id) { return deletedNodes[id].type != "subflow" && (!deletedNodes[id].z || configNodes[deletedNodes[id].z].type != "subflow")}),
|
||||||
|
changed: Object.keys(changedNodes).filter(function(id) { return changedNodes[id].type != "subflow" && (!changedNodes[id].z || configNodes[changedNodes[id].z].type != "subflow")}),
|
||||||
|
linked: Object.keys(linkChangedNodes).filter(function(id) { return linkChangedNodes[id].type != "subflow" && (!linkChangedNodes[id].z || configNodes[linkChangedNodes[id].z].type != "subflow")}),
|
||||||
|
wiringChanged: []
|
||||||
|
}
|
||||||
|
|
||||||
|
config.forEach(function(n) {
|
||||||
|
if (!configNodes[n.z] || configNodes[n.z].type != "subflow") {
|
||||||
|
var originalNode = flow.allNodes[n.id];
|
||||||
|
if (originalNode && !redUtil.compareObjects(originalNode.wires,n.wires)) {
|
||||||
|
diff.wiringChanged.push(n.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return diff;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Flow;
|
@ -25,12 +25,19 @@ var comms = require("../comms");
|
|||||||
|
|
||||||
function Node(n) {
|
function Node(n) {
|
||||||
this.id = n.id;
|
this.id = n.id;
|
||||||
flows.add(this);
|
|
||||||
this.type = n.type;
|
this.type = n.type;
|
||||||
if (n.name) {
|
if (n.name) {
|
||||||
this.name = n.name;
|
this.name = n.name;
|
||||||
}
|
}
|
||||||
this.wires = n.wires || [];
|
flows.add(this);
|
||||||
|
this.updateWires(n.wires);
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(Node, EventEmitter);
|
||||||
|
|
||||||
|
Node.prototype.updateWires = function(wires) {
|
||||||
|
this.wires = wires || [];
|
||||||
|
delete this._wire;
|
||||||
|
|
||||||
var wc = 0;
|
var wc = 0;
|
||||||
this.wires.forEach(function(w) {
|
this.wires.forEach(function(w) {
|
||||||
@ -40,14 +47,16 @@ function Node(n) {
|
|||||||
if (wc === 0) {
|
if (wc === 0) {
|
||||||
// With nothing wired to the node, no-op send
|
// With nothing wired to the node, no-op send
|
||||||
this.send = function(msg) {}
|
this.send = function(msg) {}
|
||||||
} else if (this.wires.length === 1 && this.wires[0].length === 1) {
|
} else {
|
||||||
// Single wire, so we can shortcut the send when
|
this.send = Node.prototype.send;
|
||||||
// a single message is sent
|
if (this.wires.length === 1 && this.wires[0].length === 1) {
|
||||||
this._wire = this.wires[0][0];
|
// Single wire, so we can shortcut the send when
|
||||||
|
// a single message is sent
|
||||||
|
this._wire = this.wires[0][0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
util.inherits(Node, EventEmitter);
|
}
|
||||||
|
|
||||||
Node.prototype._on = Node.prototype.on;
|
Node.prototype._on = Node.prototype.on;
|
||||||
|
|
||||||
|
@ -111,15 +111,18 @@ module.exports = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes any credentials for nodes that no longer exist
|
* Deletes any credentials for nodes that no longer exist
|
||||||
* @param getNode a function that can return a node for a given id
|
* @param config a flow config
|
||||||
* @return a promise for the saving of credentials to storage
|
* @return a promise for the saving of credentials to storage
|
||||||
*/
|
*/
|
||||||
clean: function (getNode) {
|
clean: function (config) {
|
||||||
|
var existingIds = {};
|
||||||
|
config.forEach(function(n) {
|
||||||
|
existingIds[n.id] = true;
|
||||||
|
});
|
||||||
var deletedCredentials = false;
|
var deletedCredentials = false;
|
||||||
for (var c in credentialCache) {
|
for (var c in credentialCache) {
|
||||||
if (credentialCache.hasOwnProperty(c)) {
|
if (credentialCache.hasOwnProperty(c)) {
|
||||||
var n = getNode(c);
|
if (!existingIds[c]) {
|
||||||
if (!n) {
|
|
||||||
deletedCredentials = true;
|
deletedCredentials = true;
|
||||||
delete credentialCache[c];
|
delete credentialCache[c];
|
||||||
}
|
}
|
||||||
@ -164,7 +167,6 @@ module.exports = {
|
|||||||
|
|
||||||
var dashedType = nodeType.replace(/\s+/g, '-');
|
var dashedType = nodeType.replace(/\s+/g, '-');
|
||||||
var definition = credentialsDef[dashedType];
|
var definition = credentialsDef[dashedType];
|
||||||
|
|
||||||
if (!definition) {
|
if (!definition) {
|
||||||
util.log('Credential Type ' + nodeType + ' is not registered.');
|
util.log('Credential Type ' + nodeType + ' is not registered.');
|
||||||
return;
|
return;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright 2014 IBM Corp.
|
* Copyright 2014, 2015 IBM Corp.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -20,211 +20,27 @@ var when = require("when");
|
|||||||
|
|
||||||
var typeRegistry = require("./registry");
|
var typeRegistry = require("./registry");
|
||||||
var credentials = require("./credentials");
|
var credentials = require("./credentials");
|
||||||
|
var Flow = require("./Flow");
|
||||||
var log = require("../log");
|
var log = require("../log");
|
||||||
var events = require("../events");
|
var events = require("../events");
|
||||||
|
var redUtil = require("../util");
|
||||||
var storage = null;
|
var storage = null;
|
||||||
|
|
||||||
|
|
||||||
|
var activeFlow = null;
|
||||||
|
|
||||||
var nodes = {};
|
var nodes = {};
|
||||||
var subflows = {};
|
var subflows = {};
|
||||||
var activeConfig = [];
|
var activeConfig = [];
|
||||||
var missingTypes = [];
|
var activeConfigNodes = {};
|
||||||
|
|
||||||
events.on('type-registered',function(type) {
|
events.on('type-registered',function(type) {
|
||||||
if (missingTypes.length > 0) {
|
if (activeFlow) {
|
||||||
var i = missingTypes.indexOf(type);
|
if (activeFlow.typeRegistered(type)) {
|
||||||
if (i != -1) {
|
util.log("[red] Missing type registered: "+type);
|
||||||
missingTypes.splice(i,1);
|
|
||||||
util.log("[red] Missing type registered: "+type);
|
|
||||||
if (missingTypes.length === 0) {
|
|
||||||
parseConfig();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function getID() {
|
|
||||||
return (1+Math.random()*4294967295).toString(16);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSubflow(sf,sfn) {
|
|
||||||
var node_map = {};
|
|
||||||
var newNodes = [];
|
|
||||||
var node;
|
|
||||||
var wires;
|
|
||||||
var i,j,k;
|
|
||||||
|
|
||||||
// Clone all of the subflow node definitions and give them new IDs
|
|
||||||
for (i=0;i<sf.nodes.length;i++) {
|
|
||||||
node = clone(sf.nodes[i]);
|
|
||||||
var nid = getID();
|
|
||||||
node_map[node.id] = node;
|
|
||||||
node.id = nid;
|
|
||||||
newNodes.push(node);
|
|
||||||
}
|
|
||||||
// Update all subflow interior wiring to reflect new node IDs
|
|
||||||
for (i=0;i<newNodes.length;i++) {
|
|
||||||
node = newNodes[i];
|
|
||||||
var outputs = node.wires;
|
|
||||||
|
|
||||||
for (j=0;j<outputs.length;j++) {
|
|
||||||
wires = outputs[j];
|
|
||||||
for (k=0;k<wires.length;k++) {
|
|
||||||
outputs[j][k] = node_map[outputs[j][k]].id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a subflow node to accept inbound messages and route appropriately
|
|
||||||
var Node = require("./Node");
|
|
||||||
var subflowInstance = {
|
|
||||||
id: sfn.id,
|
|
||||||
type: sfn.type,
|
|
||||||
name: sfn.name,
|
|
||||||
wires: []
|
|
||||||
}
|
|
||||||
if (sf.in) {
|
|
||||||
subflowInstance.wires = sf.in.map(function(n) { return n.wires.map(function(w) { return node_map[w.id].id;})})
|
|
||||||
}
|
|
||||||
var subflowNode = new Node(subflowInstance);
|
|
||||||
subflowNode.on("input", function(msg) { this.send(msg);});
|
|
||||||
|
|
||||||
// Wire the subflow outputs
|
|
||||||
if (sf.out) {
|
|
||||||
for (i=0;i<sf.out.length;i++) {
|
|
||||||
wires = sf.out[i].wires;
|
|
||||||
for (j=0;j<wires.length;j++) {
|
|
||||||
if (wires[j].id === sf.id) {
|
|
||||||
node = subflowNode;
|
|
||||||
delete subflowNode._wire;
|
|
||||||
} else {
|
|
||||||
node = node_map[wires[j].id];
|
|
||||||
}
|
|
||||||
node.wires[wires[j].port] = node.wires[wires[j].port].concat(sfn.wires[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Instantiate the nodes
|
|
||||||
for (i=0;i<newNodes.length;i++) {
|
|
||||||
node = newNodes[i];
|
|
||||||
var nn = null;
|
|
||||||
var type = node.type;
|
|
||||||
|
|
||||||
var m = /^subflow:(.+)$/.exec(type);
|
|
||||||
if (!m) {
|
|
||||||
var nt = typeRegistry.get(type);
|
|
||||||
if (nt) {
|
|
||||||
try {
|
|
||||||
nn = new nt(node);
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
util.log("[red] "+type+" : "+err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (nn === null) {
|
|
||||||
util.log("[red] unknown type: "+type);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var subflowId = m[1];
|
|
||||||
createSubflow(subflows[subflowId],node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the current activeConfig and creates the required node instances
|
|
||||||
*/
|
|
||||||
function parseConfig() {
|
|
||||||
var i;
|
|
||||||
var nt;
|
|
||||||
var type;
|
|
||||||
var subflow;
|
|
||||||
missingTypes = [];
|
|
||||||
|
|
||||||
// Scan the configuration for any unknown node types
|
|
||||||
for (i=0;i<activeConfig.length;i++) {
|
|
||||||
type = activeConfig[i].type;
|
|
||||||
// TODO: remove workspace in next release+1
|
|
||||||
if (type != "workspace" && type != "tab" && !/^subflow($|:.+$)/.test(type)) {
|
|
||||||
nt = typeRegistry.get(type);
|
|
||||||
if (!nt && missingTypes.indexOf(type) == -1) {
|
|
||||||
missingTypes.push(type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Abort if there are any missing types
|
|
||||||
if (missingTypes.length > 0) {
|
|
||||||
util.log("[red] Waiting for missing types to be registered:");
|
|
||||||
for (i=0;i<missingTypes.length;i++) {
|
|
||||||
util.log("[red] - "+missingTypes[i]);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
util.log("[red] Starting flows");
|
|
||||||
events.emit("nodes-starting");
|
|
||||||
|
|
||||||
for (i=0;i<activeConfig.length;i++) {
|
|
||||||
type = activeConfig[i].type;
|
|
||||||
if (type === "subflow") {
|
|
||||||
subflow = activeConfig[i];
|
|
||||||
subflow.nodes = [];
|
|
||||||
subflow.instances = [];
|
|
||||||
subflows[subflow.id] = subflow;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i=0;i<activeConfig.length;i++) {
|
|
||||||
if (subflows[activeConfig[i].z]) {
|
|
||||||
subflow = subflows[activeConfig[i].z];
|
|
||||||
subflow.nodes.push(activeConfig[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Instantiate each node in the flow
|
|
||||||
for (i=0;i<activeConfig.length;i++) {
|
|
||||||
var nn = null;
|
|
||||||
type = activeConfig[i].type;
|
|
||||||
|
|
||||||
var m = /^subflow:(.+)$/.exec(type);
|
|
||||||
if (!m) {
|
|
||||||
// TODO: remove workspace in next release+1
|
|
||||||
if (type != "workspace" && type != "tab" && type != "subflow" && !subflows[activeConfig[i].z]) {
|
|
||||||
nt = typeRegistry.get(type);
|
|
||||||
if (nt) {
|
|
||||||
try {
|
|
||||||
nn = new nt(activeConfig[i]);
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
util.log("[red] "+type+" : "+err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (nn === null) {
|
|
||||||
util.log("[red] unknown type: "+type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var subflowId = m[1];
|
|
||||||
createSubflow(subflows[subflowId],activeConfig[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Clean up any orphaned credentials
|
|
||||||
credentials.clean(flowNodes.get);
|
|
||||||
events.emit("nodes-started");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops the current activeConfig
|
|
||||||
*/
|
|
||||||
function stopFlows() {
|
|
||||||
if (activeConfig&&activeConfig.length > 0) {
|
|
||||||
util.log("[red] Stopping flows");
|
|
||||||
}
|
|
||||||
return flowNodes.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
var flowNodes = module.exports = {
|
var flowNodes = module.exports = {
|
||||||
init: function(_storage) {
|
init: function(_storage) {
|
||||||
@ -238,13 +54,12 @@ var flowNodes = module.exports = {
|
|||||||
load: function() {
|
load: function() {
|
||||||
return storage.getFlows().then(function(flows) {
|
return storage.getFlows().then(function(flows) {
|
||||||
return credentials.load().then(function() {
|
return credentials.load().then(function() {
|
||||||
activeConfig = flows;
|
activeFlow = new Flow(flows);
|
||||||
if (activeConfig && activeConfig.length > 0) {
|
flowNodes.startFlows();
|
||||||
parseConfig();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}).otherwise(function(err) {
|
}).otherwise(function(err) {
|
||||||
util.log("[red] Error loading flows : "+err);
|
util.log("[red] Error loading flows : "+err);
|
||||||
|
console.log(err.stack);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -253,7 +68,7 @@ var flowNodes = module.exports = {
|
|||||||
* @param n the node to add
|
* @param n the node to add
|
||||||
*/
|
*/
|
||||||
add: function(n) {
|
add: function(n) {
|
||||||
nodes[n.id] = n;
|
//console.log("ADDED NODE:",n.id,n.type,n.name||"");
|
||||||
n.on("log",log.log);
|
n.on("log",log.log);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -263,77 +78,118 @@ var flowNodes = module.exports = {
|
|||||||
* @return the node
|
* @return the node
|
||||||
*/
|
*/
|
||||||
get: function(i) {
|
get: function(i) {
|
||||||
return nodes[i];
|
return activeFlow.getNode(i);
|
||||||
|
},
|
||||||
|
|
||||||
|
eachNode: function(cb) {
|
||||||
|
activeFlow.eachNode(cb);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops all active nodes and clears the active set
|
* Stops all active nodes and clears the active set
|
||||||
* @return a promise for the stopping of all active nodes
|
* @return a promise for the stopping of all active nodes
|
||||||
*/
|
*/
|
||||||
clear: function() {
|
//clear: function(nodesToStop) {
|
||||||
return when.promise(function(resolve) {
|
// var stopList;
|
||||||
events.emit("nodes-stopping");
|
// if (nodesToStop == null) {
|
||||||
var promises = [];
|
// stopList = Object.keys(nodes);
|
||||||
for (var n in nodes) {
|
// } else {
|
||||||
if (nodes.hasOwnProperty(n)) {
|
// stopList = Object.keys(nodesToStop);
|
||||||
try {
|
// }
|
||||||
var p = nodes[n].close();
|
// console.log(stopList);
|
||||||
if (p) {
|
// return when.promise(function(resolve) {
|
||||||
promises.push(p);
|
// events.emit("nodes-stopping");
|
||||||
}
|
// var promises = [];
|
||||||
} catch(err) {
|
// console.log("running nodes:",Object.keys(nodes).length);
|
||||||
nodes[n].error(err);
|
// for (var i=0;i<stopList.length;i++) {
|
||||||
}
|
// var node = nodes[stopList[i]];
|
||||||
}
|
// if (node) {
|
||||||
}
|
// try {
|
||||||
when.settle(promises).then(function() {
|
// var p = node.close();
|
||||||
events.emit("nodes-stopped");
|
// if (p) {
|
||||||
nodes = {};
|
// promises.push(p);
|
||||||
resolve();
|
// }
|
||||||
});
|
// } catch(err) {
|
||||||
});
|
// node.error(err);
|
||||||
},
|
// }
|
||||||
|
// delete nodes[stopList[i]];
|
||||||
/**
|
// delete activeConfigNodes[stopList[i]];
|
||||||
* Provides an iterator over the active set of nodes
|
// }
|
||||||
* @param cb a function to be called for each node in the active set
|
// }
|
||||||
*/
|
// console.log("running nodes:",Object.keys(nodes).length);
|
||||||
each: function(cb) {
|
// when.settle(promises).then(function() {
|
||||||
for (var n in nodes) {
|
// events.emit("nodes-stopped");
|
||||||
if (nodes.hasOwnProperty(n)) {
|
// resolve();
|
||||||
cb(nodes[n]);
|
// });
|
||||||
}
|
// });
|
||||||
}
|
//},
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the active configuration
|
* @return the active configuration
|
||||||
*/
|
*/
|
||||||
getFlows: function() {
|
getFlows: function() {
|
||||||
return activeConfig;
|
return activeFlow.getFlow();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the current active config.
|
* Sets the current active config.
|
||||||
* @param config the configuration to enable
|
* @param config the configuration to enable
|
||||||
|
* @param type the type of deployment to do: full (default), nodes, flows
|
||||||
* @return a promise for the starting of the new flow
|
* @return a promise for the starting of the new flow
|
||||||
*/
|
*/
|
||||||
setFlows: function (config) {
|
setFlows: function (config,type) {
|
||||||
// Extract any credential updates
|
|
||||||
for (var i=0; i<config.length; i++) {
|
type = type||"full";
|
||||||
var node = config[i];
|
|
||||||
|
var credentialsChanged = false;
|
||||||
|
|
||||||
|
var credentialSavePromise = null;
|
||||||
|
|
||||||
|
config.forEach(function(node) {
|
||||||
if (node.credentials) {
|
if (node.credentials) {
|
||||||
credentials.extract(node);
|
credentials.extract(node);
|
||||||
delete node.credentials;
|
credentialsChanged = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (credentialsChanged) {
|
||||||
|
credentialSavePromise = credentials.save();
|
||||||
|
} else {
|
||||||
|
credentialSavePromise = when.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (type=="full") {
|
||||||
|
return credentialSavePromise
|
||||||
|
.then(function() { return storage.saveFlows(config);})
|
||||||
|
.then(function() { return flowNodes.stopFlows(); })
|
||||||
|
.then(function() { activeFlow = new Flow(config); flowNodes.startFlows();});
|
||||||
|
} else {
|
||||||
|
return credentialSavePromise
|
||||||
|
.then(function() { return storage.saveFlows(config);})
|
||||||
|
.then(function() { return activeFlow.applyConfig(config,type); });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
startFlows: function() {
|
||||||
|
util.log("[red] Starting flows");
|
||||||
|
try {
|
||||||
|
activeFlow.start();
|
||||||
|
} catch(err) {
|
||||||
|
var missingTypes = activeFlow.getMissingTypes();
|
||||||
|
if (missingTypes.length > 0) {
|
||||||
|
util.log("[red] Waiting for missing types to be registered:");
|
||||||
|
for (var i=0;i<missingTypes.length;i++) {
|
||||||
|
util.log("[red] - "+missingTypes[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return credentials.save()
|
|
||||||
.then(function() { return storage.saveFlows(config);})
|
|
||||||
.then(function() { return stopFlows();})
|
|
||||||
.then(function () {
|
|
||||||
activeConfig = config;
|
|
||||||
parseConfig();
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
stopFlows: stopFlows
|
stopFlows: function() {
|
||||||
|
util.log("[red] Stopping flows");
|
||||||
|
return activeFlow.stop();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var activeFlow = null;
|
||||||
|
|
||||||
|
@ -39,8 +39,13 @@ function registerType(type,constructor,opts) {
|
|||||||
*/
|
*/
|
||||||
function createNode(node,def) {
|
function createNode(node,def) {
|
||||||
Node.call(node,def);
|
Node.call(node,def);
|
||||||
var creds = credentials.get(node.id);
|
var id = node.id;
|
||||||
|
if (def._alias) {
|
||||||
|
id = def._alias;
|
||||||
|
}
|
||||||
|
var creds = credentials.get(id);
|
||||||
if (creds) {
|
if (creds) {
|
||||||
|
//console.log("Attaching credentials to ",node.id);
|
||||||
node.credentials = creds;
|
node.credentials = creds;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,7 +62,8 @@ function checkTypeInUse(id) {
|
|||||||
throw new Error("Unrecognised id: "+id);
|
throw new Error("Unrecognised id: "+id);
|
||||||
} else {
|
} else {
|
||||||
var inUse = {};
|
var inUse = {};
|
||||||
flows.each(function(n) {
|
var config = flows.getFlows();
|
||||||
|
config.forEach(function(n) {
|
||||||
inUse[n.type] = (inUse[n.type]||0)+1;
|
inUse[n.type] = (inUse[n.type]||0)+1;
|
||||||
});
|
});
|
||||||
var nodesInUse = [];
|
var nodesInUse = [];
|
||||||
|
@ -317,7 +317,11 @@ var registry = (function() {
|
|||||||
},
|
},
|
||||||
|
|
||||||
getNodeConfig: function(id) {
|
getNodeConfig: function(id) {
|
||||||
var config = moduleConfigs[getModule(id)].nodes[getNode(id)];
|
var config = moduleConfigs[getModule(id)];
|
||||||
|
if (!config) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
config = config.nodes[getNode(id)];
|
||||||
if (config) {
|
if (config) {
|
||||||
var result = config.config;
|
var result = config.config;
|
||||||
if (config.script) {
|
if (config.script) {
|
||||||
|
44
red/util.js
44
red/util.js
@ -57,9 +57,51 @@ function cloneMessage(msg) {
|
|||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function compareObjects(obj1,obj2) {
|
||||||
|
if (obj1 === obj2) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj1 == null || obj2 == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!(obj1 instanceof Object) && !(obj2 instanceof Object)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var isArray1 = Array.isArray(obj1);
|
||||||
|
var isArray2 = Array.isArray(obj2);
|
||||||
|
if (isArray1 != isArray2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isArray1 && isArray2) {
|
||||||
|
if (obj1.length != obj2.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (var i=0;i<obj1.length;i++) {
|
||||||
|
if (!compareObjects(obj1[i],obj2[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
var keys1 = Object.keys(obj1);
|
||||||
|
var keys2 = Object.keys(obj2);
|
||||||
|
if (keys1.length != keys2.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (var k in obj1) {
|
||||||
|
if (obj1.hasOwnProperty(k)) {
|
||||||
|
if (!compareObjects(obj1[k],obj2[k])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ensureString: ensureString,
|
ensureString: ensureString,
|
||||||
ensureBuffer: ensureBuffer,
|
ensureBuffer: ensureBuffer,
|
||||||
cloneMessage: cloneMessage
|
cloneMessage: cloneMessage,
|
||||||
|
compareObjects: compareObjects
|
||||||
};
|
};
|
||||||
|
|
||||||
|
12
settings.js
12
settings.js
@ -20,6 +20,18 @@
|
|||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
|
adminAuth: {
|
||||||
|
type: "credentials",
|
||||||
|
users: [ {
|
||||||
|
username: "nol",
|
||||||
|
password: "5f4dcc3b5aa765d61d8327deb882cf99", // password
|
||||||
|
permissions: "*"
|
||||||
|
}],
|
||||||
|
anonymous: {
|
||||||
|
permissions: "read"
|
||||||
|
}
|
||||||
|
},
|
||||||
// the tcp port that the Node-RED web server is listening on
|
// the tcp port that the Node-RED web server is listening on
|
||||||
uiPort: 1880,
|
uiPort: 1880,
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ module.exports = {
|
|||||||
credentials: credentials,
|
credentials: credentials,
|
||||||
|
|
||||||
clearFlows: function() {
|
clearFlows: function() {
|
||||||
return flows.clear();
|
return flows.stopFlows();
|
||||||
},
|
},
|
||||||
|
|
||||||
request: function() {
|
request: function() {
|
||||||
|
@ -72,7 +72,7 @@ describe("flows api", function() {
|
|||||||
});
|
});
|
||||||
it('returns error when set fails', function(done) {
|
it('returns error when set fails', function(done) {
|
||||||
var setFlows = sinon.stub(redNodes,'setFlows', function() {
|
var setFlows = sinon.stub(redNodes,'setFlows', function() {
|
||||||
return when.reject(new Error("test error"));
|
return when.reject(new Error("expected error"));
|
||||||
});
|
});
|
||||||
request(app)
|
request(app)
|
||||||
.post('/flows')
|
.post('/flows')
|
||||||
@ -83,7 +83,7 @@ describe("flows api", function() {
|
|||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
res.text.should.eql("test error");
|
res.text.should.eql("expected error");
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
821
test/red/nodes/Flow_spec.js
Normal file
821
test/red/nodes/Flow_spec.js
Normal file
@ -0,0 +1,821 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2015 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 should = require("should");
|
||||||
|
var sinon = require('sinon');
|
||||||
|
var clone = require('clone');
|
||||||
|
var util = require("util");
|
||||||
|
|
||||||
|
var Flow = require("../../../red/nodes/Flow");
|
||||||
|
var flows = require("../../../red/nodes/flows");
|
||||||
|
var Node = require("../../../red/nodes/Node");
|
||||||
|
|
||||||
|
var typeRegistry = require("../../../red/nodes/registry");
|
||||||
|
var credentials = require("../../../red/nodes/credentials");
|
||||||
|
|
||||||
|
|
||||||
|
describe('Flow', function() {
|
||||||
|
describe('#constructor',function() {
|
||||||
|
it('called with an empty flow',function() {
|
||||||
|
var config = [];
|
||||||
|
var flow = new Flow(config);
|
||||||
|
config.should.eql(flow.getFlow());
|
||||||
|
|
||||||
|
var nodeCount = 0;
|
||||||
|
flow.eachNode(function(node) {
|
||||||
|
nodeCount++;
|
||||||
|
});
|
||||||
|
|
||||||
|
nodeCount.should.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('called with a non-empty flow with no missing types', function() {
|
||||||
|
var getType = sinon.stub(typeRegistry,"get",function(type) {
|
||||||
|
// For this test, don't care what the actual type is, just
|
||||||
|
// that this returns a non-false result
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
var config = [{id:"123",type:"test"}];
|
||||||
|
var flow = new Flow(config);
|
||||||
|
config.should.eql(flow.getFlow());
|
||||||
|
|
||||||
|
flow.getMissingTypes().should.have.length(0);
|
||||||
|
} finally {
|
||||||
|
getType.restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('identifies missing types in a flow', function() {
|
||||||
|
var getType = sinon.stub(typeRegistry,"get",function(type) {
|
||||||
|
if (type == "test") {
|
||||||
|
return {};
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
var config = [{id:"123",type:"test"},{id:"456",type:"test1"},{id:"789",type:"test2"}];
|
||||||
|
var flow = new Flow(config);
|
||||||
|
config.should.eql(flow.getFlow());
|
||||||
|
|
||||||
|
flow.getMissingTypes().should.eql(["test1","test2"]);
|
||||||
|
} finally {
|
||||||
|
getType.restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('extracts node credentials', function() {
|
||||||
|
var getType = sinon.stub(typeRegistry,"get",function(type) {
|
||||||
|
// For this test, don't care what the actual type is, just
|
||||||
|
// that this returns a non-false result
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
var config = [{id:"123",type:"test",credentials:{a:1,b:2}}];
|
||||||
|
var resultingConfig = clone(config);
|
||||||
|
delete resultingConfig[0].credentials;
|
||||||
|
var flow = new Flow(config);
|
||||||
|
flow.getFlow().should.eql(resultingConfig);
|
||||||
|
flow.getMissingTypes().should.have.length(0);
|
||||||
|
} finally {
|
||||||
|
getType.restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
describe('#start',function() {
|
||||||
|
|
||||||
|
it('prevents a flow with missing types from starting', function() {
|
||||||
|
var getType = sinon.stub(typeRegistry,"get",function(type) {
|
||||||
|
if (type == "test") {
|
||||||
|
return {};
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
var config = [{id:"123",type:"test"},{id:"456",type:"test1"},{id:"789",type:"test2"}];
|
||||||
|
var flow = new Flow(config);
|
||||||
|
flow.getMissingTypes().should.have.length(2);
|
||||||
|
|
||||||
|
/*jshint immed: false */
|
||||||
|
(function() {
|
||||||
|
flow.start();
|
||||||
|
}).should.throw();
|
||||||
|
} finally {
|
||||||
|
getType.restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('missing types',function() {
|
||||||
|
it('removes missing types as they are registered', function() {
|
||||||
|
var getType = sinon.stub(typeRegistry,"get",function(type) {
|
||||||
|
if (type == "test") {
|
||||||
|
return {};
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var flowStart;
|
||||||
|
try {
|
||||||
|
var config = [{id:"123",type:"test"},{id:"456",type:"test1"},{id:"789",type:"test2"}];
|
||||||
|
var flow = new Flow(config);
|
||||||
|
|
||||||
|
flowStart = sinon.stub(flow,"start",function() {this.started = true;});
|
||||||
|
config.should.eql(flow.getFlow());
|
||||||
|
|
||||||
|
flow.getMissingTypes().should.eql(["test1","test2"]);
|
||||||
|
|
||||||
|
flow.typeRegistered("test1");
|
||||||
|
flow.getMissingTypes().should.eql(["test2"]);
|
||||||
|
flowStart.called.should.be.false;
|
||||||
|
|
||||||
|
flow.typeRegistered("test2");
|
||||||
|
flow.getMissingTypes().should.eql([]);
|
||||||
|
flowStart.called.should.be.false;
|
||||||
|
} finally {
|
||||||
|
flowStart.restore();
|
||||||
|
getType.restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('starts flows once all missing types are registered', function() {
|
||||||
|
var getType = sinon.stub(typeRegistry,"get",function(type) {
|
||||||
|
if (type == "test") {
|
||||||
|
return {};
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var flowStart;
|
||||||
|
try {
|
||||||
|
var config = [{id:"123",type:"test"},{id:"456",type:"test1"},{id:"789",type:"test2"}];
|
||||||
|
var flow = new Flow(config);
|
||||||
|
|
||||||
|
// First call to .start throws err due to missing types
|
||||||
|
/*jshint immed: false */
|
||||||
|
(function() {
|
||||||
|
flow.start();
|
||||||
|
}).should.throw();
|
||||||
|
|
||||||
|
// Stub .start so when missing types are registered, we don't actually try starting them
|
||||||
|
flowStart = sinon.stub(flow,"start",function() {});
|
||||||
|
config.should.eql(flow.getFlow());
|
||||||
|
|
||||||
|
flow.getMissingTypes().should.eql(["test1","test2"]);
|
||||||
|
|
||||||
|
flow.typeRegistered("test1");
|
||||||
|
flow.typeRegistered("test2");
|
||||||
|
flow.getMissingTypes().should.have.length(0);
|
||||||
|
flowStart.called.should.be.true;
|
||||||
|
} finally {
|
||||||
|
flowStart.restore();
|
||||||
|
getType.restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#diffFlow',function() {
|
||||||
|
var getType;
|
||||||
|
before(function() {
|
||||||
|
getType = sinon.stub(typeRegistry,"get",function(type) {
|
||||||
|
// For this test, don't care what the actual type is, just
|
||||||
|
// that this returns a non-false result
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
after(function() {
|
||||||
|
getType.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles an identical configuration', function() {
|
||||||
|
var config = [{id:"123",type:"test",foo:"a",wires:[]}];
|
||||||
|
var flow = new Flow(config);
|
||||||
|
flow.getMissingTypes().should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flow.diffFlow(config);
|
||||||
|
|
||||||
|
diffResult.should.have.property("deleted",[]);
|
||||||
|
diffResult.should.have.property("changed",[]);
|
||||||
|
diffResult.should.have.property("linked",[]);
|
||||||
|
diffResult.should.have.property("wiringChanged",[]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('identifies nodes with changed properties, including downstream linked', function() {
|
||||||
|
var config = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"b",wires:[[1]]},{id:"3",type:"test",foo:"a",wires:[]}];
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[0].foo = "b";
|
||||||
|
|
||||||
|
var flow = new Flow(config);
|
||||||
|
flow.getMissingTypes().should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flow.diffFlow(newConfig);
|
||||||
|
|
||||||
|
diffResult.should.have.property("deleted",[]);
|
||||||
|
diffResult.should.have.property("changed",["1"]);
|
||||||
|
diffResult.should.have.property("linked",["2"]);
|
||||||
|
diffResult.should.have.property("wiringChanged",[]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('identifies nodes with changed properties, including upstream linked', function() {
|
||||||
|
var config = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"b",wires:[[1]]},{id:"3",type:"test",foo:"a",wires:[]}];
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[1].bar = "c";
|
||||||
|
|
||||||
|
var flow = new Flow(config);
|
||||||
|
flow.getMissingTypes().should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flow.diffFlow(newConfig);
|
||||||
|
|
||||||
|
diffResult.should.have.property("deleted",[]);
|
||||||
|
diffResult.should.have.property("changed",["2"]);
|
||||||
|
diffResult.should.have.property("linked",["1"]);
|
||||||
|
diffResult.should.have.property("wiringChanged",[]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('identifies nodes with changed credentials, including downstream linked', function() {
|
||||||
|
var config = [{id:"1",type:"test",wires:[]},{id:"2",type:"test",bar:"b",wires:[[1]]},{id:"3",type:"test",foo:"a",wires:[]}];
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[0].credentials = {};
|
||||||
|
|
||||||
|
var flow = new Flow(config);
|
||||||
|
flow.getMissingTypes().should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flow.diffFlow(newConfig);
|
||||||
|
|
||||||
|
diffResult.should.have.property("deleted",[]);
|
||||||
|
diffResult.should.have.property("changed",["1"]);
|
||||||
|
diffResult.should.have.property("linked",["2"]);
|
||||||
|
diffResult.should.have.property("wiringChanged",[]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('identifies nodes with changed wiring', function() {
|
||||||
|
var config = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"b",wires:[[1]]},{id:"3",type:"test",foo:"a",wires:[]}];
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[1].wires[0][0] = 2;
|
||||||
|
|
||||||
|
var flow = new Flow(config);
|
||||||
|
flow.getMissingTypes().should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flow.diffFlow(newConfig);
|
||||||
|
|
||||||
|
diffResult.should.have.property("deleted",[]);
|
||||||
|
diffResult.should.have.property("changed",[]);
|
||||||
|
diffResult.should.have.property("linked",["1","2"]);
|
||||||
|
diffResult.should.have.property("wiringChanged",["2"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('identifies nodes with changed wiring - second connection added', function() {
|
||||||
|
var config = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"b",wires:[[1]]},{id:"3",type:"test",foo:"a",wires:[]}];
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[1].wires[0].push([1]);
|
||||||
|
|
||||||
|
var flow = new Flow(config);
|
||||||
|
flow.getMissingTypes().should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flow.diffFlow(newConfig);
|
||||||
|
|
||||||
|
diffResult.should.have.property("deleted",[]);
|
||||||
|
diffResult.should.have.property("changed",[]);
|
||||||
|
diffResult.should.have.property("linked",["1","2"]);
|
||||||
|
diffResult.should.have.property("wiringChanged",["2"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('identifies nodes with changed wiring - second connection added', function() {
|
||||||
|
var config = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"b",wires:[[1]]},{id:"3",type:"test",foo:"a",wires:[]}];
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[1].wires[0].push([1]);
|
||||||
|
|
||||||
|
var flow = new Flow(config);
|
||||||
|
flow.getMissingTypes().should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flow.diffFlow(newConfig);
|
||||||
|
|
||||||
|
diffResult.should.have.property("deleted",[]);
|
||||||
|
diffResult.should.have.property("changed",[]);
|
||||||
|
diffResult.should.have.property("linked",["1","2"]);
|
||||||
|
diffResult.should.have.property("wiringChanged",["2"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('identifies nodes with changed wiring - node connected', function() {
|
||||||
|
var config = [{id:"1",type:"test",foo:"a",wires:[["2"]]},{id:"2",type:"test",bar:"b",wires:[[]]},{id:"3",type:"test",foo:"a",wires:[]}];
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[1].wires.push("3");
|
||||||
|
|
||||||
|
var flow = new Flow(config);
|
||||||
|
flow.getMissingTypes().should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flow.diffFlow(newConfig);
|
||||||
|
|
||||||
|
diffResult.should.have.property("deleted",[]);
|
||||||
|
diffResult.should.have.property("changed",[]);
|
||||||
|
diffResult.should.have.property("linked",["1","2","3"]);
|
||||||
|
diffResult.should.have.property("wiringChanged",["2"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('identifies new nodes', function() {
|
||||||
|
var config = [{id:"1",type:"test",foo:"a",wires:[]},{id:"3",type:"test",foo:"a",wires:[]}];
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig.push({id:"2",type:"test",bar:"b",wires:[[1]]});
|
||||||
|
|
||||||
|
var flow = new Flow(config);
|
||||||
|
flow.getMissingTypes().should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flow.diffFlow(newConfig);
|
||||||
|
|
||||||
|
diffResult.should.have.property("deleted",[]);
|
||||||
|
diffResult.should.have.property("changed",["2"]);
|
||||||
|
diffResult.should.have.property("linked",["1"]);
|
||||||
|
diffResult.should.have.property("wiringChanged",[]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('identifies deleted nodes', function() {
|
||||||
|
var config = [{id:"1",type:"test",foo:"a",wires:[[2]]},{id:"2",type:"test",bar:"b",wires:[[3]]},{id:"3",type:"test",foo:"a",wires:[]}];
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig.splice(1,1);
|
||||||
|
newConfig[0].wires = [];
|
||||||
|
|
||||||
|
var flow = new Flow(config);
|
||||||
|
flow.getMissingTypes().should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flow.diffFlow(newConfig);
|
||||||
|
|
||||||
|
diffResult.should.have.property("deleted",["2"]);
|
||||||
|
diffResult.should.have.property("changed",[]);
|
||||||
|
diffResult.should.have.property("linked",["1","3"]);
|
||||||
|
diffResult.should.have.property("wiringChanged",["1"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('identifies config nodes changes', function() {
|
||||||
|
var config = [{id:"1",type:"test",foo:"configNode",wires:[[2]]},{id:"2",type:"test",bar:"b",wires:[[3]]},{id:"3",type:"test",foo:"a",wires:[]},{id:"configNode",type:"testConfig"}];
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[3].foo = "bar";
|
||||||
|
|
||||||
|
var flow = new Flow(config);
|
||||||
|
flow.getMissingTypes().should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flow.diffFlow(newConfig);
|
||||||
|
|
||||||
|
diffResult.should.have.property("deleted",[]);
|
||||||
|
diffResult.should.have.property("changed",["1","configNode"]);
|
||||||
|
diffResult.should.have.property("linked",["2","3"]);
|
||||||
|
diffResult.should.have.property("wiringChanged",[]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('marks a parent subflow as changed for an internal property change', function() {
|
||||||
|
var config = [
|
||||||
|
{id:"1",type:"test",wires:[[2]]},
|
||||||
|
{id:"2",type:"subflow:sf1",wires:[[3]]},
|
||||||
|
{id:"3",type:"test",wires:[]},
|
||||||
|
{id:"sf1",type:"subflow"},
|
||||||
|
{id:"sf1-1",z:"sf1",type:"test",foo:"a"},
|
||||||
|
{id:"4",type:"subflow:sf1"}
|
||||||
|
];
|
||||||
|
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[4].foo = "b";
|
||||||
|
|
||||||
|
var flow = new Flow(config);
|
||||||
|
flow.getMissingTypes().should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flow.diffFlow(newConfig);
|
||||||
|
|
||||||
|
diffResult.should.have.property("deleted",[]);
|
||||||
|
diffResult.should.have.property("changed",["2","4"]);
|
||||||
|
diffResult.should.have.property("linked",["1","3"]);
|
||||||
|
diffResult.should.have.property("wiringChanged",[]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('marks a parent subflow as changed for an internal wiring change', function() {
|
||||||
|
var config = [
|
||||||
|
{id:"1",type:"test",wires:[[2]]},
|
||||||
|
{id:"2",type:"subflow:sf1",wires:[[3]]},
|
||||||
|
{id:"3",type:"test",wires:[]},
|
||||||
|
{id:"sf1",type:"subflow"},
|
||||||
|
{id:"sf1-1",z:"sf1",type:"test"},
|
||||||
|
{id:"sf1-2",z:"sf1",type:"test"}
|
||||||
|
];
|
||||||
|
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[4].wires = [["sf1-2"]];
|
||||||
|
|
||||||
|
var flow = new Flow(config);
|
||||||
|
flow.getMissingTypes().should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flow.diffFlow(newConfig);
|
||||||
|
|
||||||
|
diffResult.should.have.property("deleted",[]);
|
||||||
|
diffResult.should.have.property("changed",["2"]);
|
||||||
|
diffResult.should.have.property("linked",["1","3"]);
|
||||||
|
diffResult.should.have.property("wiringChanged",[]);
|
||||||
|
});
|
||||||
|
it('marks a parent subflow as changed for an internal node delete', function() {
|
||||||
|
var config = [
|
||||||
|
{id:"1",type:"test",wires:[[2]]},
|
||||||
|
{id:"2",type:"subflow:sf1",wires:[[3]]},
|
||||||
|
{id:"3",type:"test",wires:[]},
|
||||||
|
{id:"sf1",type:"subflow"},
|
||||||
|
{id:"sf1-1",z:"sf1",type:"test"},
|
||||||
|
{id:"sf1-2",z:"sf1",type:"test"}
|
||||||
|
];
|
||||||
|
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig.splice(5,1);
|
||||||
|
|
||||||
|
var flow = new Flow(config);
|
||||||
|
flow.getMissingTypes().should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flow.diffFlow(newConfig);
|
||||||
|
|
||||||
|
diffResult.should.have.property("deleted",[]);
|
||||||
|
diffResult.should.have.property("changed",["2"]);
|
||||||
|
diffResult.should.have.property("linked",["1","3"]);
|
||||||
|
diffResult.should.have.property("wiringChanged",[]);
|
||||||
|
});
|
||||||
|
it('marks a parent subflow as changed for an internal subflow wiring change - input removed', function() {
|
||||||
|
var config = [
|
||||||
|
{id:"1",type:"test",wires:[[2]]},
|
||||||
|
{id:"2",type:"subflow:sf1",wires:[[3]]},
|
||||||
|
{id:"3",type:"test",wires:[]},
|
||||||
|
{id:"sf1",type:"subflow","in": [{"wires": [{"id": "sf1-1"}]}],"out": [{"wires": [{"id": "sf1-2","port": 0}]}]},
|
||||||
|
{id:"sf1-1",z:"sf1",type:"test"},
|
||||||
|
{id:"sf1-2",z:"sf1",type:"test"}
|
||||||
|
];
|
||||||
|
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[3].in[0].wires = [];
|
||||||
|
|
||||||
|
var flow = new Flow(config);
|
||||||
|
flow.getMissingTypes().should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flow.diffFlow(newConfig);
|
||||||
|
|
||||||
|
diffResult.should.have.property("deleted",[]);
|
||||||
|
diffResult.should.have.property("changed",["2"]);
|
||||||
|
diffResult.should.have.property("linked",["1","3"]);
|
||||||
|
diffResult.should.have.property("wiringChanged",[]);
|
||||||
|
});
|
||||||
|
it('marks a parent subflow as changed for an internal subflow wiring change - input added', function() {
|
||||||
|
var config = [
|
||||||
|
{id:"1",type:"test",wires:[[2]]},
|
||||||
|
{id:"2",type:"subflow:sf1",wires:[[3]]},
|
||||||
|
{id:"3",type:"test",wires:[]},
|
||||||
|
{id:"sf1",type:"subflow","in": [{"wires": [{"id": "sf1-1"}]}],"out": [{"wires": [{"id": "sf1-2","port": 0}]}]},
|
||||||
|
{id:"sf1-1",z:"sf1",type:"test"},
|
||||||
|
{id:"sf1-2",z:"sf1",type:"test"}
|
||||||
|
];
|
||||||
|
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[3].in[0].wires.push({"id":"sf1-2"});
|
||||||
|
|
||||||
|
var flow = new Flow(config);
|
||||||
|
flow.getMissingTypes().should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flow.diffFlow(newConfig);
|
||||||
|
|
||||||
|
diffResult.should.have.property("deleted",[]);
|
||||||
|
diffResult.should.have.property("changed",["2"]);
|
||||||
|
diffResult.should.have.property("linked",["1","3"]);
|
||||||
|
diffResult.should.have.property("wiringChanged",[]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('marks a parent subflow as changed for an internal subflow wiring change - output added', function() {
|
||||||
|
var config = [
|
||||||
|
{id:"1",type:"test",wires:[[2]]},
|
||||||
|
{id:"2",type:"subflow:sf1",wires:[[3]]},
|
||||||
|
{id:"3",type:"test",wires:[]},
|
||||||
|
{id:"sf1",type:"subflow","in": [{"wires": [{"id": "sf1-1"}]}],"out": [{"wires": [{"id": "sf1-2","port": 0}]}]},
|
||||||
|
{id:"sf1-1",z:"sf1",type:"test"},
|
||||||
|
{id:"sf1-2",z:"sf1",type:"test"}
|
||||||
|
];
|
||||||
|
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[3].out[0].wires.push({"id":"sf1-2","port":0});
|
||||||
|
|
||||||
|
var flow = new Flow(config);
|
||||||
|
flow.getMissingTypes().should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flow.diffFlow(newConfig);
|
||||||
|
|
||||||
|
diffResult.should.have.property("deleted",[]);
|
||||||
|
diffResult.should.have.property("changed",["2"]);
|
||||||
|
diffResult.should.have.property("linked",["1","3"]);
|
||||||
|
diffResult.should.have.property("wiringChanged",[]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('marks a parent subflow as changed for an internal subflow wiring change - output removed', function() {
|
||||||
|
var config = [
|
||||||
|
{id:"1",type:"test",wires:[[2]]},
|
||||||
|
{id:"2",type:"subflow:sf1",wires:[[3]]},
|
||||||
|
{id:"3",type:"test",wires:[]},
|
||||||
|
{id:"sf1",type:"subflow","in": [{"wires": [{"id": "sf1-1"}]}],"out": [{"wires": [{"id": "sf1-2","port": 0}]}]},
|
||||||
|
{id:"sf1-1",z:"sf1",type:"test"},
|
||||||
|
{id:"sf1-2",z:"sf1",type:"test"}
|
||||||
|
];
|
||||||
|
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[3].out[0].wires = [];
|
||||||
|
|
||||||
|
var flow = new Flow(config);
|
||||||
|
flow.getMissingTypes().should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flow.diffFlow(newConfig);
|
||||||
|
|
||||||
|
diffResult.should.have.property("deleted",[]);
|
||||||
|
diffResult.should.have.property("changed",["2"]);
|
||||||
|
diffResult.should.have.property("linked",["1","3"]);
|
||||||
|
diffResult.should.have.property("wiringChanged",[]);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#applyConfig',function() {
|
||||||
|
var getType;
|
||||||
|
var getNode;
|
||||||
|
var flowsAdd;
|
||||||
|
var credentialsClean;
|
||||||
|
|
||||||
|
var stoppedNodes = {};
|
||||||
|
var currentNodes = {};
|
||||||
|
|
||||||
|
var TestNode = function(n) {
|
||||||
|
Node.call(this,n);
|
||||||
|
var node = this;
|
||||||
|
this.handled = 0;
|
||||||
|
this.stopped = false;
|
||||||
|
this.on('input',function(msg) {
|
||||||
|
node.handled++;
|
||||||
|
node.send(msg);
|
||||||
|
});
|
||||||
|
this.on('close',function() {
|
||||||
|
node.stopped = true;
|
||||||
|
stoppedNodes[node.id] = node;
|
||||||
|
delete currentNodes[node.id];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
util.inherits(TestNode,Node);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
before(function() {
|
||||||
|
flowsAdd = sinon.stub(flows,"add",function(node) {
|
||||||
|
currentNodes[node.id] = node;
|
||||||
|
});
|
||||||
|
getNode = sinon.stub(flows,"get",function(id) {
|
||||||
|
return currentNodes[id];
|
||||||
|
});
|
||||||
|
getType = sinon.stub(typeRegistry,"get",function(type) {
|
||||||
|
return TestNode;
|
||||||
|
});
|
||||||
|
credentialsClean = sinon.stub(credentials,"clean",function(config){});
|
||||||
|
|
||||||
|
});
|
||||||
|
after(function() {
|
||||||
|
getType.restore();
|
||||||
|
flowsAdd.restore();
|
||||||
|
credentialsClean.restore();
|
||||||
|
getNode.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
currentNodes = {};
|
||||||
|
stoppedNodes = {};
|
||||||
|
});
|
||||||
|
|
||||||
|
it("instantiates an initial configuration and stops it",function(done) {
|
||||||
|
var config = [{id:"1",type:"test",foo:"a",wires:["2"]},{id:"2",type:"test",bar:"b",wires:[["3"]]},{id:"3",type:"test",foo:"a",wires:[]}];
|
||||||
|
|
||||||
|
var flow = new Flow(config);
|
||||||
|
flow.start();
|
||||||
|
|
||||||
|
currentNodes.should.have.a.property("1");
|
||||||
|
currentNodes.should.have.a.property("2");
|
||||||
|
currentNodes.should.have.a.property("3");
|
||||||
|
|
||||||
|
currentNodes["1"].should.have.a.property("handled",0);
|
||||||
|
currentNodes["2"].should.have.a.property("handled",0);
|
||||||
|
currentNodes["3"].should.have.a.property("handled",0);
|
||||||
|
|
||||||
|
|
||||||
|
currentNodes["1"].receive({payload:"test"});
|
||||||
|
|
||||||
|
currentNodes["1"].should.have.a.property("handled",1);
|
||||||
|
currentNodes["2"].should.have.a.property("handled",1);
|
||||||
|
currentNodes["3"].should.have.a.property("handled",1);
|
||||||
|
|
||||||
|
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");
|
||||||
|
stoppedNodes.should.have.a.property("1");
|
||||||
|
stoppedNodes.should.have.a.property("2");
|
||||||
|
stoppedNodes.should.have.a.property("3");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("stops all nodes on new full deploy",function(done) {
|
||||||
|
var config = [{id:"1",type:"test",foo:"a",wires:["2"]},{id:"2",type:"test",bar:"b",wires:[["3"]]},{id:"3",type:"test",foo:"a",wires:[]}];
|
||||||
|
var newConfig = [{id:"4",type:"test",foo:"a",wires:["5"]},{id:"5",type:"test",bar:"b",wires:[["6"]]},{id:"6",type:"test",foo:"a",wires:[]}];
|
||||||
|
|
||||||
|
var flow = new Flow(config);
|
||||||
|
flow.start();
|
||||||
|
|
||||||
|
currentNodes.should.have.a.property("1");
|
||||||
|
currentNodes.should.have.a.property("2");
|
||||||
|
currentNodes.should.have.a.property("3");
|
||||||
|
|
||||||
|
flow.applyConfig(newConfig).then(function() {
|
||||||
|
currentNodes.should.not.have.a.property("1");
|
||||||
|
currentNodes.should.not.have.a.property("2");
|
||||||
|
currentNodes.should.not.have.a.property("3");
|
||||||
|
stoppedNodes.should.have.a.property("1");
|
||||||
|
stoppedNodes.should.have.a.property("2");
|
||||||
|
stoppedNodes.should.have.a.property("3");
|
||||||
|
|
||||||
|
currentNodes.should.have.a.property("4");
|
||||||
|
currentNodes.should.have.a.property("5");
|
||||||
|
currentNodes.should.have.a.property("6");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("stops only modified nodes on 'nodes' deploy",function(done) {
|
||||||
|
var config = [{id:"1",type:"test",name:"a",wires:["2"]},{id:"2",type:"test",name:"b",wires:[["3"]]},{id:"3",type:"test",name:"c",wires:[]}];
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[1].name = "B";
|
||||||
|
|
||||||
|
var flow = new Flow(config);
|
||||||
|
flow.start();
|
||||||
|
|
||||||
|
currentNodes.should.have.a.property("1");
|
||||||
|
currentNodes.should.have.a.property("2");
|
||||||
|
currentNodes.should.have.a.property("3");
|
||||||
|
currentNodes["2"].should.have.a.property("name","b");
|
||||||
|
|
||||||
|
currentNodes["1"].receive({payload:"test"});
|
||||||
|
currentNodes["1"].should.have.a.property("handled",1);
|
||||||
|
currentNodes["2"].should.have.a.property("handled",1);
|
||||||
|
currentNodes["3"].should.have.a.property("handled",1);
|
||||||
|
|
||||||
|
|
||||||
|
flow.applyConfig(newConfig,"nodes").then(function() {
|
||||||
|
currentNodes.should.have.a.property("1");
|
||||||
|
currentNodes.should.have.a.property("2");
|
||||||
|
currentNodes.should.have.a.property("3");
|
||||||
|
currentNodes["2"].should.have.a.property("name","B");
|
||||||
|
|
||||||
|
stoppedNodes.should.not.have.a.property("1");
|
||||||
|
stoppedNodes.should.have.a.property("2");
|
||||||
|
stoppedNodes.should.not.have.a.property("3");
|
||||||
|
stoppedNodes["2"].should.have.a.property("name","b");
|
||||||
|
|
||||||
|
|
||||||
|
currentNodes["1"].receive({payload:"test"});
|
||||||
|
currentNodes["1"].should.have.a.property("handled",2);
|
||||||
|
currentNodes["2"].should.have.a.property("handled",1);
|
||||||
|
currentNodes["3"].should.have.a.property("handled",2);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("stops only modified flows on 'flows' deploy",function(done) {
|
||||||
|
var config = [{id:"1",type:"test",name:"a",wires:["2"]},{id:"2",type:"test",name:"b",wires:[[]]},{id:"3",type:"test",name:"c",wires:[]}];
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[1].name = "B";
|
||||||
|
|
||||||
|
var flow = new Flow(config);
|
||||||
|
flow.start();
|
||||||
|
|
||||||
|
currentNodes.should.have.a.property("1");
|
||||||
|
currentNodes.should.have.a.property("2");
|
||||||
|
currentNodes.should.have.a.property("3");
|
||||||
|
currentNodes["2"].should.have.a.property("name","b");
|
||||||
|
|
||||||
|
currentNodes["1"].receive({payload:"test"});
|
||||||
|
currentNodes["1"].should.have.a.property("handled",1);
|
||||||
|
currentNodes["2"].should.have.a.property("handled",1);
|
||||||
|
currentNodes["3"].should.have.a.property("handled",0);
|
||||||
|
|
||||||
|
currentNodes["3"].receive({payload:"test"});
|
||||||
|
currentNodes["3"].should.have.a.property("handled",1);
|
||||||
|
|
||||||
|
flow.applyConfig(newConfig,"flows").then(function() {
|
||||||
|
currentNodes.should.have.a.property("1");
|
||||||
|
currentNodes.should.have.a.property("2");
|
||||||
|
currentNodes.should.have.a.property("3");
|
||||||
|
currentNodes["2"].should.have.a.property("name","B");
|
||||||
|
|
||||||
|
stoppedNodes.should.have.a.property("1");
|
||||||
|
stoppedNodes.should.have.a.property("2");
|
||||||
|
stoppedNodes.should.not.have.a.property("3");
|
||||||
|
|
||||||
|
stoppedNodes["2"].should.have.a.property("name","b");
|
||||||
|
|
||||||
|
currentNodes["1"].receive({payload:"test"});
|
||||||
|
currentNodes["1"].should.have.a.property("handled",1);
|
||||||
|
currentNodes["2"].should.have.a.property("handled",1);
|
||||||
|
|
||||||
|
currentNodes["3"].receive({payload:"test"});
|
||||||
|
currentNodes["3"].should.have.a.property("handled",2);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rewires otherwise unmodified nodes on 'nodes' deploy",function(done) {
|
||||||
|
var config = [{id:"1",type:"test",name:"a",wires:["2"]},{id:"2",type:"test",name:"b",wires:[[]]},{id:"3",type:"test",name:"c",wires:[]}];
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[1].wires[0].push("3");
|
||||||
|
|
||||||
|
var flow = new Flow(config);
|
||||||
|
flow.start();
|
||||||
|
|
||||||
|
currentNodes.should.have.a.property("1");
|
||||||
|
currentNodes.should.have.a.property("2");
|
||||||
|
currentNodes.should.have.a.property("3");
|
||||||
|
|
||||||
|
currentNodes["1"].receive({payload:"test"});
|
||||||
|
currentNodes["1"].should.have.a.property("handled",1);
|
||||||
|
currentNodes["2"].should.have.a.property("handled",1);
|
||||||
|
currentNodes["3"].should.have.a.property("handled",0);
|
||||||
|
|
||||||
|
flow.applyConfig(newConfig,"nodes").then(function() {
|
||||||
|
currentNodes.should.have.a.property("1");
|
||||||
|
currentNodes.should.have.a.property("2");
|
||||||
|
currentNodes.should.have.a.property("3");
|
||||||
|
|
||||||
|
stoppedNodes.should.not.have.a.property("1");
|
||||||
|
stoppedNodes.should.not.have.a.property("2");
|
||||||
|
stoppedNodes.should.not.have.a.property("3");
|
||||||
|
|
||||||
|
currentNodes["1"].receive({payload:"test"});
|
||||||
|
currentNodes["1"].should.have.a.property("handled",2);
|
||||||
|
currentNodes["2"].should.have.a.property("handled",2);
|
||||||
|
currentNodes["3"].should.have.a.property("handled",1);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("stops rewired but otherwise unmodified nodes on 'flows' deploy",function(done) {
|
||||||
|
var config = [{id:"1",type:"test",name:"a",wires:["2"]},{id:"2",type:"test",name:"b",wires:[[]]},{id:"3",type:"test",name:"c",wires:[]}];
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[1].wires[0].push("3");
|
||||||
|
|
||||||
|
var flow = new Flow(config);
|
||||||
|
flow.start();
|
||||||
|
|
||||||
|
currentNodes.should.have.a.property("1");
|
||||||
|
currentNodes.should.have.a.property("2");
|
||||||
|
currentNodes.should.have.a.property("3");
|
||||||
|
|
||||||
|
currentNodes["1"].receive({payload:"test"});
|
||||||
|
currentNodes["1"].should.have.a.property("handled",1);
|
||||||
|
currentNodes["2"].should.have.a.property("handled",1);
|
||||||
|
currentNodes["3"].should.have.a.property("handled",0);
|
||||||
|
|
||||||
|
flow.applyConfig(newConfig,"flows").then(function() {
|
||||||
|
currentNodes.should.have.a.property("1");
|
||||||
|
currentNodes.should.have.a.property("2");
|
||||||
|
currentNodes.should.have.a.property("3");
|
||||||
|
|
||||||
|
stoppedNodes.should.have.a.property("1");
|
||||||
|
stoppedNodes.should.have.a.property("2");
|
||||||
|
stoppedNodes.should.have.a.property("3");
|
||||||
|
|
||||||
|
currentNodes["1"].receive({payload:"test"});
|
||||||
|
currentNodes["1"].should.have.a.property("handled",1);
|
||||||
|
currentNodes["2"].should.have.a.property("handled",1);
|
||||||
|
currentNodes["3"].should.have.a.property("handled",1);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
@ -17,6 +17,8 @@
|
|||||||
var should = require("should");
|
var should = require("should");
|
||||||
var sinon = require('sinon');
|
var sinon = require('sinon');
|
||||||
var RedNode = require("../../../red/nodes/Node");
|
var RedNode = require("../../../red/nodes/Node");
|
||||||
|
var flows = require("../../../red/nodes/flows");
|
||||||
|
|
||||||
var comms = require('../../../red/comms');
|
var comms = require('../../../red/comms');
|
||||||
|
|
||||||
describe('Node', function() {
|
describe('Node', function() {
|
||||||
@ -90,12 +92,16 @@ describe('Node', function() {
|
|||||||
it('emits a single message', function(done) {
|
it('emits a single message', function(done) {
|
||||||
var n1 = new RedNode({id:'n1',type:'abc',wires:[['n2']]});
|
var n1 = new RedNode({id:'n1',type:'abc',wires:[['n2']]});
|
||||||
var n2 = new RedNode({id:'n2',type:'abc'});
|
var n2 = new RedNode({id:'n2',type:'abc'});
|
||||||
|
var flowGet = sinon.stub(flows,"get",function(id) {
|
||||||
|
return {'n1':n1,'n2':n2}[id];
|
||||||
|
});
|
||||||
var message = {payload:"hello world"};
|
var message = {payload:"hello world"};
|
||||||
|
|
||||||
n2.on('input',function(msg) {
|
n2.on('input',function(msg) {
|
||||||
// msg equals message, and is not a new copy
|
// msg equals message, and is not a new copy
|
||||||
should.deepEqual(msg,message);
|
should.deepEqual(msg,message);
|
||||||
should.strictEqual(msg,message);
|
should.strictEqual(msg,message);
|
||||||
|
flowGet.restore();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -105,6 +111,9 @@ describe('Node', function() {
|
|||||||
it('emits multiple messages on a single output', function(done) {
|
it('emits multiple messages on a single output', function(done) {
|
||||||
var n1 = new RedNode({id:'n1',type:'abc',wires:[['n2']]});
|
var n1 = new RedNode({id:'n1',type:'abc',wires:[['n2']]});
|
||||||
var n2 = new RedNode({id:'n2',type:'abc'});
|
var n2 = new RedNode({id:'n2',type:'abc'});
|
||||||
|
var flowGet = sinon.stub(flows,"get",function(id) {
|
||||||
|
return {'n1':n1,'n2':n2}[id];
|
||||||
|
});
|
||||||
|
|
||||||
var messages = [
|
var messages = [
|
||||||
{payload:"hello world"},
|
{payload:"hello world"},
|
||||||
@ -126,6 +135,7 @@ describe('Node', function() {
|
|||||||
|
|
||||||
rcvdCount += 1;
|
rcvdCount += 1;
|
||||||
if (rcvdCount === 2) {
|
if (rcvdCount === 2) {
|
||||||
|
flowGet.restore();
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -138,6 +148,9 @@ describe('Node', function() {
|
|||||||
var n3 = new RedNode({id:'n3',type:'abc'});
|
var n3 = new RedNode({id:'n3',type:'abc'});
|
||||||
var n4 = new RedNode({id:'n4',type:'abc'});
|
var n4 = new RedNode({id:'n4',type:'abc'});
|
||||||
var n5 = new RedNode({id:'n5',type:'abc'});
|
var n5 = new RedNode({id:'n5',type:'abc'});
|
||||||
|
var flowGet = sinon.stub(flows,"get",function(id) {
|
||||||
|
return {'n1':n1,'n2':n2,'n3':n3,'n4':n4,'n5':n5}[id];
|
||||||
|
});
|
||||||
|
|
||||||
var messages = [
|
var messages = [
|
||||||
{payload:"hello world"},
|
{payload:"hello world"},
|
||||||
@ -153,6 +166,7 @@ describe('Node', function() {
|
|||||||
should.strictEqual(msg,messages[0]);
|
should.strictEqual(msg,messages[0]);
|
||||||
rcvdCount += 1;
|
rcvdCount += 1;
|
||||||
if (rcvdCount == 3) {
|
if (rcvdCount == 3) {
|
||||||
|
flowGet.restore();
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -167,6 +181,7 @@ describe('Node', function() {
|
|||||||
should.notStrictEqual(msg,messages[2]);
|
should.notStrictEqual(msg,messages[2]);
|
||||||
rcvdCount += 1;
|
rcvdCount += 1;
|
||||||
if (rcvdCount == 3) {
|
if (rcvdCount == 3) {
|
||||||
|
flowGet.restore();
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -177,6 +192,7 @@ describe('Node', function() {
|
|||||||
should.notStrictEqual(msg,messages[2]);
|
should.notStrictEqual(msg,messages[2]);
|
||||||
rcvdCount += 1;
|
rcvdCount += 1;
|
||||||
if (rcvdCount == 3) {
|
if (rcvdCount == 3) {
|
||||||
|
flowGet.restore();
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -187,12 +203,16 @@ describe('Node', function() {
|
|||||||
it('emits no messages', function(done) {
|
it('emits no messages', function(done) {
|
||||||
var n1 = new RedNode({id:'n1',type:'abc',wires:[['n2']]});
|
var n1 = new RedNode({id:'n1',type:'abc',wires:[['n2']]});
|
||||||
var n2 = new RedNode({id:'n2',type:'abc'});
|
var n2 = new RedNode({id:'n2',type:'abc'});
|
||||||
|
var flowGet = sinon.stub(flows,"get",function(id) {
|
||||||
|
return {'n1':n1,'n2':n2}[id];
|
||||||
|
});
|
||||||
|
|
||||||
n2.on('input',function(msg) {
|
n2.on('input',function(msg) {
|
||||||
should.fail(null,null,"unexpected message");
|
should.fail(null,null,"unexpected message");
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
|
flowGet.restore();
|
||||||
done();
|
done();
|
||||||
}, 200);
|
}, 200);
|
||||||
|
|
||||||
@ -202,7 +222,10 @@ describe('Node', function() {
|
|||||||
it('emits messages ignoring non-existent nodes', function(done) {
|
it('emits messages ignoring non-existent nodes', function(done) {
|
||||||
var n1 = new RedNode({id:'n1',type:'abc',wires:[['n9'],['n2']]});
|
var n1 = new RedNode({id:'n1',type:'abc',wires:[['n9'],['n2']]});
|
||||||
var n2 = new RedNode({id:'n2',type:'abc'});
|
var n2 = new RedNode({id:'n2',type:'abc'});
|
||||||
|
var flowGet = sinon.stub(flows,"get",function(id) {
|
||||||
|
return {'n1':n1,'n2':n2}[id];
|
||||||
|
});
|
||||||
|
|
||||||
var messages = [
|
var messages = [
|
||||||
{payload:"hello world"},
|
{payload:"hello world"},
|
||||||
{payload:"hello world again"}
|
{payload:"hello world again"}
|
||||||
@ -212,6 +235,7 @@ describe('Node', function() {
|
|||||||
n2.on('input',function(msg) {
|
n2.on('input',function(msg) {
|
||||||
should.deepEqual(msg,messages[1]);
|
should.deepEqual(msg,messages[1]);
|
||||||
should.strictEqual(msg,messages[1]);
|
should.strictEqual(msg,messages[1]);
|
||||||
|
flowGet.restore();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -222,6 +246,9 @@ describe('Node', function() {
|
|||||||
var n1 = new RedNode({id:'n1',type:'abc',wires:[['n2'],['n3']]});
|
var n1 = new RedNode({id:'n1',type:'abc',wires:[['n2'],['n3']]});
|
||||||
var n2 = new RedNode({id:'n2',type:'abc'});
|
var n2 = new RedNode({id:'n2',type:'abc'});
|
||||||
var n3 = new RedNode({id:'n3',type:'abc'});
|
var n3 = new RedNode({id:'n3',type:'abc'});
|
||||||
|
var flowGet = sinon.stub(flows,"get",function(id) {
|
||||||
|
return {'n1':n1,'n2':n2,'n3':n3}[id];
|
||||||
|
});
|
||||||
|
|
||||||
var req = {};
|
var req = {};
|
||||||
var res = {};
|
var res = {};
|
||||||
@ -234,6 +261,7 @@ describe('Node', function() {
|
|||||||
msg.cloned.should.be.exactly(message.cloned);
|
msg.cloned.should.be.exactly(message.cloned);
|
||||||
msg.req.should.be.exactly(message.req);
|
msg.req.should.be.exactly(message.req);
|
||||||
msg.res.should.be.exactly(message.res);
|
msg.res.should.be.exactly(message.res);
|
||||||
|
flowGet.restore();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -243,6 +271,7 @@ describe('Node', function() {
|
|||||||
msg.cloned.should.not.be.exactly(message.cloned);
|
msg.cloned.should.not.be.exactly(message.cloned);
|
||||||
msg.req.should.be.exactly(message.req);
|
msg.req.should.be.exactly(message.req);
|
||||||
msg.res.should.be.exactly(message.res);
|
msg.res.should.be.exactly(message.res);
|
||||||
|
flowGet.restore();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -112,9 +112,7 @@ describe('Credentials', function() {
|
|||||||
credentials.init(storage);
|
credentials.init(storage);
|
||||||
credentials.load().then(function() {
|
credentials.load().then(function() {
|
||||||
should.exist(credentials.get("a"));
|
should.exist(credentials.get("a"));
|
||||||
credentials.clean(function() {
|
credentials.clean([]);
|
||||||
return false;
|
|
||||||
});
|
|
||||||
storage.saveCredentials.callCount.should.be.exactly(1);
|
storage.saveCredentials.callCount.should.be.exactly(1);
|
||||||
should.not.exist(credentials.get("a"));
|
should.not.exist(credentials.get("a"));
|
||||||
storage.saveCredentials.restore();
|
storage.saveCredentials.restore();
|
||||||
@ -211,287 +209,287 @@ describe('Credentials', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('extract and store credential updates in the provided node', function() {
|
//describe('extract and store credential updates in the provided node', function() {
|
||||||
var path = require('path');
|
// var path = require('path');
|
||||||
var fs = require('fs-extra');
|
// var fs = require('fs-extra');
|
||||||
var http = require('http');
|
// var http = require('http');
|
||||||
var express = require('express');
|
// var express = require('express');
|
||||||
var server = require("../../../red/server");
|
// var server = require("../../../red/server");
|
||||||
var localfilesystem = require("../../../red/storage/localfilesystem");
|
// var localfilesystem = require("../../../red/storage/localfilesystem");
|
||||||
var app = express();
|
// var app = express();
|
||||||
var RED = require("../../../red/red.js");
|
// var RED = require("../../../red/red.js");
|
||||||
|
//
|
||||||
var userDir = path.join(__dirname,".testUserHome");
|
// var userDir = path.join(__dirname,".testUserHome");
|
||||||
before(function(done) {
|
// before(function(done) {
|
||||||
fs.remove(userDir,function(err) {
|
// fs.remove(userDir,function(err) {
|
||||||
fs.mkdir(userDir,function() {
|
// fs.mkdir(userDir,function() {
|
||||||
sinon.stub(index, 'load', function() {
|
// sinon.stub(index, 'load', function() {
|
||||||
return when.promise(function(resolve,reject){
|
// return when.promise(function(resolve,reject){
|
||||||
resolve([]);
|
// resolve([]);
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
sinon.stub(localfilesystem, 'getCredentials', function() {
|
// sinon.stub(localfilesystem, 'getCredentials', function() {
|
||||||
return when.promise(function(resolve,reject) {
|
// return when.promise(function(resolve,reject) {
|
||||||
resolve({"tab1":{"foo": 2, "pswd":'sticks'}});
|
// resolve({"tab1":{"foo": 2, "pswd":'sticks'}});
|
||||||
});
|
// });
|
||||||
}) ;
|
// }) ;
|
||||||
RED.init(http.createServer(function(req,res){app(req,res)}),
|
// RED.init(http.createServer(function(req,res){app(req,res)}),
|
||||||
{userDir: userDir});
|
// {userDir: userDir});
|
||||||
server.start().then(function () {
|
// server.start().then(function () {
|
||||||
done();
|
// done();
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
//
|
||||||
after(function(done) {
|
// after(function(done) {
|
||||||
fs.remove(userDir,done);
|
// fs.remove(userDir,done);
|
||||||
server.stop();
|
// server.stop();
|
||||||
index.load.restore();
|
// index.load.restore();
|
||||||
localfilesystem.getCredentials.restore();
|
// localfilesystem.getCredentials.restore();
|
||||||
});
|
// });
|
||||||
|
//
|
||||||
function TestNode(n) {
|
// function TestNode(n) {
|
||||||
index.createNode(this, n);
|
// index.createNode(this, n);
|
||||||
var node = this;
|
// var node = this;
|
||||||
this.on("log", function() {
|
// this.on("log", function() {
|
||||||
// do nothing
|
// // do nothing
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
it(': credential updated with good value', function(done) {
|
// it(': credential updated with good value', function(done) {
|
||||||
index.registerType('test', TestNode, {
|
// index.registerType('test', TestNode, {
|
||||||
credentials: {
|
// credentials: {
|
||||||
foo: {type:"test"}
|
// foo: {type:"test"}
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
index.loadFlows().then(function() {
|
// index.loadFlows().then(function() {
|
||||||
var testnode = new TestNode({id:'tab1',type:'test',name:'barney'});
|
// var testnode = new TestNode({id:'tab1',type:'test',name:'barney'});
|
||||||
credentials.extract(testnode);
|
// credentials.extract(testnode);
|
||||||
should.exist(credentials.get('tab1'));
|
// should.exist(credentials.get('tab1'));
|
||||||
credentials.get('tab1').should.have.property('foo',2);
|
// credentials.get('tab1').should.have.property('foo',2);
|
||||||
|
//
|
||||||
// set credentials to be an updated value and checking this is extracted properly
|
// // set credentials to be an updated value and checking this is extracted properly
|
||||||
testnode.credentials = {"foo": 3};
|
// testnode.credentials = {"foo": 3};
|
||||||
credentials.extract(testnode);
|
// credentials.extract(testnode);
|
||||||
should.exist(credentials.get('tab1'));
|
// should.exist(credentials.get('tab1'));
|
||||||
credentials.get('tab1').should.not.have.property('foo',2);
|
// credentials.get('tab1').should.not.have.property('foo',2);
|
||||||
credentials.get('tab1').should.have.property('foo',3);
|
// credentials.get('tab1').should.have.property('foo',3);
|
||||||
done();
|
// done();
|
||||||
}).otherwise(function(err){
|
// }).otherwise(function(err){
|
||||||
done(err);
|
// done(err);
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
//
|
||||||
|
// it(': credential updated with empty value', function(done) {
|
||||||
|
// index.registerType('test', TestNode, {
|
||||||
|
// credentials: {
|
||||||
|
// foo: {type:"test"}
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// index.loadFlows().then(function() {
|
||||||
|
// var testnode = new TestNode({id:'tab1',type:'test',name:'barney'});
|
||||||
|
// // setting value of "foo" credential to be empty removes foo as a property
|
||||||
|
// testnode.credentials = {"foo": ''};
|
||||||
|
// credentials.extract(testnode);
|
||||||
|
// should.exist(credentials.get('tab1'));
|
||||||
|
// credentials.get('tab1').should.not.have.property('foo',2);
|
||||||
|
// credentials.get('tab1').should.not.have.property('foo');
|
||||||
|
// done();
|
||||||
|
// }).otherwise(function(err){
|
||||||
|
// done(err);
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// it(': undefined credential updated', function(done) {
|
||||||
|
// index.registerType('test', TestNode, {
|
||||||
|
// credentials: {
|
||||||
|
// foo: {type:"test"}
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// index.loadFlows().then(function() {
|
||||||
|
// var testnode = new TestNode({id:'tab1',type:'test',name:'barney'});
|
||||||
|
// // setting value of an undefined credential should not change anything
|
||||||
|
// testnode.credentials = {"bar": 4};
|
||||||
|
// credentials.extract(testnode);
|
||||||
|
// should.exist(credentials.get('tab1'));
|
||||||
|
// credentials.get('tab1').should.have.property('foo',2);
|
||||||
|
// credentials.get('tab1').should.not.have.property('bar');
|
||||||
|
// done();
|
||||||
|
// }).otherwise(function(err){
|
||||||
|
// done(err);
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// it(': password credential updated', function(done) {
|
||||||
|
// index.registerType('password', TestNode, {
|
||||||
|
// credentials: {
|
||||||
|
// pswd: {type:"password"}
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// index.loadFlows().then(function() {
|
||||||
|
// var testnode = new TestNode({id:'tab1',type:'password',name:'barney'});
|
||||||
|
// // setting value of password credential should update password
|
||||||
|
// testnode.credentials = {"pswd": 'fiddle'};
|
||||||
|
// credentials.extract(testnode);
|
||||||
|
// should.exist(credentials.get('tab1'));
|
||||||
|
// credentials.get('tab1').should.have.property('pswd','fiddle');
|
||||||
|
// credentials.get('tab1').should.not.have.property('pswd','sticks');
|
||||||
|
// done();
|
||||||
|
// }).otherwise(function(err){
|
||||||
|
// done(err);
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// it(': password credential not updated', function(done) {
|
||||||
|
// index.registerType('password', TestNode, {
|
||||||
|
// credentials: {
|
||||||
|
// pswd: {type:"password"}
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// index.loadFlows().then(function() {
|
||||||
|
// var testnode = new TestNode({id:'tab1',type:'password',name:'barney'});
|
||||||
|
// // setting value of password credential should update password
|
||||||
|
// testnode.credentials = {"pswd": '__PWRD__'};
|
||||||
|
// credentials.extract(testnode);
|
||||||
|
// should.exist(credentials.get('tab1'));
|
||||||
|
// credentials.get('tab1').should.have.property('pswd','sticks');
|
||||||
|
// credentials.get('tab1').should.not.have.property('pswd','__PWRD__');
|
||||||
|
// done();
|
||||||
|
// }).otherwise(function(err){
|
||||||
|
// done(err);
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
//})
|
||||||
|
|
||||||
it(': credential updated with empty value', function(done) {
|
//describe('registerEndpoint', function() {
|
||||||
index.registerType('test', TestNode, {
|
// var path = require('path');
|
||||||
credentials: {
|
// var fs = require('fs-extra');
|
||||||
foo: {type:"test"}
|
// var http = require('http');
|
||||||
}
|
// var express = require('express');
|
||||||
});
|
// var request = require('supertest');
|
||||||
index.loadFlows().then(function() {
|
//
|
||||||
var testnode = new TestNode({id:'tab1',type:'test',name:'barney'});
|
// var server = require("../../../red/server");
|
||||||
// setting value of "foo" credential to be empty removes foo as a property
|
// var localfilesystem = require("../../../red/storage/localfilesystem");
|
||||||
testnode.credentials = {"foo": ''};
|
// var app = express();
|
||||||
credentials.extract(testnode);
|
// var RED = require("../../../red/red.js");
|
||||||
should.exist(credentials.get('tab1'));
|
//
|
||||||
credentials.get('tab1').should.not.have.property('foo',2);
|
// var userDir = path.join(__dirname,".testUserHome");
|
||||||
credentials.get('tab1').should.not.have.property('foo');
|
// before(function(done) {
|
||||||
done();
|
// fs.remove(userDir,function(err) {
|
||||||
}).otherwise(function(err){
|
// fs.mkdir(userDir,function() {
|
||||||
done(err);
|
// sinon.stub(index, 'load', function() {
|
||||||
});
|
// return when.promise(function(resolve,reject){
|
||||||
});
|
// resolve([]);
|
||||||
|
// });
|
||||||
it(': undefined credential updated', function(done) {
|
// });
|
||||||
index.registerType('test', TestNode, {
|
// sinon.stub(localfilesystem, 'getCredentials', function() {
|
||||||
credentials: {
|
// return when.promise(function(resolve,reject) {
|
||||||
foo: {type:"test"}
|
// resolve({"tab1":{"foo": 2, "pswd":'sticks'}});
|
||||||
}
|
// });
|
||||||
});
|
// }) ;
|
||||||
index.loadFlows().then(function() {
|
// RED.init(http.createServer(function(req,res){app(req,res)}),
|
||||||
var testnode = new TestNode({id:'tab1',type:'test',name:'barney'});
|
// {userDir: userDir});
|
||||||
// setting value of an undefined credential should not change anything
|
// server.start().then(function () {
|
||||||
testnode.credentials = {"bar": 4};
|
// done();
|
||||||
credentials.extract(testnode);
|
// });
|
||||||
should.exist(credentials.get('tab1'));
|
// });
|
||||||
credentials.get('tab1').should.have.property('foo',2);
|
// });
|
||||||
credentials.get('tab1').should.not.have.property('bar');
|
// });
|
||||||
done();
|
//
|
||||||
}).otherwise(function(err){
|
// after(function(done) {
|
||||||
done(err);
|
// fs.remove(userDir,done);
|
||||||
});
|
// server.stop();
|
||||||
});
|
// index.load.restore();
|
||||||
|
// localfilesystem.getCredentials.restore();
|
||||||
it(': password credential updated', function(done) {
|
// });
|
||||||
index.registerType('password', TestNode, {
|
//
|
||||||
credentials: {
|
// function TestNode(n) {
|
||||||
pswd: {type:"password"}
|
// index.createNode(this, n);
|
||||||
}
|
// var node = this;
|
||||||
});
|
// this.on("log", function() {
|
||||||
index.loadFlows().then(function() {
|
// // do nothing
|
||||||
var testnode = new TestNode({id:'tab1',type:'password',name:'barney'});
|
// });
|
||||||
// setting value of password credential should update password
|
// }
|
||||||
testnode.credentials = {"pswd": 'fiddle'};
|
//
|
||||||
credentials.extract(testnode);
|
// it(': valid credential type', function(done) {
|
||||||
should.exist(credentials.get('tab1'));
|
// index.registerType('test', TestNode, {
|
||||||
credentials.get('tab1').should.have.property('pswd','fiddle');
|
// credentials: {
|
||||||
credentials.get('tab1').should.not.have.property('pswd','sticks');
|
// foo: {type:"test"}
|
||||||
done();
|
// }
|
||||||
}).otherwise(function(err){
|
// });
|
||||||
done(err);
|
// index.loadFlows().then(function() {
|
||||||
});
|
// var testnode = new TestNode({id:'tab1',type:'foo',name:'barney'});
|
||||||
});
|
// request(RED.httpAdmin).get('/credentials/test/tab1').expect(200).end(function(err,res) {
|
||||||
|
// if (err) {
|
||||||
it(': password credential not updated', function(done) {
|
// done(err);
|
||||||
index.registerType('password', TestNode, {
|
// }
|
||||||
credentials: {
|
// res.body.should.have.property('foo', 2);
|
||||||
pswd: {type:"password"}
|
// done();
|
||||||
}
|
// });
|
||||||
});
|
// }).otherwise(function(err){
|
||||||
index.loadFlows().then(function() {
|
// done(err);
|
||||||
var testnode = new TestNode({id:'tab1',type:'password',name:'barney'});
|
// });
|
||||||
// setting value of password credential should update password
|
// });
|
||||||
testnode.credentials = {"pswd": '__PWRD__'};
|
//
|
||||||
credentials.extract(testnode);
|
// it(': password credential type', function(done) {
|
||||||
should.exist(credentials.get('tab1'));
|
// index.registerType('password', TestNode, {
|
||||||
credentials.get('tab1').should.have.property('pswd','sticks');
|
// credentials: {
|
||||||
credentials.get('tab1').should.not.have.property('pswd','__PWRD__');
|
// pswd: {type:"password"}
|
||||||
done();
|
// }
|
||||||
}).otherwise(function(err){
|
// });
|
||||||
done(err);
|
// index.loadFlows().then(function() {
|
||||||
});
|
// var testnode = new TestNode({id:'tab1',type:'pswd',name:'barney'});
|
||||||
});
|
// request(RED.httpAdmin).get('/credentials/password/tab1').expect(200).end(function(err,res) {
|
||||||
|
// if (err) {
|
||||||
})
|
// done(err);
|
||||||
|
// }
|
||||||
describe('registerEndpoint', function() {
|
// res.body.should.have.property('has_pswd', true);
|
||||||
var path = require('path');
|
// res.body.should.not.have.property('pswd');
|
||||||
var fs = require('fs-extra');
|
// done();
|
||||||
var http = require('http');
|
// });
|
||||||
var express = require('express');
|
// }).otherwise(function(err){
|
||||||
var request = require('supertest');
|
// done(err);
|
||||||
|
// });
|
||||||
var server = require("../../../red/server");
|
// });
|
||||||
var localfilesystem = require("../../../red/storage/localfilesystem");
|
//
|
||||||
var app = express();
|
// it(': returns 404 for undefined credential type', function(done) {
|
||||||
var RED = require("../../../red/red.js");
|
// index.registerType('test', TestNode, {
|
||||||
|
// credentials: {
|
||||||
var userDir = path.join(__dirname,".testUserHome");
|
// foo: {type:"test"}
|
||||||
before(function(done) {
|
// }
|
||||||
fs.remove(userDir,function(err) {
|
// });
|
||||||
fs.mkdir(userDir,function() {
|
// index.loadFlows().then(function() {
|
||||||
sinon.stub(index, 'load', function() {
|
// var testnode = new TestNode({id:'tab1',type:'foo',name:'barney'});
|
||||||
return when.promise(function(resolve,reject){
|
// request(RED.httpAdmin).get('/credentials/unknownType/tab1').expect(404).end(done);
|
||||||
resolve([]);
|
// }).otherwise(function(err){
|
||||||
});
|
// done(err);
|
||||||
});
|
// });
|
||||||
sinon.stub(localfilesystem, 'getCredentials', function() {
|
// });
|
||||||
return when.promise(function(resolve,reject) {
|
//
|
||||||
resolve({"tab1":{"foo": 2, "pswd":'sticks'}});
|
// it(': undefined nodeID', function(done) {
|
||||||
});
|
// index.registerType('test', TestNode, {
|
||||||
}) ;
|
// credentials: {
|
||||||
RED.init(http.createServer(function(req,res){app(req,res)}),
|
// foo: {type:"test"}
|
||||||
{userDir: userDir});
|
// }
|
||||||
server.start().then(function () {
|
// });
|
||||||
done();
|
// index.loadFlows().then(function() {
|
||||||
});
|
// var testnode = new TestNode({id:'tab1',type:'foo',name:'barney'});
|
||||||
});
|
// request(RED.httpAdmin).get('/credentials/test/unknownNode').expect(200).end(function(err,res) {
|
||||||
});
|
// if (err) {
|
||||||
});
|
// done(err);
|
||||||
|
// }
|
||||||
after(function(done) {
|
// var b = res.body;
|
||||||
fs.remove(userDir,done);
|
// res.body.should.not.have.property('foo');
|
||||||
server.stop();
|
// done();
|
||||||
index.load.restore();
|
// });
|
||||||
localfilesystem.getCredentials.restore();
|
// }).otherwise(function(err){
|
||||||
});
|
// done(err);
|
||||||
|
// });
|
||||||
function TestNode(n) {
|
// });
|
||||||
index.createNode(this, n);
|
//
|
||||||
var node = this;
|
//})
|
||||||
this.on("log", function() {
|
|
||||||
// do nothing
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
it(': valid credential type', function(done) {
|
|
||||||
index.registerType('test', TestNode, {
|
|
||||||
credentials: {
|
|
||||||
foo: {type:"test"}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
index.loadFlows().then(function() {
|
|
||||||
var testnode = new TestNode({id:'tab1',type:'foo',name:'barney'});
|
|
||||||
request(RED.httpAdmin).get('/credentials/test/tab1').expect(200).end(function(err,res) {
|
|
||||||
if (err) {
|
|
||||||
done(err);
|
|
||||||
}
|
|
||||||
res.body.should.have.property('foo', 2);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
}).otherwise(function(err){
|
|
||||||
done(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it(': password credential type', function(done) {
|
|
||||||
index.registerType('password', TestNode, {
|
|
||||||
credentials: {
|
|
||||||
pswd: {type:"password"}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
index.loadFlows().then(function() {
|
|
||||||
var testnode = new TestNode({id:'tab1',type:'pswd',name:'barney'});
|
|
||||||
request(RED.httpAdmin).get('/credentials/password/tab1').expect(200).end(function(err,res) {
|
|
||||||
if (err) {
|
|
||||||
done(err);
|
|
||||||
}
|
|
||||||
res.body.should.have.property('has_pswd', true);
|
|
||||||
res.body.should.not.have.property('pswd');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
}).otherwise(function(err){
|
|
||||||
done(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it(': returns 404 for undefined credential type', function(done) {
|
|
||||||
index.registerType('test', TestNode, {
|
|
||||||
credentials: {
|
|
||||||
foo: {type:"test"}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
index.loadFlows().then(function() {
|
|
||||||
var testnode = new TestNode({id:'tab1',type:'foo',name:'barney'});
|
|
||||||
request(RED.httpAdmin).get('/credentials/unknownType/tab1').expect(404).end(done);
|
|
||||||
}).otherwise(function(err){
|
|
||||||
done(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it(': undefined nodeID', function(done) {
|
|
||||||
index.registerType('test', TestNode, {
|
|
||||||
credentials: {
|
|
||||||
foo: {type:"test"}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
index.loadFlows().then(function() {
|
|
||||||
var testnode = new TestNode({id:'tab1',type:'foo',name:'barney'});
|
|
||||||
request(RED.httpAdmin).get('/credentials/test/unknownNode').expect(200).end(function(err,res) {
|
|
||||||
if (err) {
|
|
||||||
done(err);
|
|
||||||
}
|
|
||||||
var b = res.body;
|
|
||||||
res.body.should.not.have.property('foo');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
}).otherwise(function(err){
|
|
||||||
done(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -47,39 +47,13 @@ function loadFlows(testFlows, cb) {
|
|||||||
describe('flows', function() {
|
describe('flows', function() {
|
||||||
|
|
||||||
afterEach(function(done) {
|
afterEach(function(done) {
|
||||||
flows.clear().then(function() {
|
flows.stopFlows().then(function() {
|
||||||
loadFlows([],done);
|
loadFlows([],done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#add',function() {
|
|
||||||
it('should be called by node constructor',function(done) {
|
|
||||||
var n = new RedNode({id:'123',type:'abc'});
|
|
||||||
should.deepEqual(n, flows.get("123"));
|
|
||||||
flows.clear().then(function() {
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('#each',function() {
|
|
||||||
it('should "visit" all nodes',function(done) {
|
|
||||||
var nodes = [
|
|
||||||
new RedNode({id:'n0'}),
|
|
||||||
new RedNode({id:'n1'})
|
|
||||||
];
|
|
||||||
var count = 0;
|
|
||||||
flows.each(function(node) {
|
|
||||||
should.deepEqual(nodes[count], node);
|
|
||||||
count += 1;
|
|
||||||
if (count == 2) {
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('#load',function() {
|
describe('#load',function() {
|
||||||
|
|
||||||
it('should load nothing when storage is empty',function(done) {
|
it('should load nothing when storage is empty',function(done) {
|
||||||
loadFlows([], done);
|
loadFlows([], done);
|
||||||
});
|
});
|
||||||
@ -92,7 +66,7 @@ describe('flows', function() {
|
|||||||
it('should load and start a registered node type', function(done) {
|
it('should load and start a registered node type', function(done) {
|
||||||
RED.registerType('debug', function() {});
|
RED.registerType('debug', function() {});
|
||||||
var typeRegistryGet = sinon.stub(typeRegistry,"get",function(nt) {
|
var typeRegistryGet = sinon.stub(typeRegistry,"get",function(nt) {
|
||||||
return function() {};
|
return RedNode;
|
||||||
});
|
});
|
||||||
loadFlows([{"id":"n1","type":"debug"}], function() { });
|
loadFlows([{"id":"n1","type":"debug"}], function() { });
|
||||||
events.once('nodes-started', function() {
|
events.once('nodes-started', function() {
|
||||||
@ -104,8 +78,7 @@ describe('flows', function() {
|
|||||||
it('should load and start when node type is registered', function(done) {
|
it('should load and start when node type is registered', function(done) {
|
||||||
var typeRegistryGet = sinon.stub(typeRegistry,"get");
|
var typeRegistryGet = sinon.stub(typeRegistry,"get");
|
||||||
typeRegistryGet.onCall(0).returns(null);
|
typeRegistryGet.onCall(0).returns(null);
|
||||||
typeRegistryGet.returns(function(){});
|
typeRegistryGet.returns(RedNode);
|
||||||
|
|
||||||
loadFlows([{"id":"n2","type":"inject"}], function() {
|
loadFlows([{"id":"n2","type":"inject"}], function() {
|
||||||
events.emit('type-registered','inject');
|
events.emit('type-registered','inject');
|
||||||
});
|
});
|
||||||
@ -118,7 +91,7 @@ describe('flows', function() {
|
|||||||
it('should not instantiate nodes of an unused subflow', function(done) {
|
it('should not instantiate nodes of an unused subflow', function(done) {
|
||||||
RED.registerType('abc', function() {});
|
RED.registerType('abc', function() {});
|
||||||
var typeRegistryGet = sinon.stub(typeRegistry,"get",function(nt) {
|
var typeRegistryGet = sinon.stub(typeRegistry,"get",function(nt) {
|
||||||
return function() {};
|
return RedNode;
|
||||||
});
|
});
|
||||||
loadFlows([{"id":"n1","type":"subflow",inputs:[],outputs:[],wires:[]},
|
loadFlows([{"id":"n1","type":"subflow",inputs:[],outputs:[],wires:[]},
|
||||||
{"id":"n2","type":"abc","z":"n1",wires:[]}
|
{"id":"n2","type":"abc","z":"n1",wires:[]}
|
||||||
@ -126,11 +99,10 @@ describe('flows', function() {
|
|||||||
events.once('nodes-started', function() {
|
events.once('nodes-started', function() {
|
||||||
(flows.get("n2") == null).should.be.true;
|
(flows.get("n2") == null).should.be.true;
|
||||||
var ncount = 0
|
var ncount = 0
|
||||||
flows.each(function(n) {
|
flows.eachNode(function(n) {
|
||||||
ncount++;
|
ncount++;
|
||||||
});
|
});
|
||||||
ncount.should.equal(0);
|
ncount.should.equal(0);
|
||||||
console.log(ncount);
|
|
||||||
typeRegistryGet.restore();
|
typeRegistryGet.restore();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -149,7 +121,7 @@ describe('flows', function() {
|
|||||||
(flows.get("n2") == null).should.be.true;
|
(flows.get("n2") == null).should.be.true;
|
||||||
var ncount = 0
|
var ncount = 0
|
||||||
var nodes = [];
|
var nodes = [];
|
||||||
flows.each(function(n) {
|
flows.eachNode(function(n) {
|
||||||
nodes.push(n);
|
nodes.push(n);
|
||||||
});
|
});
|
||||||
nodes.should.have.lengthOf(2);
|
nodes.should.have.lengthOf(2);
|
||||||
|
@ -41,16 +41,6 @@ describe('NodeRegistry', function() {
|
|||||||
var settings = stubSettings({},false);
|
var settings = stubSettings({},false);
|
||||||
var settingsWithStorage = stubSettings({},true);
|
var settingsWithStorage = stubSettings({},true);
|
||||||
|
|
||||||
it('automatically registers new nodes',function() {
|
|
||||||
var testNode = RedNodes.getNode('123');
|
|
||||||
should.not.exist(n);
|
|
||||||
var n = new RedNode({id:'123',type:'abc'});
|
|
||||||
|
|
||||||
var newNode = RedNodes.getNode('123');
|
|
||||||
|
|
||||||
should.strictEqual(n,newNode);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('handles nodes that export a function', function(done) {
|
it('handles nodes that export a function', function(done) {
|
||||||
typeRegistry.init(settings);
|
typeRegistry.init(settings);
|
||||||
typeRegistry.load(resourcesDir + "TestNode1",true).then(function() {
|
typeRegistry.load(resourcesDir + "TestNode1",true).then(function() {
|
||||||
@ -239,11 +229,12 @@ describe('NodeRegistry', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns nothing for an unregistered type config', function() {
|
it('returns nothing for an unregistered type config', function(done) {
|
||||||
typeRegistry.init(settings);
|
typeRegistry.init(settings);
|
||||||
typeRegistry.load("wontexist",true).then(function(){
|
typeRegistry.load("wontexist",true).then(function(){
|
||||||
var config = typeRegistry.getNodeConfig("imaginary-shark");
|
var config = typeRegistry.getNodeConfig("imaginary-shark");
|
||||||
(config === null).should.be.true;
|
(config === null).should.be.true;
|
||||||
|
done();
|
||||||
}).catch(function(e) {
|
}).catch(function(e) {
|
||||||
done(e);
|
done(e);
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user