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() {
|
||||
$.ajax({
|
||||
headers: {
|
||||
"Accept":"application/json"
|
||||
"Accept":"application/json",
|
||||
},
|
||||
cache: false,
|
||||
url: 'flows',
|
||||
success: function(nodes) {
|
||||
var currentHash = window.location.hash;
|
||||
RED.nodes.import(nodes);
|
||||
RED.nodes.version(nodes.rev);
|
||||
RED.nodes.import(nodes.flows);
|
||||
RED.nodes.dirty(false);
|
||||
RED.view.redraw(true);
|
||||
if (/^#flow\/.+$/.test(currentHash)) {
|
||||
|
@ -23,11 +23,22 @@ RED.nodes = (function() {
|
||||
var workspaces = {};
|
||||
var workspacesOrder =[];
|
||||
var subflows = {};
|
||||
var loadedFlowVersion = null;
|
||||
var pending = {
|
||||
deleted: {},
|
||||
added: {}
|
||||
};
|
||||
|
||||
var dirty = false;
|
||||
|
||||
function setDirty(d) {
|
||||
dirty = d;
|
||||
if (!d) {
|
||||
pending = {
|
||||
deleted: {},
|
||||
added: {}
|
||||
};
|
||||
}
|
||||
RED.events.emit("nodes:change",{dirty:dirty});
|
||||
}
|
||||
|
||||
@ -175,6 +186,8 @@ RED.nodes = (function() {
|
||||
}
|
||||
nodes.push(n);
|
||||
}
|
||||
delete pending.deleted[n.id];
|
||||
pending.added[n.id] = true;
|
||||
RED.events.emit('nodes:add',n);
|
||||
}
|
||||
function addLink(l) {
|
||||
@ -240,6 +253,12 @@ RED.nodes = (function() {
|
||||
if (node && node._def.onremove) {
|
||||
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};
|
||||
}
|
||||
|
||||
@ -252,6 +271,8 @@ RED.nodes = (function() {
|
||||
|
||||
function addWorkspace(ws) {
|
||||
workspaces[ws.id] = ws;
|
||||
pending.added[ws.id] = true;
|
||||
delete pending.deleted[ws.id];
|
||||
ws._def = {
|
||||
defaults: {
|
||||
label: {value:""}
|
||||
@ -289,6 +310,8 @@ RED.nodes = (function() {
|
||||
var result = removeNode(removedNodes[n].id);
|
||||
removedLinks = removedLinks.concat(result.links);
|
||||
}
|
||||
pending.deleted[id] = true;
|
||||
delete pending.added[id]
|
||||
return {nodes:removedNodes,links:removedLinks};
|
||||
}
|
||||
|
||||
@ -318,6 +341,8 @@ RED.nodes = (function() {
|
||||
outputs: sf.out.length
|
||||
}
|
||||
subflows[sf.id] = sf;
|
||||
delete pending.deleted[sf.id];
|
||||
pending.added[sf.id] = true;
|
||||
RED.nodes.registerType("subflow:"+sf.id, {
|
||||
defaults:{name:{value:""}},
|
||||
info: sf.info,
|
||||
@ -341,6 +366,8 @@ RED.nodes = (function() {
|
||||
}
|
||||
function removeSubflow(sf) {
|
||||
delete subflows[sf.id];
|
||||
delete pending.added[sf.id];
|
||||
pending.deleted[sf.id] = true;
|
||||
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) {
|
||||
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) {
|
||||
if (def.defaults.hasOwnProperty(d)) {
|
||||
configNode[d] = n[d];
|
||||
configNode._config[d] = JSON.stringify(n[d]);
|
||||
}
|
||||
}
|
||||
if (def.hasOwnProperty('credentials') && n.hasOwnProperty('credentials')) {
|
||||
@ -864,7 +892,7 @@ RED.nodes = (function() {
|
||||
if (n.type !== "workspace" && n.type !== "tab" && n.type !== "subflow") {
|
||||
def = registry.getNodeType(n.type);
|
||||
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 (subflow_blacklist[n.z]) {
|
||||
continue;
|
||||
@ -947,8 +975,11 @@ RED.nodes = (function() {
|
||||
for (d in node._def.defaults) {
|
||||
if (node._def.defaults.hasOwnProperty(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')) {
|
||||
node.credentials = {};
|
||||
for (d in node._def.credentials) {
|
||||
@ -959,9 +990,6 @@ RED.nodes = (function() {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node.credentials) {
|
||||
console.log(node);
|
||||
}
|
||||
addNode(node);
|
||||
RED.editor.validateNode(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 {
|
||||
registry:registry,
|
||||
setNodeList: registry.setNodeList,
|
||||
@ -1185,11 +1221,15 @@ RED.nodes = (function() {
|
||||
|
||||
node: getNode,
|
||||
|
||||
version: flowVersion,
|
||||
|
||||
filterNodes: filterNodes,
|
||||
filterLinks: filterLinks,
|
||||
|
||||
import: importNodes,
|
||||
|
||||
pending: function() { return pending },
|
||||
|
||||
getAllFlowNodes: getAllFlowNodes,
|
||||
createExportableNodeSet: createExportableNodeSet,
|
||||
createCompleteNodeSet: createCompleteNodeSet,
|
||||
|
@ -84,6 +84,7 @@ RED.settings = (function () {
|
||||
if (auth_tokens) {
|
||||
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);
|
||||
}
|
||||
|
||||
var currentDiff = null;
|
||||
|
||||
/**
|
||||
* options:
|
||||
@ -76,7 +77,7 @@ RED.deploy = (function() {
|
||||
$('#btn-deploy').click(function() { save(); });
|
||||
|
||||
$( "#node-dialog-confirm-deploy" ).dialog({
|
||||
title: "Confirm deploy",
|
||||
title: RED._('deploy.confirm.button.confirm'),
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
width: 550,
|
||||
@ -88,6 +89,15 @@ RED.deploy = (function() {
|
||||
$( 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"),
|
||||
class: "primary",
|
||||
@ -97,7 +107,7 @@ RED.deploy = (function() {
|
||||
if (ignoreChecked) {
|
||||
ignoreDeployWarnings[$( "#node-dialog-confirm-deploy-type" ).val()] = true;
|
||||
}
|
||||
save(true);
|
||||
save(true,$( "#node-dialog-confirm-deploy-type" ).val() === "conflict");
|
||||
$( 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>'+
|
||||
'<input type="hidden" id="node-dialog-confirm-deploy-type">'+
|
||||
'</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");
|
||||
}
|
||||
});
|
||||
|
||||
// $("#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) {
|
||||
@ -160,11 +372,157 @@ RED.deploy = (function() {
|
||||
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()) {
|
||||
//$("#debug-tab-clear").click(); // uncomment this to auto clear debug on deploy
|
||||
|
||||
if (!force) {
|
||||
if (!skipValidation) {
|
||||
var hasUnknown = false;
|
||||
var hasInvalid = false;
|
||||
var hasUnusedConfig = false;
|
||||
@ -196,6 +554,7 @@ RED.deploy = (function() {
|
||||
$( "#node-dialog-confirm-deploy-config" ).hide();
|
||||
$( "#node-dialog-confirm-deploy-unknown" ).hide();
|
||||
$( "#node-dialog-confirm-deploy-unused" ).hide();
|
||||
$( "#node-dialog-confirm-deploy-conflict" ).hide();
|
||||
|
||||
var showWarning = false;
|
||||
|
||||
@ -229,24 +588,28 @@ RED.deploy = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
var nns = RED.nodes.createCompleteNodeSet();
|
||||
|
||||
$("#btn-deploy-icon").removeClass('fa-download');
|
||||
$("#btn-deploy-icon").addClass('spinner');
|
||||
RED.nodes.dirty(false);
|
||||
|
||||
var data = {flows:nns};
|
||||
|
||||
if (!force) {
|
||||
data.rev = RED.nodes.version();
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url:"flows",
|
||||
type: "POST",
|
||||
data: JSON.stringify(nns),
|
||||
data: JSON.stringify(data),
|
||||
contentType: "application/json; charset=utf-8",
|
||||
headers: {
|
||||
"Node-RED-Deployment-Type":deploymentType
|
||||
}
|
||||
}).done(function(data,textStatus,xhr) {
|
||||
RED.nodes.dirty(false);
|
||||
RED.nodes.version(data.rev);
|
||||
if (hasUnusedConfig) {
|
||||
RED.notify(
|
||||
'<p>'+RED._("deploy.successfulDeploy")+'</p>'+
|
||||
@ -264,10 +627,14 @@ RED.deploy = (function() {
|
||||
}
|
||||
});
|
||||
RED.nodes.eachConfig(function (confNode) {
|
||||
confNode.changed = false;
|
||||
if (confNode.credentials) {
|
||||
delete confNode.credentials;
|
||||
}
|
||||
});
|
||||
RED.nodes.eachWorkspace(function(ws) {
|
||||
ws.changed = false;
|
||||
})
|
||||
// Once deployed, cannot undo back to a clean state
|
||||
RED.history.markAllDirty();
|
||||
RED.view.redraw();
|
||||
@ -276,6 +643,8 @@ RED.deploy = (function() {
|
||||
RED.nodes.dirty(true);
|
||||
if (xhr.status === 401) {
|
||||
RED.notify(RED._("deploy.deployFailed",{message:RED._("user.notAuthorized")}),"error");
|
||||
} else if (xhr.status === 409) {
|
||||
resolveConflict(nns);
|
||||
} else if (xhr.responseText) {
|
||||
RED.notify(RED._("deploy.deployFailed",{message:xhr.responseText}),"error");
|
||||
} else {
|
||||
@ -287,7 +656,6 @@ RED.deploy = (function() {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
init: init
|
||||
}
|
||||
|
@ -93,6 +93,7 @@ RED.workspaces = (function() {
|
||||
node: workspace,
|
||||
dirty: RED.nodes.dirty()
|
||||
}
|
||||
workspace.changed = true;
|
||||
RED.history.push(historyEvent);
|
||||
workspace_tabs.renameTab(workspace.id,label);
|
||||
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;
|
||||
color: $editor-button-color-primary !important;
|
||||
}
|
||||
&.disabled {
|
||||
border-color: $form-input-border-color;
|
||||
color: $workspace-button-color-disabled !important;
|
||||
background: $editor-button-background;
|
||||
}
|
||||
}
|
||||
&.disabled {
|
||||
background: none;
|
||||
|
@ -42,6 +42,7 @@
|
||||
@import "popover";
|
||||
@import "flow";
|
||||
@import "palette-editor";
|
||||
@import "diff";
|
||||
|
||||
|
||||
@import "ui/common/editableList";
|
||||
|
@ -6,7 +6,7 @@
|
||||
<meta name="apple-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");
|
||||
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">
|
||||
<ul style="font-size: 0.9em; width: 400px; margin: 10px auto; text-align: left;" id="node-dialog-confirm-deploy-unknown-list"></ul>
|
||||
</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>
|
||||
</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">
|
||||
<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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -25,13 +25,23 @@ module.exports = {
|
||||
log = runtime.log;
|
||||
},
|
||||
get: function(req,res) {
|
||||
log.audit({event: "flows.get"},req);
|
||||
var version = req.get("Node-RED-API-Version")||"v1";
|
||||
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) {
|
||||
var version = req.get("Node-RED-API-Version")||"v1";
|
||||
var flows = req.body;
|
||||
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') {
|
||||
redNodes.loadFlows().then(function() {
|
||||
res.status(204).end();
|
||||
@ -41,8 +51,28 @@ module.exports = {
|
||||
res.status(500).json({error:"unexpected_error", message:err.message});
|
||||
});
|
||||
} else {
|
||||
redNodes.setFlows(flows,deploymentType).then(function() {
|
||||
var flowConfig = flows;
|
||||
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) {
|
||||
log.warn(log._("api.flows.error-save",{message:err.message}));
|
||||
log.warn(err.stack);
|
||||
|
@ -121,12 +121,15 @@
|
||||
"confirm": {
|
||||
"button": {
|
||||
"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.",
|
||||
"improperlyConfigured": "The workspace contains some nodes that are not properly configured:",
|
||||
"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": {
|
||||
|
@ -66,33 +66,47 @@ function init(runtime) {
|
||||
typeEventRegistered = true;
|
||||
}
|
||||
}
|
||||
function load() {
|
||||
|
||||
function loadFlows() {
|
||||
return storage.getFlows().then(function(config) {
|
||||
return credentials.load(config.credentials).then(function() {
|
||||
return setConfig(config.flows,"load");
|
||||
return config;
|
||||
});
|
||||
}).otherwise(function(err) {
|
||||
log.warn(log._("nodes.flows.error",{message:err.toString()}));
|
||||
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";
|
||||
|
||||
var configSavePromise = null;
|
||||
|
||||
var config = null;
|
||||
var diff;
|
||||
var newFlowConfig = flowUtil.parseConfig(clone(config));
|
||||
if (type !== 'full' && type !== 'load') {
|
||||
var newFlowConfig;
|
||||
|
||||
if (type === "load") {
|
||||
configSavePromise = loadFlows().then(function(_config) {
|
||||
config = clone(_config.flows);
|
||||
newFlowConfig = flowUtil.parseConfig(clone(config));
|
||||
type = "full";
|
||||
return _config.rev;
|
||||
});
|
||||
} else {
|
||||
config = clone(_config);
|
||||
newFlowConfig = flowUtil.parseConfig(clone(config));
|
||||
if (type !== 'full') {
|
||||
diff = flowUtil.diffConfigs(activeFlowConfig,newFlowConfig);
|
||||
}
|
||||
|
||||
if (type === 'load') {
|
||||
type = 'full';
|
||||
configSavePromise = when.resolve();
|
||||
} else {
|
||||
credentials.clean(config);
|
||||
var credsDirty = credentials.dirty();
|
||||
configSavePromise = credentials.export().then(function(creds) {
|
||||
@ -101,18 +115,22 @@ function setConfig(_config,type,muteLog) {
|
||||
credentialsDirty:credsDirty,
|
||||
credentials: creds
|
||||
}
|
||||
storage.saveFlows(saveConfig);
|
||||
return storage.saveFlows(saveConfig);
|
||||
});
|
||||
}
|
||||
|
||||
return configSavePromise
|
||||
.then(function() {
|
||||
activeConfig = config;
|
||||
.then(function(flowRevision) {
|
||||
activeConfig = {
|
||||
flows:config,
|
||||
rev:flowRevision
|
||||
};
|
||||
activeFlowConfig = newFlowConfig;
|
||||
if (started) {
|
||||
return stop(type,diff,muteLog).then(function() {
|
||||
context.clean(activeFlowConfig);
|
||||
start(type,diff,muteLog);
|
||||
return flowRevision;
|
||||
}).otherwise(function(err) {
|
||||
})
|
||||
}
|
||||
@ -143,7 +161,7 @@ function eachNode(cb) {
|
||||
}
|
||||
}
|
||||
|
||||
function getConfig() {
|
||||
function getFlows() {
|
||||
return activeConfig;
|
||||
}
|
||||
|
||||
@ -341,8 +359,8 @@ function checkTypeInUse(id) {
|
||||
throw new Error(log._("nodes.index.unrecognised-id", {id:id}));
|
||||
} else {
|
||||
var inUse = {};
|
||||
var config = getConfig();
|
||||
config.forEach(function(n) {
|
||||
var config = getFlows();
|
||||
config.flows.forEach(function(n) {
|
||||
inUse[n.type] = (inUse[n.type]||0)+1;
|
||||
});
|
||||
var nodesInUse = [];
|
||||
@ -418,10 +436,10 @@ function addFlow(flow) {
|
||||
nodes.push(node);
|
||||
}
|
||||
}
|
||||
var newConfig = clone(activeConfig);
|
||||
var newConfig = clone(activeConfig.flows);
|
||||
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+"]"}));
|
||||
return flow.id;
|
||||
});
|
||||
@ -501,7 +519,7 @@ function updateFlow(id,newFlow) {
|
||||
}
|
||||
label = activeFlowConfig.flows[id].label;
|
||||
}
|
||||
var newConfig = clone(activeConfig);
|
||||
var newConfig = clone(activeConfig.flows);
|
||||
var nodes;
|
||||
|
||||
if (id === 'global') {
|
||||
@ -539,7 +557,7 @@ function updateFlow(id,newFlow) {
|
||||
}
|
||||
|
||||
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+"]"}));
|
||||
})
|
||||
}
|
||||
@ -556,12 +574,12 @@ function removeFlow(id) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
var newConfig = clone(activeConfig);
|
||||
var newConfig = clone(activeConfig.flows);
|
||||
newConfig = newConfig.filter(function(node) {
|
||||
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+"]"}));
|
||||
});
|
||||
}
|
||||
@ -581,7 +599,7 @@ module.exports = {
|
||||
/**
|
||||
* Gets the current flow configuration
|
||||
*/
|
||||
getFlows: getConfig,
|
||||
getFlows: getFlows,
|
||||
|
||||
/**
|
||||
* Sets the current active config.
|
||||
@ -589,7 +607,7 @@ module.exports = {
|
||||
* @param type the type of deployment to do: full (default), nodes, flows, load
|
||||
* @return a promise for the saving/starting of the new flow
|
||||
*/
|
||||
setFlows: setConfig,
|
||||
setFlows: setFlows,
|
||||
|
||||
/**
|
||||
* Starts the current flow configuration
|
||||
|
@ -16,6 +16,8 @@
|
||||
|
||||
var when = require('when');
|
||||
var Path = require('path');
|
||||
var crypto = require('crypto');
|
||||
|
||||
var log = require("../log");
|
||||
|
||||
var runtime;
|
||||
@ -57,10 +59,12 @@ var storageModuleInterface = {
|
||||
getFlows: function() {
|
||||
return storageModule.getFlows().then(function(flows) {
|
||||
return storageModule.getCredentials().then(function(creds) {
|
||||
return {
|
||||
var result = {
|
||||
flows: flows,
|
||||
credentials: creds
|
||||
}
|
||||
};
|
||||
result.rev = crypto.createHash('md5').update(JSON.stringify(result)).digest("hex");
|
||||
return result;
|
||||
})
|
||||
});
|
||||
},
|
||||
@ -73,9 +77,12 @@ var storageModuleInterface = {
|
||||
} else {
|
||||
credentialSavePromise = when.resolve();
|
||||
}
|
||||
delete config.credentialsDirty;
|
||||
|
||||
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() {
|
||||
|
@ -34,12 +34,12 @@ describe("flows api", function() {
|
||||
app.post("/flows",flows.post);
|
||||
});
|
||||
|
||||
it('returns flow', function(done) {
|
||||
it('returns flow - v1', function(done) {
|
||||
flows.init({
|
||||
settings: {},
|
||||
log:{warn:function(){},_:function(){},audit:function(){}},
|
||||
nodes:{
|
||||
getFlows: function() { return [1,2,3]; }
|
||||
getFlows: function() { return {rev:"123",flows:[1,2,3]}; }
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
@ -50,13 +50,60 @@ describe("flows api", function() {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.be.an.Array;
|
||||
try {
|
||||
res.body.should.have.lengthOf(3);
|
||||
done();
|
||||
} catch(e) {
|
||||
return done(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('sets flows - default', function(done) {
|
||||
it('returns flow - v2', 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();});
|
||||
flows.init({
|
||||
log:{warn:function(){},_:function(){},audit:function(){}},
|
||||
@ -77,7 +124,7 @@ describe("flows api", function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('sets flows - non-default', function(done) {
|
||||
it('sets flows - non-default - v1', function(done) {
|
||||
var setFlows = sinon.spy(function() { return when.resolve();});
|
||||
flows.init({
|
||||
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) {
|
||||
var loadFlows = sinon.spy(function() { return when.resolve(); });
|
||||
flows.init({
|
||||
|
@ -116,22 +116,31 @@ describe('flows/index', function() {
|
||||
flows.setFlows(originalConfig).then(function() {
|
||||
credentialsClean.called.should.be.true;
|
||||
storage.hasOwnProperty('conf').should.be.true;
|
||||
flows.getFlows().should.eql(originalConfig);
|
||||
flows.getFlows().flows.should.eql(originalConfig);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
it('sets the full flow for type load', function(done) {
|
||||
it('loads the full flow for type load', function(done) {
|
||||
var originalConfig = [
|
||||
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
|
||||
{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() {
|
||||
credentialsClean.called.should.be.false;
|
||||
// 'load' type does not trigger a save
|
||||
storage.hasOwnProperty('conf').should.be.false;
|
||||
flows.getFlows().should.eql(originalConfig);
|
||||
loadStorage.hasOwnProperty('conf').should.be.false;
|
||||
flows.getFlows().flows.should.eql(originalConfig);
|
||||
done();
|
||||
});
|
||||
|
||||
@ -147,10 +156,10 @@ describe('flows/index', function() {
|
||||
credentialsClean.called.should.be.true;
|
||||
storage.hasOwnProperty('conf').should.be.true;
|
||||
var cleanedFlows = flows.getFlows();
|
||||
storage.conf.flows.should.eql(cleanedFlows);
|
||||
cleanedFlows.should.not.eql(originalConfig);
|
||||
cleanedFlows[0].credentials = {"a":1};
|
||||
cleanedFlows.should.eql(originalConfig);
|
||||
storage.conf.flows.should.eql(cleanedFlows.flows);
|
||||
cleanedFlows.flows.should.not.eql(originalConfig);
|
||||
cleanedFlows.flows[0].credentials = {"a":1};
|
||||
cleanedFlows.flows.should.eql(originalConfig);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -170,7 +179,7 @@ describe('flows/index', function() {
|
||||
|
||||
events.once('nodes-started',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['t2'].start.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() {
|
||||
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['t2'].start.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;
|
||||
// 'load' type does not trigger a save
|
||||
storage.hasOwnProperty('conf').should.be.false;
|
||||
flows.getFlows().should.eql(originalConfig);
|
||||
flows.getFlows().flows.should.eql(originalConfig);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -535,7 +544,7 @@ describe('flows/index', function() {
|
||||
{id:"t2-3",z:"t1",type:"test"}
|
||||
]
|
||||
}).then(function(id) {
|
||||
flows.getFlows().should.have.lengthOf(6);
|
||||
flows.getFlows().flows.should.have.lengthOf(6);
|
||||
var createdFlows = Object.keys(flowCreate.flows);
|
||||
createdFlows.should.have.lengthOf(3);
|
||||
createdFlows[2].should.eql(id);
|
||||
|
@ -39,13 +39,14 @@ describe("red/nodes/index", function() {
|
||||
});
|
||||
|
||||
var testFlows = [{"type":"test","id":"tab1","label":"Sheet 1"}];
|
||||
var testCredentials = {"tab1":{"b":1,"c":2}};
|
||||
var storage = {
|
||||
getFlows: function() {
|
||||
return when({flows:testFlows,credentials:{"tab1":{"b":1,"c":2}}});
|
||||
return when({red:123,flows:testFlows,credentials:testCredentials});
|
||||
},
|
||||
saveFlows: function(conf) {
|
||||
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) {
|
||||
index.init(runtime);
|
||||
index.loadFlows().then(function() {
|
||||
should.deepEqual(testFlows, index.getFlows());
|
||||
console.log(testFlows);
|
||||
console.log(index.getFlows());
|
||||
should.deepEqual(testFlows, index.getFlows().flows);
|
||||
done();
|
||||
}).otherwise(function(err) {
|
||||
done(err);
|
||||
@ -173,8 +176,8 @@ describe("red/nodes/index", function() {
|
||||
index.registerType('test', TestNode);
|
||||
index.loadFlows().then(function() {
|
||||
var info = index.disableNode("5678");
|
||||
registry.disableNode.calledOnce.should.be.true;
|
||||
registry.disableNode.calledWith("5678").should.be.true;
|
||||
registry.disableNode.calledOnce.should.be.true();
|
||||
registry.disableNode.calledWith("5678").should.be.true();
|
||||
info.should.eql(randomNodeInfo);
|
||||
done();
|
||||
}).otherwise(function(err) {
|
||||
|
@ -79,6 +79,7 @@ describe("red/storage/index", function() {
|
||||
},
|
||||
saveFlows : function (flows) {
|
||||
flows.should.be.true;
|
||||
return when.resolve("");
|
||||
},
|
||||
getCredentials : function() {
|
||||
calledFlagGetCredentials = true;
|
||||
|
Loading…
Reference in New Issue
Block a user