mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Add v2 /flows api and deploy-overwrite protection
This commit is contained in:
parent
c60e0d389c
commit
b4be1184fd
@ -67,13 +67,14 @@ var RED = (function() {
|
|||||||
function loadFlows() {
|
function loadFlows() {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
headers: {
|
headers: {
|
||||||
"Accept":"application/json"
|
"Accept":"application/json",
|
||||||
},
|
},
|
||||||
cache: false,
|
cache: false,
|
||||||
url: 'flows',
|
url: 'flows',
|
||||||
success: function(nodes) {
|
success: function(nodes) {
|
||||||
var currentHash = window.location.hash;
|
var currentHash = window.location.hash;
|
||||||
RED.nodes.import(nodes);
|
RED.nodes.version(nodes.rev);
|
||||||
|
RED.nodes.import(nodes.flows);
|
||||||
RED.nodes.dirty(false);
|
RED.nodes.dirty(false);
|
||||||
RED.view.redraw(true);
|
RED.view.redraw(true);
|
||||||
if (/^#flow\/.+$/.test(currentHash)) {
|
if (/^#flow\/.+$/.test(currentHash)) {
|
||||||
|
@ -23,11 +23,22 @@ RED.nodes = (function() {
|
|||||||
var workspaces = {};
|
var workspaces = {};
|
||||||
var workspacesOrder =[];
|
var workspacesOrder =[];
|
||||||
var subflows = {};
|
var subflows = {};
|
||||||
|
var loadedFlowVersion = null;
|
||||||
|
var pending = {
|
||||||
|
deleted: {},
|
||||||
|
added: {}
|
||||||
|
};
|
||||||
|
|
||||||
var dirty = false;
|
var dirty = false;
|
||||||
|
|
||||||
function setDirty(d) {
|
function setDirty(d) {
|
||||||
dirty = d;
|
dirty = d;
|
||||||
|
if (!d) {
|
||||||
|
pending = {
|
||||||
|
deleted: {},
|
||||||
|
added: {}
|
||||||
|
};
|
||||||
|
}
|
||||||
RED.events.emit("nodes:change",{dirty:dirty});
|
RED.events.emit("nodes:change",{dirty:dirty});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,6 +186,8 @@ RED.nodes = (function() {
|
|||||||
}
|
}
|
||||||
nodes.push(n);
|
nodes.push(n);
|
||||||
}
|
}
|
||||||
|
delete pending.deleted[n.id];
|
||||||
|
pending.added[n.id] = true;
|
||||||
RED.events.emit('nodes:add',n);
|
RED.events.emit('nodes:add',n);
|
||||||
}
|
}
|
||||||
function addLink(l) {
|
function addLink(l) {
|
||||||
@ -240,6 +253,12 @@ RED.nodes = (function() {
|
|||||||
if (node && node._def.onremove) {
|
if (node && node._def.onremove) {
|
||||||
node._def.onremove.call(n);
|
node._def.onremove.call(n);
|
||||||
}
|
}
|
||||||
|
delete pending.added[id];
|
||||||
|
pending.deleted[id] = true;
|
||||||
|
removedNodes.forEach(function(node) {
|
||||||
|
delete pending.added[node.id];
|
||||||
|
pending.deleted[node.id] = true;
|
||||||
|
});
|
||||||
return {links:removedLinks,nodes:removedNodes};
|
return {links:removedLinks,nodes:removedNodes};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -252,6 +271,8 @@ RED.nodes = (function() {
|
|||||||
|
|
||||||
function addWorkspace(ws) {
|
function addWorkspace(ws) {
|
||||||
workspaces[ws.id] = ws;
|
workspaces[ws.id] = ws;
|
||||||
|
pending.added[ws.id] = true;
|
||||||
|
delete pending.deleted[ws.id];
|
||||||
ws._def = {
|
ws._def = {
|
||||||
defaults: {
|
defaults: {
|
||||||
label: {value:""}
|
label: {value:""}
|
||||||
@ -289,6 +310,8 @@ RED.nodes = (function() {
|
|||||||
var result = removeNode(removedNodes[n].id);
|
var result = removeNode(removedNodes[n].id);
|
||||||
removedLinks = removedLinks.concat(result.links);
|
removedLinks = removedLinks.concat(result.links);
|
||||||
}
|
}
|
||||||
|
pending.deleted[id] = true;
|
||||||
|
delete pending.added[id]
|
||||||
return {nodes:removedNodes,links:removedLinks};
|
return {nodes:removedNodes,links:removedLinks};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,6 +341,8 @@ RED.nodes = (function() {
|
|||||||
outputs: sf.out.length
|
outputs: sf.out.length
|
||||||
}
|
}
|
||||||
subflows[sf.id] = sf;
|
subflows[sf.id] = sf;
|
||||||
|
delete pending.deleted[sf.id];
|
||||||
|
pending.added[sf.id] = true;
|
||||||
RED.nodes.registerType("subflow:"+sf.id, {
|
RED.nodes.registerType("subflow:"+sf.id, {
|
||||||
defaults:{name:{value:""}},
|
defaults:{name:{value:""}},
|
||||||
info: sf.info,
|
info: sf.info,
|
||||||
@ -341,6 +366,8 @@ RED.nodes = (function() {
|
|||||||
}
|
}
|
||||||
function removeSubflow(sf) {
|
function removeSubflow(sf) {
|
||||||
delete subflows[sf.id];
|
delete subflows[sf.id];
|
||||||
|
delete pending.added[sf.id];
|
||||||
|
pending.deleted[sf.id] = true;
|
||||||
registry.removeNodeType("subflow:"+sf.id);
|
registry.removeNodeType("subflow:"+sf.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -831,10 +858,11 @@ RED.nodes = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!existingConfigNode) { //} || !compareNodes(existingConfigNode,n,true) || existingConfigNode._def.exclusive || existingConfigNode.z !== n.z) {
|
if (!existingConfigNode) { //} || !compareNodes(existingConfigNode,n,true) || existingConfigNode._def.exclusive || existingConfigNode.z !== n.z) {
|
||||||
configNode = {id:n.id, z:n.z, type:n.type, users:[]};
|
configNode = {id:n.id, z:n.z, type:n.type, users:[], _config:{}};
|
||||||
for (d in def.defaults) {
|
for (d in def.defaults) {
|
||||||
if (def.defaults.hasOwnProperty(d)) {
|
if (def.defaults.hasOwnProperty(d)) {
|
||||||
configNode[d] = n[d];
|
configNode[d] = n[d];
|
||||||
|
configNode._config[d] = JSON.stringify(n[d]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (def.hasOwnProperty('credentials') && n.hasOwnProperty('credentials')) {
|
if (def.hasOwnProperty('credentials') && n.hasOwnProperty('credentials')) {
|
||||||
@ -864,7 +892,7 @@ RED.nodes = (function() {
|
|||||||
if (n.type !== "workspace" && n.type !== "tab" && n.type !== "subflow") {
|
if (n.type !== "workspace" && n.type !== "tab" && n.type !== "subflow") {
|
||||||
def = registry.getNodeType(n.type);
|
def = registry.getNodeType(n.type);
|
||||||
if (!def || def.category != "config") {
|
if (!def || def.category != "config") {
|
||||||
var node = {x:n.x,y:n.y,z:n.z,type:0,wires:n.wires,changed:false};
|
var node = {x:n.x,y:n.y,z:n.z,type:0,wires:n.wires,changed:false,_config:{}};
|
||||||
if (createNewIds) {
|
if (createNewIds) {
|
||||||
if (subflow_blacklist[n.z]) {
|
if (subflow_blacklist[n.z]) {
|
||||||
continue;
|
continue;
|
||||||
@ -947,8 +975,11 @@ RED.nodes = (function() {
|
|||||||
for (d in node._def.defaults) {
|
for (d in node._def.defaults) {
|
||||||
if (node._def.defaults.hasOwnProperty(d)) {
|
if (node._def.defaults.hasOwnProperty(d)) {
|
||||||
node[d] = n[d];
|
node[d] = n[d];
|
||||||
|
node._config[d] = JSON.stringify(n[d]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
node._config.x = node.x;
|
||||||
|
node._config.y = node.y;
|
||||||
if (node._def.hasOwnProperty('credentials') && n.hasOwnProperty('credentials')) {
|
if (node._def.hasOwnProperty('credentials') && n.hasOwnProperty('credentials')) {
|
||||||
node.credentials = {};
|
node.credentials = {};
|
||||||
for (d in node._def.credentials) {
|
for (d in node._def.credentials) {
|
||||||
@ -959,9 +990,6 @@ RED.nodes = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (node.credentials) {
|
|
||||||
console.log(node);
|
|
||||||
}
|
|
||||||
addNode(node);
|
addNode(node);
|
||||||
RED.editor.validateNode(node);
|
RED.editor.validateNode(node);
|
||||||
node_map[n.id] = node;
|
node_map[n.id] = node;
|
||||||
@ -1122,6 +1150,14 @@ RED.nodes = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function flowVersion(version) {
|
||||||
|
if (version !== undefined) {
|
||||||
|
loadedFlowVersion = version;
|
||||||
|
} else {
|
||||||
|
return loadedFlowVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
registry:registry,
|
registry:registry,
|
||||||
setNodeList: registry.setNodeList,
|
setNodeList: registry.setNodeList,
|
||||||
@ -1185,11 +1221,15 @@ RED.nodes = (function() {
|
|||||||
|
|
||||||
node: getNode,
|
node: getNode,
|
||||||
|
|
||||||
|
version: flowVersion,
|
||||||
|
|
||||||
filterNodes: filterNodes,
|
filterNodes: filterNodes,
|
||||||
filterLinks: filterLinks,
|
filterLinks: filterLinks,
|
||||||
|
|
||||||
import: importNodes,
|
import: importNodes,
|
||||||
|
|
||||||
|
pending: function() { return pending },
|
||||||
|
|
||||||
getAllFlowNodes: getAllFlowNodes,
|
getAllFlowNodes: getAllFlowNodes,
|
||||||
createExportableNodeSet: createExportableNodeSet,
|
createExportableNodeSet: createExportableNodeSet,
|
||||||
createCompleteNodeSet: createCompleteNodeSet,
|
createCompleteNodeSet: createCompleteNodeSet,
|
||||||
|
@ -84,6 +84,7 @@ RED.settings = (function () {
|
|||||||
if (auth_tokens) {
|
if (auth_tokens) {
|
||||||
jqXHR.setRequestHeader("Authorization","Bearer "+auth_tokens.access_token);
|
jqXHR.setRequestHeader("Authorization","Bearer "+auth_tokens.access_token);
|
||||||
}
|
}
|
||||||
|
jqXHR.setRequestHeader("Node-RED-API-Version","v2");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -35,6 +35,7 @@ RED.deploy = (function() {
|
|||||||
$("#btn-deploy img").attr("src",deploymentTypes[type].img);
|
$("#btn-deploy img").attr("src",deploymentTypes[type].img);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var currentDiff = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* options:
|
* options:
|
||||||
@ -76,7 +77,7 @@ RED.deploy = (function() {
|
|||||||
$('#btn-deploy').click(function() { save(); });
|
$('#btn-deploy').click(function() { save(); });
|
||||||
|
|
||||||
$( "#node-dialog-confirm-deploy" ).dialog({
|
$( "#node-dialog-confirm-deploy" ).dialog({
|
||||||
title: "Confirm deploy",
|
title: RED._('deploy.confirm.button.confirm'),
|
||||||
modal: true,
|
modal: true,
|
||||||
autoOpen: false,
|
autoOpen: false,
|
||||||
width: 550,
|
width: 550,
|
||||||
@ -88,6 +89,15 @@ RED.deploy = (function() {
|
|||||||
$( this ).dialog( "close" );
|
$( this ).dialog( "close" );
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// id: "node-dialog-confirm-deploy-review",
|
||||||
|
// text: RED._("deploy.confirm.button.review"),
|
||||||
|
// class: "primary",
|
||||||
|
// click: function() {
|
||||||
|
// showDiff();
|
||||||
|
// $( this ).dialog( "close" );
|
||||||
|
// }
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
text: RED._("deploy.confirm.button.confirm"),
|
text: RED._("deploy.confirm.button.confirm"),
|
||||||
class: "primary",
|
class: "primary",
|
||||||
@ -97,7 +107,7 @@ RED.deploy = (function() {
|
|||||||
if (ignoreChecked) {
|
if (ignoreChecked) {
|
||||||
ignoreDeployWarnings[$( "#node-dialog-confirm-deploy-type" ).val()] = true;
|
ignoreDeployWarnings[$( "#node-dialog-confirm-deploy-type" ).val()] = true;
|
||||||
}
|
}
|
||||||
save(true);
|
save(true,$( "#node-dialog-confirm-deploy-type" ).val() === "conflict");
|
||||||
$( this ).dialog( "close" );
|
$( this ).dialog( "close" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,6 +119,15 @@ RED.deploy = (function() {
|
|||||||
'<label style="display:inline;" for="node-dialog-confirm-deploy-hide"> do not warn about this again</label>'+
|
'<label style="display:inline;" for="node-dialog-confirm-deploy-hide"> do not warn about this again</label>'+
|
||||||
'<input type="hidden" id="node-dialog-confirm-deploy-type">'+
|
'<input type="hidden" id="node-dialog-confirm-deploy-type">'+
|
||||||
'</div>');
|
'</div>');
|
||||||
|
},
|
||||||
|
open: function() {
|
||||||
|
if ($( "#node-dialog-confirm-deploy-type" ).val() === "conflict") {
|
||||||
|
// $("#node-dialog-confirm-deploy-review").show();
|
||||||
|
$("#node-dialog-confirm-deploy-hide").parent().hide();
|
||||||
|
} else {
|
||||||
|
// $("#node-dialog-confirm-deploy-review").hide();
|
||||||
|
$("#node-dialog-confirm-deploy-hide").parent().show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -123,6 +142,199 @@ RED.deploy = (function() {
|
|||||||
$("#btn-deploy").addClass("disabled");
|
$("#btn-deploy").addClass("disabled");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// $("#node-dialog-view-diff").dialog({
|
||||||
|
// title: RED._('deploy.confirm.button.review'),
|
||||||
|
// modal: true,
|
||||||
|
// autoOpen: false,
|
||||||
|
// buttons: [
|
||||||
|
// {
|
||||||
|
// text: RED._("deploy.confirm.button.cancel"),
|
||||||
|
// click: function() {
|
||||||
|
// $( this ).dialog( "close" );
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// text: RED._("deploy.confirm.button.merge"),
|
||||||
|
// class: "primary",
|
||||||
|
// click: function() {
|
||||||
|
// $( this ).dialog( "close" );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
// open: function() {
|
||||||
|
// $(this).dialog({width:Math.min($(window).width(),900),height:Math.min($(window).height(),600)});
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// $("#node-dialog-view-diff-diff").editableList({
|
||||||
|
// addButton: false,
|
||||||
|
// scrollOnAdd: false,
|
||||||
|
// addItem: function(container,i,object) {
|
||||||
|
// var tab = object.tab.n;
|
||||||
|
// var tabDiv = $('<div>',{class:"node-diff-tab collapsed"}).appendTo(container);
|
||||||
|
//
|
||||||
|
// var titleRow = $('<div>',{class:"node-diff-tab-title"}).appendTo(tabDiv);
|
||||||
|
// titleRow.click(function(evt) {
|
||||||
|
// evt.preventDefault();
|
||||||
|
// titleRow.parent().toggleClass('collapsed');
|
||||||
|
// })
|
||||||
|
// var chevron = $('<i class="fa fa-angle-down node-diff-chevron ">').appendTo(titleRow);
|
||||||
|
// var title = $('<span>').html(tab.label||tab.id).appendTo(titleRow);
|
||||||
|
//
|
||||||
|
// var stats = $('<span>',{class:"node-diff-tab-stats"}).appendTo(titleRow);
|
||||||
|
//
|
||||||
|
// var addedCount = 0;
|
||||||
|
// var deletedCount = 0;
|
||||||
|
// var changedCount = 0;
|
||||||
|
// var conflictedCount = 0;
|
||||||
|
//
|
||||||
|
// object.tab.nodes.forEach(function(node) {
|
||||||
|
// var realNode = RED.nodes.node(node.id);
|
||||||
|
// var hasChanges = false;
|
||||||
|
// if (currentDiff.added[node.id]) {
|
||||||
|
// addedCount++;
|
||||||
|
// hasChanges = true;
|
||||||
|
// }
|
||||||
|
// if (currentDiff.deleted[node.id]) {
|
||||||
|
// deletedCount++;
|
||||||
|
// hasChanges = true;
|
||||||
|
// }
|
||||||
|
// if (currentDiff.changed[node.id]) {
|
||||||
|
// changedCount++;
|
||||||
|
// hasChanges = true;
|
||||||
|
// }
|
||||||
|
// if (currentDiff.conflicted[node.id]) {
|
||||||
|
// conflictedCount++;
|
||||||
|
// hasChanges = true;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (hasChanges) {
|
||||||
|
// var def = RED.nodes.getType(node.type)||{};
|
||||||
|
// var div = $("<div>",{class:"node-diff-node-entry collapsed"}).appendTo(tabDiv);
|
||||||
|
// var nodeTitleDiv = $("<div>",{class:"node-diff-node-entry-title"}).appendTo(div);
|
||||||
|
// nodeTitleDiv.click(function(evt) {
|
||||||
|
// evt.preventDefault();
|
||||||
|
// $(this).parent().toggleClass('collapsed');
|
||||||
|
// })
|
||||||
|
// var newNode = currentDiff.newConfig.all[node.id];
|
||||||
|
// var nodePropertiesDiv = $("<div>",{class:"node-diff-node-entry-properties"}).appendTo(div);
|
||||||
|
//
|
||||||
|
// var nodePropertiesTable = $("<table>").appendTo(nodePropertiesDiv);
|
||||||
|
//
|
||||||
|
// if (node.hasOwnProperty('x')) {
|
||||||
|
// if (newNode.x !== node.x || newNode.y !== node.y) {
|
||||||
|
// var currentPosition = node.x+", "+node.y
|
||||||
|
// var newPosition = newNode.x+", "+newNode.y;
|
||||||
|
// $("<tr><td>position</td><td>"+currentPosition+"</td><td>"+newPosition+"</td></tr>").appendTo(nodePropertiesTable);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// var properties = Object.keys(node).filter(function(p) { return p!='z'&&p!='wires'&&p!=='x'&&p!=='y'&&p!=='id'&&p!=='type'&&(!def.defaults||!def.defaults.hasOwnProperty(p))});
|
||||||
|
// if (def.defaults) {
|
||||||
|
// properties = properties.concat(Object.keys(def.defaults));
|
||||||
|
// }
|
||||||
|
// properties.forEach(function(d) {
|
||||||
|
// var localValue = JSON.stringify(node[d]);
|
||||||
|
// var remoteValue = JSON.stringify(newNode[d]);
|
||||||
|
// var originalValue = realNode._config[d];
|
||||||
|
//
|
||||||
|
// if (remoteValue !== originalValue) {
|
||||||
|
// var formattedProperty = formatNodeProperty(node[d]);
|
||||||
|
// var newFormattedProperty = formatNodeProperty(newNode[d]);
|
||||||
|
// if (localValue === originalValue) {
|
||||||
|
// // no conflict change
|
||||||
|
// } else {
|
||||||
|
// // conflicting change
|
||||||
|
// }
|
||||||
|
// $("<tr><td>"+d+'</td><td class="">'+formattedProperty+'</td><td class="node-diff-property-changed">'+newFormattedProperty+"</td></tr>").appendTo(nodePropertiesTable);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// })
|
||||||
|
// var nodeChevron = $('<i class="fa fa-angle-down node-diff-chevron">').appendTo(nodeTitleDiv);
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// // var leftColumn = $('<div>',{class:"node-diff-column"}).appendTo(div);
|
||||||
|
// // var rightColumn = $('<div>',{class:"node-diff-column"}).appendTo(div);
|
||||||
|
// // rightColumn.html(" ");
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// var nodeDiv = $("<div>",{class:"node-diff-node-entry-node"}).appendTo(nodeTitleDiv);
|
||||||
|
// var colour = def.color;
|
||||||
|
// var icon_url = "arrow-in.png";
|
||||||
|
// if (node.type === 'tab') {
|
||||||
|
// colour = "#C0DEED";
|
||||||
|
// icon_url = "subflow.png";
|
||||||
|
// } else if (def.category === 'config') {
|
||||||
|
// icon_url = "cog.png";
|
||||||
|
// } else if (node.type === 'unknown') {
|
||||||
|
// icon_url = "alert.png";
|
||||||
|
// } else {
|
||||||
|
// icon_url = def.icon;
|
||||||
|
// }
|
||||||
|
// nodeDiv.css('backgroundColor',colour);
|
||||||
|
//
|
||||||
|
// var iconContainer = $('<div/>',{class:"palette_icon_container"}).appendTo(nodeDiv);
|
||||||
|
// $('<div/>',{class:"palette_icon",style:"background-image: url(icons/"+icon_url+")"}).appendTo(iconContainer);
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// var contentDiv = $('<div>',{class:"node-diff-node-description"}).appendTo(nodeTitleDiv);
|
||||||
|
//
|
||||||
|
// $('<span>',{class:"node-diff-node-label"}).html(node.label || node.name || node.id).appendTo(contentDiv);
|
||||||
|
// //$('<div>',{class:"red-ui-search-result-node-type"}).html(node.type).appendTo(contentDiv);
|
||||||
|
// //$('<div>',{class:"red-ui-search-result-node-id"}).html(node.id).appendTo(contentDiv);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// var statsInfo = '<span class="node-diff-count">'+object.tab.nodes.length+" nodes"+
|
||||||
|
// (addedCount+deletedCount+changedCount+conflictedCount > 0 ? " : ":"")+
|
||||||
|
// "</span> "+
|
||||||
|
// ((addedCount > 0)?'<span class="node-diff-added">'+addedCount+' added</span> ':'')+
|
||||||
|
// ((deletedCount > 0)?'<span class="node-diff-deleted">'+deletedCount+' deleted</span> ':'')+
|
||||||
|
// ((changedCount > 0)?'<span class="node-diff-changed">'+changedCount+' changed</span> ':'')+
|
||||||
|
// ((conflictedCount > 0)?'<span class="node-diff-conflicted">'+conflictedCount+' conflicts</span>':'');
|
||||||
|
// stats.html(statsInfo);
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// //
|
||||||
|
// //
|
||||||
|
// //
|
||||||
|
// // var node = object.node;
|
||||||
|
// // var realNode = RED.nodes.node(node.id);
|
||||||
|
// // var def = RED.nodes.getType(object.node.type)||{};
|
||||||
|
// // var l = "";
|
||||||
|
// // if (def && def.label && realNode) {
|
||||||
|
// // l = def.label;
|
||||||
|
// // try {
|
||||||
|
// // l = (typeof l === "function" ? l.call(realNode) : l);
|
||||||
|
// // } catch(err) {
|
||||||
|
// // console.log("Definition error: "+node.type+".label",err);
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
// // l = l||node.label||node.name||node.id||"";
|
||||||
|
// // console.log(node);
|
||||||
|
// // var div = $('<div>').appendTo(container);
|
||||||
|
// // div.html(l);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatNodeProperty(prop) {
|
||||||
|
var formattedProperty = prop;
|
||||||
|
if (formattedProperty === null) {
|
||||||
|
formattedProperty = 'null';
|
||||||
|
} else if (formattedProperty === undefined) {
|
||||||
|
formattedProperty = 'undefined';
|
||||||
|
} else if (typeof formattedProperty === 'object') {
|
||||||
|
formattedProperty = JSON.stringify(formattedProperty);
|
||||||
|
}
|
||||||
|
if (/\n/.test(formattedProperty)) {
|
||||||
|
formattedProperty = "<pre>"+formattedProperty+"</pre>"
|
||||||
|
}
|
||||||
|
return formattedProperty;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNodeInfo(node) {
|
function getNodeInfo(node) {
|
||||||
@ -160,11 +372,157 @@ RED.deploy = (function() {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function save(force) {
|
function resolveConflict(currentNodes) {
|
||||||
|
$( "#node-dialog-confirm-deploy-config" ).hide();
|
||||||
|
$( "#node-dialog-confirm-deploy-unknown" ).hide();
|
||||||
|
$( "#node-dialog-confirm-deploy-unused" ).hide();
|
||||||
|
$( "#node-dialog-confirm-deploy-conflict" ).show();
|
||||||
|
$( "#node-dialog-confirm-deploy-type" ).val("conflict");
|
||||||
|
$( "#node-dialog-confirm-deploy" ).dialog( "open" );
|
||||||
|
|
||||||
|
// $("#node-dialog-confirm-deploy-review").append($('<img src="red/images/spin.svg" style="background: rgba(255,255,255,0.8); margin-top: -16px; margin-left: -8px; height:16px; position: absolute; "/>'));
|
||||||
|
// $("#node-dialog-confirm-deploy-review .ui-button-text").css("opacity",0.4);
|
||||||
|
// $("#node-dialog-confirm-deploy-review").attr("disabled",true).addClass("disabled");
|
||||||
|
// $.ajax({
|
||||||
|
// headers: {
|
||||||
|
// "Accept":"application/json",
|
||||||
|
// },
|
||||||
|
// cache: false,
|
||||||
|
// url: 'flows',
|
||||||
|
// success: function(nodes) {
|
||||||
|
// var newNodes = nodes.flows;
|
||||||
|
// var newRevision = nodes.rev;
|
||||||
|
// generateDiff(currentNodes,newNodes);
|
||||||
|
// $("#node-dialog-confirm-deploy-review").attr("disabled",false).removeClass("disabled");
|
||||||
|
// $("#node-dialog-confirm-deploy-review img").remove();
|
||||||
|
// $("#node-dialog-confirm-deploy-review .ui-button-text").css("opacity",1);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
// function parseNodes(nodeList) {
|
||||||
|
// var tabOrder = [];
|
||||||
|
// var tabs = {};
|
||||||
|
// var subflows = {};
|
||||||
|
// var globals = [];
|
||||||
|
// var all = {};
|
||||||
|
//
|
||||||
|
// nodeList.forEach(function(node) {
|
||||||
|
// all[node.id] = node;
|
||||||
|
// if (node.type === 'tab') {
|
||||||
|
// tabOrder.push(node.id);
|
||||||
|
// tabs[node.id] = {n:node,nodes:[]};
|
||||||
|
// } else if (node.type === 'subflow') {
|
||||||
|
// subflows[node.id] = {n:node,nodes:[]};
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// nodeList.forEach(function(node) {
|
||||||
|
// if (node.type !== 'tab' && node.type !== 'subflow') {
|
||||||
|
// if (tabs[node.z]) {
|
||||||
|
// tabs[node.z].nodes.push(node);
|
||||||
|
// } else if (subflows[node.z]) {
|
||||||
|
// subflows[node.z].nodes.push(node);
|
||||||
|
// } else {
|
||||||
|
// globals.push(node);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// return {
|
||||||
|
// all: all,
|
||||||
|
// tabOrder: tabOrder,
|
||||||
|
// tabs: tabs,
|
||||||
|
// subflows: subflows,
|
||||||
|
// globals: globals
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function generateDiff(currentNodes,newNodes) {
|
||||||
|
// var currentConfig = parseNodes(currentNodes);
|
||||||
|
// var newConfig = parseNodes(newNodes);
|
||||||
|
// var pending = RED.nodes.pending();
|
||||||
|
// var added = {};
|
||||||
|
// var deleted = {};
|
||||||
|
// var changed = {};
|
||||||
|
// var conflicted = {};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Object.keys(currentConfig.all).forEach(function(id) {
|
||||||
|
// var node = RED.nodes.workspace(id)||RED.nodes.subflow(id)||RED.nodes.node(id);
|
||||||
|
// if (!newConfig.all.hasOwnProperty(id)) {
|
||||||
|
// if (!pending.added.hasOwnProperty(id)) {
|
||||||
|
// deleted[id] = true;
|
||||||
|
// conflicted[id] = node.changed;
|
||||||
|
// }
|
||||||
|
// } else if (JSON.stringify(currentConfig.all[id]) !== JSON.stringify(newConfig.all[id])) {
|
||||||
|
// changed[id] = true;
|
||||||
|
// conflicted[id] = node.changed;
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// Object.keys(newConfig.all).forEach(function(id) {
|
||||||
|
// if (!currentConfig.all.hasOwnProperty(id) && !pending.deleted.hasOwnProperty(id)) {
|
||||||
|
// added[id] = true;
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// // console.log("Added",added);
|
||||||
|
// // console.log("Deleted",deleted);
|
||||||
|
// // console.log("Changed",changed);
|
||||||
|
// // console.log("Conflicted",conflicted);
|
||||||
|
//
|
||||||
|
// var formatString = function(id) {
|
||||||
|
// return conflicted[id]?"!":(added[id]?"+":(deleted[id]?"-":(changed[id]?"~":" ")));
|
||||||
|
// }
|
||||||
|
// newConfig.tabOrder.forEach(function(tabId) {
|
||||||
|
// var tab = newConfig.tabs[tabId];
|
||||||
|
// console.log(formatString(tabId),"Flow:",tab.n.label, "("+tab.n.id+")");
|
||||||
|
// tab.nodes.forEach(function(node) {
|
||||||
|
// console.log(" ",formatString(node.id),node.type,node.name || node.id);
|
||||||
|
// })
|
||||||
|
// if (currentConfig.tabs[tabId]) {
|
||||||
|
// currentConfig.tabs[tabId].nodes.forEach(function(node) {
|
||||||
|
// if (deleted[node.id]) {
|
||||||
|
// console.log(" ",formatString(node.id),node.type,node.name || node.id);
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// currentConfig.tabOrder.forEach(function(tabId) {
|
||||||
|
// if (deleted[tabId]) {
|
||||||
|
// console.log(formatString(tabId),"Flow:",tab.n.label, "("+tab.n.id+")");
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// currentDiff = {
|
||||||
|
// currentConfig: currentConfig,
|
||||||
|
// newConfig: newConfig,
|
||||||
|
// added: added,
|
||||||
|
// deleted: deleted,
|
||||||
|
// changed: changed,
|
||||||
|
// conflicted: conflicted
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function showDiff() {
|
||||||
|
// if (currentDiff) {
|
||||||
|
// var list = $("#node-dialog-view-diff-diff");
|
||||||
|
// list.editableList('empty');
|
||||||
|
// var currentConfig = currentDiff.currentConfig;
|
||||||
|
// currentConfig.tabOrder.forEach(function(tabId) {
|
||||||
|
// var tab = currentConfig.tabs[tabId];
|
||||||
|
// list.editableList('addItem',{tab:tab})
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// $("#node-dialog-view-diff").dialog("open");
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
function save(skipValidation,force) {
|
||||||
if (RED.nodes.dirty()) {
|
if (RED.nodes.dirty()) {
|
||||||
//$("#debug-tab-clear").click(); // uncomment this to auto clear debug on deploy
|
//$("#debug-tab-clear").click(); // uncomment this to auto clear debug on deploy
|
||||||
|
|
||||||
if (!force) {
|
if (!skipValidation) {
|
||||||
var hasUnknown = false;
|
var hasUnknown = false;
|
||||||
var hasInvalid = false;
|
var hasInvalid = false;
|
||||||
var hasUnusedConfig = false;
|
var hasUnusedConfig = false;
|
||||||
@ -196,6 +554,7 @@ RED.deploy = (function() {
|
|||||||
$( "#node-dialog-confirm-deploy-config" ).hide();
|
$( "#node-dialog-confirm-deploy-config" ).hide();
|
||||||
$( "#node-dialog-confirm-deploy-unknown" ).hide();
|
$( "#node-dialog-confirm-deploy-unknown" ).hide();
|
||||||
$( "#node-dialog-confirm-deploy-unused" ).hide();
|
$( "#node-dialog-confirm-deploy-unused" ).hide();
|
||||||
|
$( "#node-dialog-confirm-deploy-conflict" ).hide();
|
||||||
|
|
||||||
var showWarning = false;
|
var showWarning = false;
|
||||||
|
|
||||||
@ -229,24 +588,28 @@ RED.deploy = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var nns = RED.nodes.createCompleteNodeSet();
|
var nns = RED.nodes.createCompleteNodeSet();
|
||||||
|
|
||||||
$("#btn-deploy-icon").removeClass('fa-download');
|
$("#btn-deploy-icon").removeClass('fa-download');
|
||||||
$("#btn-deploy-icon").addClass('spinner');
|
$("#btn-deploy-icon").addClass('spinner');
|
||||||
RED.nodes.dirty(false);
|
|
||||||
|
var data = {flows:nns};
|
||||||
|
|
||||||
|
if (!force) {
|
||||||
|
data.rev = RED.nodes.version();
|
||||||
|
}
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url:"flows",
|
url:"flows",
|
||||||
type: "POST",
|
type: "POST",
|
||||||
data: JSON.stringify(nns),
|
data: JSON.stringify(data),
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
headers: {
|
headers: {
|
||||||
"Node-RED-Deployment-Type":deploymentType
|
"Node-RED-Deployment-Type":deploymentType
|
||||||
}
|
}
|
||||||
}).done(function(data,textStatus,xhr) {
|
}).done(function(data,textStatus,xhr) {
|
||||||
|
RED.nodes.dirty(false);
|
||||||
|
RED.nodes.version(data.rev);
|
||||||
if (hasUnusedConfig) {
|
if (hasUnusedConfig) {
|
||||||
RED.notify(
|
RED.notify(
|
||||||
'<p>'+RED._("deploy.successfulDeploy")+'</p>'+
|
'<p>'+RED._("deploy.successfulDeploy")+'</p>'+
|
||||||
@ -264,10 +627,14 @@ RED.deploy = (function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
RED.nodes.eachConfig(function (confNode) {
|
RED.nodes.eachConfig(function (confNode) {
|
||||||
|
confNode.changed = false;
|
||||||
if (confNode.credentials) {
|
if (confNode.credentials) {
|
||||||
delete confNode.credentials;
|
delete confNode.credentials;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
RED.nodes.eachWorkspace(function(ws) {
|
||||||
|
ws.changed = false;
|
||||||
|
})
|
||||||
// Once deployed, cannot undo back to a clean state
|
// Once deployed, cannot undo back to a clean state
|
||||||
RED.history.markAllDirty();
|
RED.history.markAllDirty();
|
||||||
RED.view.redraw();
|
RED.view.redraw();
|
||||||
@ -276,6 +643,8 @@ RED.deploy = (function() {
|
|||||||
RED.nodes.dirty(true);
|
RED.nodes.dirty(true);
|
||||||
if (xhr.status === 401) {
|
if (xhr.status === 401) {
|
||||||
RED.notify(RED._("deploy.deployFailed",{message:RED._("user.notAuthorized")}),"error");
|
RED.notify(RED._("deploy.deployFailed",{message:RED._("user.notAuthorized")}),"error");
|
||||||
|
} else if (xhr.status === 409) {
|
||||||
|
resolveConflict(nns);
|
||||||
} else if (xhr.responseText) {
|
} else if (xhr.responseText) {
|
||||||
RED.notify(RED._("deploy.deployFailed",{message:xhr.responseText}),"error");
|
RED.notify(RED._("deploy.deployFailed",{message:xhr.responseText}),"error");
|
||||||
} else {
|
} else {
|
||||||
@ -287,7 +656,6 @@ RED.deploy = (function() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
init: init
|
init: init
|
||||||
}
|
}
|
||||||
|
@ -93,6 +93,7 @@ RED.workspaces = (function() {
|
|||||||
node: workspace,
|
node: workspace,
|
||||||
dirty: RED.nodes.dirty()
|
dirty: RED.nodes.dirty()
|
||||||
}
|
}
|
||||||
|
workspace.changed = true;
|
||||||
RED.history.push(historyEvent);
|
RED.history.push(historyEvent);
|
||||||
workspace_tabs.renameTab(workspace.id,label);
|
workspace_tabs.renameTab(workspace.id,label);
|
||||||
RED.nodes.dirty(true);
|
RED.nodes.dirty(true);
|
||||||
|
165
editor/sass/diff.scss
Normal file
165
editor/sass/diff.scss
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2016 IBM Corp.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
**/
|
||||||
|
|
||||||
|
|
||||||
|
#node-dialog-view-diff {
|
||||||
|
height: 600px;
|
||||||
|
|
||||||
|
.red-ui-editableList-container {
|
||||||
|
border-radius:1px;
|
||||||
|
padding:0;
|
||||||
|
}
|
||||||
|
ol {
|
||||||
|
position: absolute;
|
||||||
|
top:10px;
|
||||||
|
bottom:10px;
|
||||||
|
left:10px;
|
||||||
|
right:10px;
|
||||||
|
li {
|
||||||
|
padding: 0px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
.red-ui-editableList-item-content {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
.node-diff-tab {
|
||||||
|
border: 1px solid $secondary-border-color;
|
||||||
|
border-radius: 3px;
|
||||||
|
|
||||||
|
&.collapsed {
|
||||||
|
.node-diff-tab-title > .node-diff-chevron {
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
.node-diff-node-entry {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.node-diff-tab-stats {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-diff-chevron {
|
||||||
|
width: 15px;
|
||||||
|
text-align: center;
|
||||||
|
margin: 3px 5px 3px 5px;
|
||||||
|
transition: transform 0.1s ease-in-out;
|
||||||
|
|
||||||
|
}
|
||||||
|
.node-diff-node-entry {
|
||||||
|
padding: 0 0 0 5px;
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-bottom: 1px solid $secondary-border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.collapsed {
|
||||||
|
.node-diff-chevron {
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
.node-diff-node-entry-properties {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
table-layout:fixed;
|
||||||
|
}
|
||||||
|
td, th {
|
||||||
|
border: 1px solid $secondary-border-color;
|
||||||
|
padding: 3px 5px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:nth-child(1) {
|
||||||
|
width: 150px;
|
||||||
|
}
|
||||||
|
td:not(:first-child) {
|
||||||
|
width: calc(50% - 150px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.node-diff-column {
|
||||||
|
display:inline-block;
|
||||||
|
height:100%;
|
||||||
|
width:50%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
white-space:nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
&:first-child {
|
||||||
|
border-right: 1px solid $secondary-border-color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.node-diff-tab-title {
|
||||||
|
padding: 3px 3px 3px 0;
|
||||||
|
background: #f6f6f6;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-diff-node-entry-node {
|
||||||
|
vertical-align: middle;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 5px;
|
||||||
|
width: 24px;
|
||||||
|
height: 20px;
|
||||||
|
background: #ddd;
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1px solid #999;
|
||||||
|
background-position: 5% 50%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.palette-icon {
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
.palette_icon_container {
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.node-diff-node-entry-title {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.node-diff-node-entry-properties {
|
||||||
|
margin-left: 30px;
|
||||||
|
margin-right: 8px;
|
||||||
|
margin-bottom:8px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.node-diff-node-description {
|
||||||
|
color: $form-text-color;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-right: 5px;
|
||||||
|
padding-top: 5px;
|
||||||
|
display: inline-block;
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
display: table;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.node-diff-count { color: #999}
|
||||||
|
.node-diff-added { color: #009900}
|
||||||
|
.node-diff-deleted { color: #f80000}
|
||||||
|
.node-diff-changed { color: #f89406}
|
||||||
|
.node-diff-conflicted { color: purple}
|
@ -99,6 +99,11 @@
|
|||||||
background: $editor-button-background-primary-hover;
|
background: $editor-button-background-primary-hover;
|
||||||
color: $editor-button-color-primary !important;
|
color: $editor-button-color-primary !important;
|
||||||
}
|
}
|
||||||
|
&.disabled {
|
||||||
|
border-color: $form-input-border-color;
|
||||||
|
color: $workspace-button-color-disabled !important;
|
||||||
|
background: $editor-button-background;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&.disabled {
|
&.disabled {
|
||||||
background: none;
|
background: none;
|
||||||
|
@ -42,6 +42,7 @@
|
|||||||
@import "popover";
|
@import "popover";
|
||||||
@import "flow";
|
@import "flow";
|
||||||
@import "palette-editor";
|
@import "palette-editor";
|
||||||
|
@import "diff";
|
||||||
|
|
||||||
|
|
||||||
@import "ui/common/editableList";
|
@import "ui/common/editableList";
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
<meta name="mobile-web-app-capable" content="yes">
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
<!--
|
<!--
|
||||||
Copyright 2013, 2015 IBM Corp.
|
Copyright 2013, 2016 IBM Corp.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@ -93,8 +93,13 @@
|
|||||||
<div id="node-dialog-confirm-deploy-unknown" style="text-align: left; padding-top: 10px;" data-i18n="[prepend]deploy.confirm.unknown;[append]deploy.confirm.confirm">
|
<div id="node-dialog-confirm-deploy-unknown" style="text-align: left; padding-top: 10px;" data-i18n="[prepend]deploy.confirm.unknown;[append]deploy.confirm.confirm">
|
||||||
<ul style="font-size: 0.9em; width: 400px; margin: 10px auto; text-align: left;" id="node-dialog-confirm-deploy-unknown-list"></ul>
|
<ul style="font-size: 0.9em; width: 400px; margin: 10px auto; text-align: left;" id="node-dialog-confirm-deploy-unknown-list"></ul>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="node-dialog-confirm-deploy-conflict" style="text-align: left; padding-top: 10px;" data-i18n="[prepend]deploy.confirm.conflict;[append]deploy.confirm.confirm">
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="node-dialog-view-diff" class="hide">
|
||||||
|
<ol id="node-dialog-view-diff-diff"></ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="node-dialog-library-save-confirm" class="hide">
|
<div id="node-dialog-library-save-confirm" class="hide">
|
||||||
<form class="form-horizontal">
|
<form class="form-horizontal">
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright 2014, 2015 IBM Corp.
|
* Copyright 2014, 2016 IBM Corp.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -25,13 +25,23 @@ module.exports = {
|
|||||||
log = runtime.log;
|
log = runtime.log;
|
||||||
},
|
},
|
||||||
get: function(req,res) {
|
get: function(req,res) {
|
||||||
log.audit({event: "flows.get"},req);
|
var version = req.get("Node-RED-API-Version")||"v1";
|
||||||
res.json(redNodes.getFlows());
|
if (version === "v1") {
|
||||||
|
log.audit({event: "flows.get",version:"v1"},req);
|
||||||
|
res.json(redNodes.getFlows().flows);
|
||||||
|
} else if (version === "v2") {
|
||||||
|
log.audit({event: "flows.get",version:"v2"},req);
|
||||||
|
res.json(redNodes.getFlows());
|
||||||
|
} else {
|
||||||
|
log.audit({event: "flows.get",version:version,error:"bad_api_version"},req);
|
||||||
|
res.status(400).json({error:"bad_api_version"});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
post: function(req,res) {
|
post: function(req,res) {
|
||||||
|
var version = req.get("Node-RED-API-Version")||"v1";
|
||||||
var flows = req.body;
|
var flows = req.body;
|
||||||
var deploymentType = req.get("Node-RED-Deployment-Type")||"full";
|
var deploymentType = req.get("Node-RED-Deployment-Type")||"full";
|
||||||
log.audit({event: "flows.set",type:deploymentType},req);
|
log.audit({event: "flows.set",type:deploymentType,version:version},req);
|
||||||
if (deploymentType === 'reload') {
|
if (deploymentType === 'reload') {
|
||||||
redNodes.loadFlows().then(function() {
|
redNodes.loadFlows().then(function() {
|
||||||
res.status(204).end();
|
res.status(204).end();
|
||||||
@ -41,8 +51,28 @@ module.exports = {
|
|||||||
res.status(500).json({error:"unexpected_error", message:err.message});
|
res.status(500).json({error:"unexpected_error", message:err.message});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
redNodes.setFlows(flows,deploymentType).then(function() {
|
var flowConfig = flows;
|
||||||
res.status(204).end();
|
if (version === "v2") {
|
||||||
|
flowConfig = flows.flows;
|
||||||
|
if (flows.hasOwnProperty('rev')) {
|
||||||
|
var currentVersion = redNodes.getFlows().rev;
|
||||||
|
if (currentVersion !== flows.rev) {
|
||||||
|
//TODO: log warning
|
||||||
|
return res.status(409).json({error:"version_mismatch"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (version !== 'v1') {
|
||||||
|
log.audit({event: "flows.set",version:version,error:"bad_api_version"},req);
|
||||||
|
return res.status(400).json({error:"bad_api_version"});
|
||||||
|
}
|
||||||
|
redNodes.setFlows(flowConfig,deploymentType).then(function(flowId) {
|
||||||
|
if (version === "v1") {
|
||||||
|
res.status(204).end();
|
||||||
|
} else if (version === "v2") {
|
||||||
|
res.json({rev:flowId});
|
||||||
|
} else {
|
||||||
|
// TODO: invalid version
|
||||||
|
}
|
||||||
}).otherwise(function(err) {
|
}).otherwise(function(err) {
|
||||||
log.warn(log._("api.flows.error-save",{message:err.message}));
|
log.warn(log._("api.flows.error-save",{message:err.message}));
|
||||||
log.warn(err.stack);
|
log.warn(err.stack);
|
||||||
|
@ -121,12 +121,15 @@
|
|||||||
"confirm": {
|
"confirm": {
|
||||||
"button": {
|
"button": {
|
||||||
"confirm": "Confirm deploy",
|
"confirm": "Confirm deploy",
|
||||||
"cancel": "Cancel"
|
"review": "Review differences",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"merge": "Merge changes"
|
||||||
},
|
},
|
||||||
"undeployedChanges": "You have undeployed changes.\n\nLeaving this page will lose these changes.",
|
"undeployedChanges": "You have undeployed changes.\n\nLeaving this page will lose these changes.",
|
||||||
"improperlyConfigured": "The workspace contains some nodes that are not properly configured:",
|
"improperlyConfigured": "The workspace contains some nodes that are not properly configured:",
|
||||||
"unknown": "The workspace contains some unknown node types:",
|
"unknown": "The workspace contains some unknown node types:",
|
||||||
"confirm": "Are you sure you want to deploy?"
|
"confirm": "Are you sure you want to deploy?",
|
||||||
|
"conflict": "The server is running a more recent set of flows."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"subflow": {
|
"subflow": {
|
||||||
|
@ -66,33 +66,47 @@ function init(runtime) {
|
|||||||
typeEventRegistered = true;
|
typeEventRegistered = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function load() {
|
|
||||||
|
function loadFlows() {
|
||||||
return storage.getFlows().then(function(config) {
|
return storage.getFlows().then(function(config) {
|
||||||
return credentials.load(config.credentials).then(function() {
|
return credentials.load(config.credentials).then(function() {
|
||||||
return setConfig(config.flows,"load");
|
return config;
|
||||||
});
|
});
|
||||||
}).otherwise(function(err) {
|
}).otherwise(function(err) {
|
||||||
log.warn(log._("nodes.flows.error",{message:err.toString()}));
|
log.warn(log._("nodes.flows.error",{message:err.toString()}));
|
||||||
console.log(err.stack);
|
console.log(err.stack);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
function load() {
|
||||||
|
return setFlows(null,"load",false);
|
||||||
|
}
|
||||||
|
|
||||||
function setConfig(_config,type,muteLog) {
|
/*
|
||||||
var config = clone(_config);
|
* _config - new node array configuration
|
||||||
|
* type - full/nodes/flows/load (default full)
|
||||||
|
* muteLog - don't emit the standard log messages (used for individual flow api)
|
||||||
|
*/
|
||||||
|
function setFlows(_config,type,muteLog) {
|
||||||
type = type||"full";
|
type = type||"full";
|
||||||
|
|
||||||
var configSavePromise = null;
|
var configSavePromise = null;
|
||||||
|
var config = null;
|
||||||
var diff;
|
var diff;
|
||||||
var newFlowConfig = flowUtil.parseConfig(clone(config));
|
var newFlowConfig;
|
||||||
if (type !== 'full' && type !== 'load') {
|
|
||||||
diff = flowUtil.diffConfigs(activeFlowConfig,newFlowConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === 'load') {
|
if (type === "load") {
|
||||||
type = 'full';
|
configSavePromise = loadFlows().then(function(_config) {
|
||||||
configSavePromise = when.resolve();
|
config = clone(_config.flows);
|
||||||
|
newFlowConfig = flowUtil.parseConfig(clone(config));
|
||||||
|
type = "full";
|
||||||
|
return _config.rev;
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
|
config = clone(_config);
|
||||||
|
newFlowConfig = flowUtil.parseConfig(clone(config));
|
||||||
|
if (type !== 'full') {
|
||||||
|
diff = flowUtil.diffConfigs(activeFlowConfig,newFlowConfig);
|
||||||
|
}
|
||||||
credentials.clean(config);
|
credentials.clean(config);
|
||||||
var credsDirty = credentials.dirty();
|
var credsDirty = credentials.dirty();
|
||||||
configSavePromise = credentials.export().then(function(creds) {
|
configSavePromise = credentials.export().then(function(creds) {
|
||||||
@ -101,18 +115,22 @@ function setConfig(_config,type,muteLog) {
|
|||||||
credentialsDirty:credsDirty,
|
credentialsDirty:credsDirty,
|
||||||
credentials: creds
|
credentials: creds
|
||||||
}
|
}
|
||||||
storage.saveFlows(saveConfig);
|
return storage.saveFlows(saveConfig);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return configSavePromise
|
return configSavePromise
|
||||||
.then(function() {
|
.then(function(flowRevision) {
|
||||||
activeConfig = config;
|
activeConfig = {
|
||||||
|
flows:config,
|
||||||
|
rev:flowRevision
|
||||||
|
};
|
||||||
activeFlowConfig = newFlowConfig;
|
activeFlowConfig = newFlowConfig;
|
||||||
if (started) {
|
if (started) {
|
||||||
return stop(type,diff,muteLog).then(function() {
|
return stop(type,diff,muteLog).then(function() {
|
||||||
context.clean(activeFlowConfig);
|
context.clean(activeFlowConfig);
|
||||||
start(type,diff,muteLog);
|
start(type,diff,muteLog);
|
||||||
|
return flowRevision;
|
||||||
}).otherwise(function(err) {
|
}).otherwise(function(err) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -143,7 +161,7 @@ function eachNode(cb) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getConfig() {
|
function getFlows() {
|
||||||
return activeConfig;
|
return activeConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,8 +359,8 @@ function checkTypeInUse(id) {
|
|||||||
throw new Error(log._("nodes.index.unrecognised-id", {id:id}));
|
throw new Error(log._("nodes.index.unrecognised-id", {id:id}));
|
||||||
} else {
|
} else {
|
||||||
var inUse = {};
|
var inUse = {};
|
||||||
var config = getConfig();
|
var config = getFlows();
|
||||||
config.forEach(function(n) {
|
config.flows.forEach(function(n) {
|
||||||
inUse[n.type] = (inUse[n.type]||0)+1;
|
inUse[n.type] = (inUse[n.type]||0)+1;
|
||||||
});
|
});
|
||||||
var nodesInUse = [];
|
var nodesInUse = [];
|
||||||
@ -418,10 +436,10 @@ function addFlow(flow) {
|
|||||||
nodes.push(node);
|
nodes.push(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var newConfig = clone(activeConfig);
|
var newConfig = clone(activeConfig.flows);
|
||||||
newConfig = newConfig.concat(nodes);
|
newConfig = newConfig.concat(nodes);
|
||||||
|
|
||||||
return setConfig(newConfig,'flows',true).then(function() {
|
return setFlows(newConfig,'flows',true).then(function() {
|
||||||
log.info(log._("nodes.flows.added-flow",{label:(flow.label?flow.label+" ":"")+"["+flow.id+"]"}));
|
log.info(log._("nodes.flows.added-flow",{label:(flow.label?flow.label+" ":"")+"["+flow.id+"]"}));
|
||||||
return flow.id;
|
return flow.id;
|
||||||
});
|
});
|
||||||
@ -501,7 +519,7 @@ function updateFlow(id,newFlow) {
|
|||||||
}
|
}
|
||||||
label = activeFlowConfig.flows[id].label;
|
label = activeFlowConfig.flows[id].label;
|
||||||
}
|
}
|
||||||
var newConfig = clone(activeConfig);
|
var newConfig = clone(activeConfig.flows);
|
||||||
var nodes;
|
var nodes;
|
||||||
|
|
||||||
if (id === 'global') {
|
if (id === 'global') {
|
||||||
@ -539,7 +557,7 @@ function updateFlow(id,newFlow) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
newConfig = newConfig.concat(nodes);
|
newConfig = newConfig.concat(nodes);
|
||||||
return setConfig(newConfig,'flows',true).then(function() {
|
return setFlows(newConfig,'flows',true).then(function() {
|
||||||
log.info(log._("nodes.flows.updated-flow",{label:(label?label+" ":"")+"["+id+"]"}));
|
log.info(log._("nodes.flows.updated-flow",{label:(label?label+" ":"")+"["+id+"]"}));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -556,12 +574,12 @@ function removeFlow(id) {
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
var newConfig = clone(activeConfig);
|
var newConfig = clone(activeConfig.flows);
|
||||||
newConfig = newConfig.filter(function(node) {
|
newConfig = newConfig.filter(function(node) {
|
||||||
return node.z !== id && node.id !== id;
|
return node.z !== id && node.id !== id;
|
||||||
});
|
});
|
||||||
|
|
||||||
return setConfig(newConfig,'flows',true).then(function() {
|
return setFlows(newConfig,'flows',true).then(function() {
|
||||||
log.info(log._("nodes.flows.removed-flow",{label:(flow.label?flow.label+" ":"")+"["+flow.id+"]"}));
|
log.info(log._("nodes.flows.removed-flow",{label:(flow.label?flow.label+" ":"")+"["+flow.id+"]"}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -581,7 +599,7 @@ module.exports = {
|
|||||||
/**
|
/**
|
||||||
* Gets the current flow configuration
|
* Gets the current flow configuration
|
||||||
*/
|
*/
|
||||||
getFlows: getConfig,
|
getFlows: getFlows,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the current active config.
|
* Sets the current active config.
|
||||||
@ -589,7 +607,7 @@ module.exports = {
|
|||||||
* @param type the type of deployment to do: full (default), nodes, flows, load
|
* @param type the type of deployment to do: full (default), nodes, flows, load
|
||||||
* @return a promise for the saving/starting of the new flow
|
* @return a promise for the saving/starting of the new flow
|
||||||
*/
|
*/
|
||||||
setFlows: setConfig,
|
setFlows: setFlows,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the current flow configuration
|
* Starts the current flow configuration
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
var when = require('when');
|
var when = require('when');
|
||||||
var Path = require('path');
|
var Path = require('path');
|
||||||
|
var crypto = require('crypto');
|
||||||
|
|
||||||
var log = require("../log");
|
var log = require("../log");
|
||||||
|
|
||||||
var runtime;
|
var runtime;
|
||||||
@ -57,10 +59,12 @@ var storageModuleInterface = {
|
|||||||
getFlows: function() {
|
getFlows: function() {
|
||||||
return storageModule.getFlows().then(function(flows) {
|
return storageModule.getFlows().then(function(flows) {
|
||||||
return storageModule.getCredentials().then(function(creds) {
|
return storageModule.getCredentials().then(function(creds) {
|
||||||
return {
|
var result = {
|
||||||
flows: flows,
|
flows: flows,
|
||||||
credentials: creds
|
credentials: creds
|
||||||
}
|
};
|
||||||
|
result.rev = crypto.createHash('md5').update(JSON.stringify(result)).digest("hex");
|
||||||
|
return result;
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -73,9 +77,12 @@ var storageModuleInterface = {
|
|||||||
} else {
|
} else {
|
||||||
credentialSavePromise = when.resolve();
|
credentialSavePromise = when.resolve();
|
||||||
}
|
}
|
||||||
|
delete config.credentialsDirty;
|
||||||
|
|
||||||
return credentialSavePromise.then(function() {
|
return credentialSavePromise.then(function() {
|
||||||
return storageModule.saveFlows(flows);
|
return storageModule.saveFlows(flows).then(function() {
|
||||||
|
return crypto.createHash('md5').update(JSON.stringify(config)).digest("hex");
|
||||||
|
})
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// getCredentials: function() {
|
// getCredentials: function() {
|
||||||
|
@ -34,12 +34,12 @@ describe("flows api", function() {
|
|||||||
app.post("/flows",flows.post);
|
app.post("/flows",flows.post);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns flow', function(done) {
|
it('returns flow - v1', function(done) {
|
||||||
flows.init({
|
flows.init({
|
||||||
settings: {},
|
settings: {},
|
||||||
log:{warn:function(){},_:function(){},audit:function(){}},
|
log:{warn:function(){},_:function(){},audit:function(){}},
|
||||||
nodes:{
|
nodes:{
|
||||||
getFlows: function() { return [1,2,3]; }
|
getFlows: function() { return {rev:"123",flows:[1,2,3]}; }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
request(app)
|
request(app)
|
||||||
@ -50,13 +50,60 @@ describe("flows api", function() {
|
|||||||
if (err) {
|
if (err) {
|
||||||
return done(err);
|
return done(err);
|
||||||
}
|
}
|
||||||
res.body.should.be.an.Array;
|
try {
|
||||||
res.body.should.have.lengthOf(3);
|
res.body.should.have.lengthOf(3);
|
||||||
done();
|
done();
|
||||||
|
} catch(e) {
|
||||||
|
return done(e);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it('returns flow - v2', function(done) {
|
||||||
it('sets flows - default', function(done) {
|
flows.init({
|
||||||
|
settings: {},
|
||||||
|
log:{warn:function(){},_:function(){},audit:function(){}},
|
||||||
|
nodes:{
|
||||||
|
getFlows: function() { return {rev:"123",flows:[1,2,3]}; }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
request(app)
|
||||||
|
.get('/flows')
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.set('Node-RED-API-Version','v2')
|
||||||
|
.expect(200)
|
||||||
|
.end(function(err,res) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
res.body.should.have.a.property('rev','123');
|
||||||
|
res.body.should.have.a.property('flows');
|
||||||
|
res.body.flows.should.have.lengthOf(3);
|
||||||
|
done();
|
||||||
|
} catch(e) {
|
||||||
|
return done(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns flow - bad version', function(done) {
|
||||||
|
request(app)
|
||||||
|
.get('/flows')
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.set('Node-RED-API-Version','xxx')
|
||||||
|
.expect(400)
|
||||||
|
.end(function(err,res) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
res.body.should.have.a.property('error','bad_api_version');
|
||||||
|
done();
|
||||||
|
} catch(e) {
|
||||||
|
return done(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('sets flows - default - v1', function(done) {
|
||||||
var setFlows = sinon.spy(function() { return when.resolve();});
|
var setFlows = sinon.spy(function() { return when.resolve();});
|
||||||
flows.init({
|
flows.init({
|
||||||
log:{warn:function(){},_:function(){},audit:function(){}},
|
log:{warn:function(){},_:function(){},audit:function(){}},
|
||||||
@ -77,7 +124,7 @@ describe("flows api", function() {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('sets flows - non-default', function(done) {
|
it('sets flows - non-default - v1', function(done) {
|
||||||
var setFlows = sinon.spy(function() { return when.resolve();});
|
var setFlows = sinon.spy(function() { return when.resolve();});
|
||||||
flows.init({
|
flows.init({
|
||||||
log:{warn:function(){},_:function(){},audit:function(){}},
|
log:{warn:function(){},_:function(){},audit:function(){}},
|
||||||
@ -100,6 +147,96 @@ describe("flows api", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('set flows - rejects mismatched revision - v2', function(done) {
|
||||||
|
var setFlows = sinon.spy(function() { return when.resolve();});
|
||||||
|
var getFlows = sinon.spy(function() { return {rev:123,flows:[1,2,3]}});
|
||||||
|
flows.init({
|
||||||
|
log:{warn:function(){},_:function(){},audit:function(){}},
|
||||||
|
nodes:{
|
||||||
|
setFlows: setFlows,
|
||||||
|
getFlows: getFlows
|
||||||
|
}
|
||||||
|
});
|
||||||
|
request(app)
|
||||||
|
.post('/flows')
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.set('Node-RED-API-Version','v2')
|
||||||
|
.send({rev:456,flows:[4,5,6]})
|
||||||
|
.expect(409)
|
||||||
|
.end(function(err,res) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
res.body.should.have.property("error","version_mismatch");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('set flows - rev provided - v2', function(done) {
|
||||||
|
var setFlows = sinon.spy(function() { return when.resolve(456);});
|
||||||
|
var getFlows = sinon.spy(function() { return {rev:123,flows:[1,2,3]}});
|
||||||
|
flows.init({
|
||||||
|
log:{warn:function(){},_:function(){},audit:function(){}},
|
||||||
|
nodes:{
|
||||||
|
setFlows: setFlows,
|
||||||
|
getFlows: getFlows
|
||||||
|
}
|
||||||
|
});
|
||||||
|
request(app)
|
||||||
|
.post('/flows')
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.set('Node-RED-API-Version','v2')
|
||||||
|
.send({rev:123,flows:[4,5,6]})
|
||||||
|
.expect(200)
|
||||||
|
.end(function(err,res) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
res.body.should.have.property("rev",456);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('set flows - no rev provided - v2', function(done) {
|
||||||
|
var setFlows = sinon.spy(function() { return when.resolve(456);});
|
||||||
|
var getFlows = sinon.spy(function() { return {rev:123,flows:[1,2,3]}});
|
||||||
|
flows.init({
|
||||||
|
log:{warn:function(){},_:function(){},audit:function(){}},
|
||||||
|
nodes:{
|
||||||
|
setFlows: setFlows,
|
||||||
|
getFlows: getFlows
|
||||||
|
}
|
||||||
|
});
|
||||||
|
request(app)
|
||||||
|
.post('/flows')
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.set('Node-RED-API-Version','v2')
|
||||||
|
.send({flows:[4,5,6]})
|
||||||
|
.expect(200)
|
||||||
|
.end(function(err,res) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
res.body.should.have.property("rev",456);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('sets flow - bad version', function(done) {
|
||||||
|
request(app)
|
||||||
|
.post('/flows')
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.set('Node-RED-API-Version','xxx')
|
||||||
|
.expect(400)
|
||||||
|
.end(function(err,res) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
res.body.should.have.a.property('error','bad_api_version');
|
||||||
|
done();
|
||||||
|
} catch(e) {
|
||||||
|
return done(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
it('reloads flows', function(done) {
|
it('reloads flows', function(done) {
|
||||||
var loadFlows = sinon.spy(function() { return when.resolve(); });
|
var loadFlows = sinon.spy(function() { return when.resolve(); });
|
||||||
flows.init({
|
flows.init({
|
||||||
|
@ -116,22 +116,31 @@ describe('flows/index', function() {
|
|||||||
flows.setFlows(originalConfig).then(function() {
|
flows.setFlows(originalConfig).then(function() {
|
||||||
credentialsClean.called.should.be.true;
|
credentialsClean.called.should.be.true;
|
||||||
storage.hasOwnProperty('conf').should.be.true;
|
storage.hasOwnProperty('conf').should.be.true;
|
||||||
flows.getFlows().should.eql(originalConfig);
|
flows.getFlows().flows.should.eql(originalConfig);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
it('sets the full flow for type load', function(done) {
|
it('loads the full flow for type load', function(done) {
|
||||||
var originalConfig = [
|
var originalConfig = [
|
||||||
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
|
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
|
||||||
{id:"t1",type:"tab"}
|
{id:"t1",type:"tab"}
|
||||||
];
|
];
|
||||||
flows.init({settings:{},storage:storage});
|
var loadStorage = {
|
||||||
|
saveFlows: function(conf) {
|
||||||
|
loadStorage.conf = conf;
|
||||||
|
return when.resolve(456);
|
||||||
|
},
|
||||||
|
getFlows: function() {
|
||||||
|
return when.resolve({flows:originalConfig,rev:123})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flows.init({settings:{},storage:loadStorage});
|
||||||
flows.setFlows(originalConfig,"load").then(function() {
|
flows.setFlows(originalConfig,"load").then(function() {
|
||||||
credentialsClean.called.should.be.false;
|
credentialsClean.called.should.be.false;
|
||||||
// 'load' type does not trigger a save
|
// 'load' type does not trigger a save
|
||||||
storage.hasOwnProperty('conf').should.be.false;
|
loadStorage.hasOwnProperty('conf').should.be.false;
|
||||||
flows.getFlows().should.eql(originalConfig);
|
flows.getFlows().flows.should.eql(originalConfig);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -147,10 +156,10 @@ describe('flows/index', function() {
|
|||||||
credentialsClean.called.should.be.true;
|
credentialsClean.called.should.be.true;
|
||||||
storage.hasOwnProperty('conf').should.be.true;
|
storage.hasOwnProperty('conf').should.be.true;
|
||||||
var cleanedFlows = flows.getFlows();
|
var cleanedFlows = flows.getFlows();
|
||||||
storage.conf.flows.should.eql(cleanedFlows);
|
storage.conf.flows.should.eql(cleanedFlows.flows);
|
||||||
cleanedFlows.should.not.eql(originalConfig);
|
cleanedFlows.flows.should.not.eql(originalConfig);
|
||||||
cleanedFlows[0].credentials = {"a":1};
|
cleanedFlows.flows[0].credentials = {"a":1};
|
||||||
cleanedFlows.should.eql(originalConfig);
|
cleanedFlows.flows.should.eql(originalConfig);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -170,7 +179,7 @@ describe('flows/index', function() {
|
|||||||
|
|
||||||
events.once('nodes-started',function() {
|
events.once('nodes-started',function() {
|
||||||
flows.setFlows(newConfig,"nodes").then(function() {
|
flows.setFlows(newConfig,"nodes").then(function() {
|
||||||
flows.getFlows().should.eql(newConfig);
|
flows.getFlows().flows.should.eql(newConfig);
|
||||||
flowCreate.flows['t1'].update.called.should.be.true;
|
flowCreate.flows['t1'].update.called.should.be.true;
|
||||||
flowCreate.flows['t2'].start.called.should.be.true;
|
flowCreate.flows['t2'].start.called.should.be.true;
|
||||||
flowCreate.flows['_GLOBAL_'].update.called.should.be.true;
|
flowCreate.flows['_GLOBAL_'].update.called.should.be.true;
|
||||||
@ -199,7 +208,7 @@ describe('flows/index', function() {
|
|||||||
|
|
||||||
events.once('nodes-started',function() {
|
events.once('nodes-started',function() {
|
||||||
flows.setFlows(newConfig,"nodes").then(function() {
|
flows.setFlows(newConfig,"nodes").then(function() {
|
||||||
flows.getFlows().should.eql(newConfig);
|
flows.getFlows().flows.should.eql(newConfig);
|
||||||
flowCreate.flows['t1'].update.called.should.be.true;
|
flowCreate.flows['t1'].update.called.should.be.true;
|
||||||
flowCreate.flows['t2'].start.called.should.be.true;
|
flowCreate.flows['t2'].start.called.should.be.true;
|
||||||
flowCreate.flows['_GLOBAL_'].update.called.should.be.true;
|
flowCreate.flows['_GLOBAL_'].update.called.should.be.true;
|
||||||
@ -229,7 +238,7 @@ describe('flows/index', function() {
|
|||||||
credentialsLoad.called.should.be.true;
|
credentialsLoad.called.should.be.true;
|
||||||
// 'load' type does not trigger a save
|
// 'load' type does not trigger a save
|
||||||
storage.hasOwnProperty('conf').should.be.false;
|
storage.hasOwnProperty('conf').should.be.false;
|
||||||
flows.getFlows().should.eql(originalConfig);
|
flows.getFlows().flows.should.eql(originalConfig);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -535,7 +544,7 @@ describe('flows/index', function() {
|
|||||||
{id:"t2-3",z:"t1",type:"test"}
|
{id:"t2-3",z:"t1",type:"test"}
|
||||||
]
|
]
|
||||||
}).then(function(id) {
|
}).then(function(id) {
|
||||||
flows.getFlows().should.have.lengthOf(6);
|
flows.getFlows().flows.should.have.lengthOf(6);
|
||||||
var createdFlows = Object.keys(flowCreate.flows);
|
var createdFlows = Object.keys(flowCreate.flows);
|
||||||
createdFlows.should.have.lengthOf(3);
|
createdFlows.should.have.lengthOf(3);
|
||||||
createdFlows[2].should.eql(id);
|
createdFlows[2].should.eql(id);
|
||||||
|
@ -39,13 +39,14 @@ describe("red/nodes/index", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
var testFlows = [{"type":"test","id":"tab1","label":"Sheet 1"}];
|
var testFlows = [{"type":"test","id":"tab1","label":"Sheet 1"}];
|
||||||
|
var testCredentials = {"tab1":{"b":1,"c":2}};
|
||||||
var storage = {
|
var storage = {
|
||||||
getFlows: function() {
|
getFlows: function() {
|
||||||
return when({flows:testFlows,credentials:{"tab1":{"b":1,"c":2}}});
|
return when({red:123,flows:testFlows,credentials:testCredentials});
|
||||||
},
|
},
|
||||||
saveFlows: function(conf) {
|
saveFlows: function(conf) {
|
||||||
should.deepEqual(testFlows, conf.flows);
|
should.deepEqual(testFlows, conf.flows);
|
||||||
return when();
|
return when.resolve(123);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -84,7 +85,9 @@ describe("red/nodes/index", function() {
|
|||||||
it('flows should be initialised',function(done) {
|
it('flows should be initialised',function(done) {
|
||||||
index.init(runtime);
|
index.init(runtime);
|
||||||
index.loadFlows().then(function() {
|
index.loadFlows().then(function() {
|
||||||
should.deepEqual(testFlows, index.getFlows());
|
console.log(testFlows);
|
||||||
|
console.log(index.getFlows());
|
||||||
|
should.deepEqual(testFlows, index.getFlows().flows);
|
||||||
done();
|
done();
|
||||||
}).otherwise(function(err) {
|
}).otherwise(function(err) {
|
||||||
done(err);
|
done(err);
|
||||||
@ -173,8 +176,8 @@ describe("red/nodes/index", function() {
|
|||||||
index.registerType('test', TestNode);
|
index.registerType('test', TestNode);
|
||||||
index.loadFlows().then(function() {
|
index.loadFlows().then(function() {
|
||||||
var info = index.disableNode("5678");
|
var info = index.disableNode("5678");
|
||||||
registry.disableNode.calledOnce.should.be.true;
|
registry.disableNode.calledOnce.should.be.true();
|
||||||
registry.disableNode.calledWith("5678").should.be.true;
|
registry.disableNode.calledWith("5678").should.be.true();
|
||||||
info.should.eql(randomNodeInfo);
|
info.should.eql(randomNodeInfo);
|
||||||
done();
|
done();
|
||||||
}).otherwise(function(err) {
|
}).otherwise(function(err) {
|
||||||
|
@ -79,6 +79,7 @@ describe("red/storage/index", function() {
|
|||||||
},
|
},
|
||||||
saveFlows : function (flows) {
|
saveFlows : function (flows) {
|
||||||
flows.should.be.true;
|
flows.should.be.true;
|
||||||
|
return when.resolve("");
|
||||||
},
|
},
|
||||||
getCredentials : function() {
|
getCredentials : function() {
|
||||||
calledFlagGetCredentials = true;
|
calledFlagGetCredentials = true;
|
||||||
|
Loading…
Reference in New Issue
Block a user