1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Add add/update/delete flow apis

This commit is contained in:
Nick O'Leary 2015-12-09 21:51:46 +00:00
parent fd2e47ed73
commit c4b1795396
8 changed files with 305 additions and 68 deletions

View File

@ -26,7 +26,64 @@ module.exports = {
}, },
get: function(req,res) { get: function(req,res) {
var id = req.params.id; var id = req.params.id;
log.audit({event: "flow.get"},req); var flow = redNodes.getFlow(id);
res.json(redNodes.getFlow(id)); if (flow) {
log.audit({event: "flow.get",id:id},req);
res.json(flow);
} else {
log.audit({event: "flow.get",id:id,error:"not_found"},req);
res.status(404).end();
}
},
post: function(req,res) {
var flow = req.body;
redNodes.addFlow(flow).then(function(id) {
log.audit({event: "flow.add",id:id},req);
res.json({id:id});
}).otherwise(function(err) {
log.audit({event: "flow.add",error:err.code||"unexpected_error",message:err.toString()},req);
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
})
},
put: function(req,res) {
var id = req.params.id;
var flow = req.body;
try {
redNodes.updateFlow(id,flow).then(function() {
log.audit({event: "flow.update",id:id},req);
res.json({id:id});
}).otherwise(function(err) {
console.log(err.stack);
log.audit({event: "flow.update",error:err.code||"unexpected_error",message:err.toString()},req);
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
})
} catch(err) {
if (err.code === 404) {
log.audit({event: "flow.update",id:id,error:"not_found"},req);
res.status(404).end();
} else {
console.log(err.stack);
log.audit({event: "flow.update",error:err.code||"unexpected_error",message:err.toString()},req);
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
}
}
},
delete: function(req,res) {
var id = req.params.id;
try {
redNodes.removeFlow(id).then(function() {
log.audit({event: "flow.remove",id:id},req);
res.status(204).end();
})
} catch(err) {
if (err.code === 404) {
log.audit({event: "flow.remove",id:id,error:"not_found"},req);
res.status(404).end();
} else {
log.audit({event: "flow.remove",id:id,error:err.code||"unexpected_error",message:err.toString()},req);
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
}
}
} }
} }

View File

@ -106,6 +106,9 @@ function init(_server,runtime) {
adminApp.post("/flows",needsPermission("flows.write"),flows.post); adminApp.post("/flows",needsPermission("flows.write"),flows.post);
adminApp.get("/flow/:id",needsPermission("flows.read"),flow.get); adminApp.get("/flow/:id",needsPermission("flows.read"),flow.get);
adminApp.post("/flow",needsPermission("flows.write"),flow.post);
adminApp.delete("/flow/:id",needsPermission("flows.write"),flow.delete);
adminApp.put("/flow/:id",needsPermission("flows.write"),flow.put);
// Nodes // Nodes
adminApp.get("/nodes",needsPermission("nodes.read"),nodes.getAll); adminApp.get("/nodes",needsPermission("nodes.read"),nodes.getAll);

View File

@ -121,6 +121,7 @@ module.exports = {
var nodeType = node.type; var nodeType = node.type;
var newCreds = node.credentials; var newCreds = node.credentials;
if (newCreds) { if (newCreds) {
delete node.credentials;
var savedCredentials = credentialCache[nodeID] || {}; var savedCredentials = credentialCache[nodeID] || {};
var dashedType = nodeType.replace(/\s+/g, '-'); var dashedType = nodeType.replace(/\s+/g, '-');
var definition = credentialsDef[dashedType]; var definition = credentialsDef[dashedType];
@ -145,7 +146,6 @@ module.exports = {
} }
} }
credentialCache[nodeID] = savedCredentials; credentialCache[nodeID] = savedCredentials;
delete node.credentials;
} }
}, },

View File

@ -47,7 +47,7 @@ function Flow(global,flow) {
} }
} }
} }
if (diff) { if (diff && diff.rewired) {
for (var j=0;j<diff.rewired.length;j++) { for (var j=0;j<diff.rewired.length;j++) {
var rewireNode = activeNodes[diff.rewired[j]]; var rewireNode = activeNodes[diff.rewired[j]];
if (rewireNode) { if (rewireNode) {
@ -262,7 +262,6 @@ function mapEnvVarProperties(obj,prop) {
} }
function createNode(type,config) { function createNode(type,config) {
// console.log("CREATE",type,config.id);
var nn = null; var nn = null;
var nt = typeRegistry.get(type); var nt = typeRegistry.get(type);
if (nt) { if (nt) {

View File

@ -200,6 +200,7 @@ function handleStatus(node,statusMessage) {
function start(type,diff) { function start(type,diff) {
//dumpActiveNodes();
type = type||"full"; type = type||"full";
started = true; started = true;
var i; var i;
@ -231,14 +232,18 @@ function start(type,diff) {
} }
var id; var id;
if (!diff) { if (!diff) {
activeFlows['_GLOBAL_'] = Flow.create(activeFlowConfig); if (!activeFlows['global']) {
activeFlows['global'] = Flow.create(activeFlowConfig);
}
for (id in activeFlowConfig.flows) { for (id in activeFlowConfig.flows) {
if (activeFlowConfig.flows.hasOwnProperty(id)) { if (activeFlowConfig.flows.hasOwnProperty(id)) {
if (!activeFlows[id]) {
activeFlows[id] = Flow.create(activeFlowConfig,activeFlowConfig.flows[id]); activeFlows[id] = Flow.create(activeFlowConfig,activeFlowConfig.flows[id]);
} }
} }
}
} else { } else {
activeFlows['_GLOBAL_'].update(activeFlowConfig,activeFlowConfig); activeFlows['global'].update(activeFlowConfig,activeFlowConfig);
for (id in activeFlowConfig.flows) { for (id in activeFlowConfig.flows) {
if (activeFlowConfig.flows.hasOwnProperty(id)) { if (activeFlowConfig.flows.hasOwnProperty(id)) {
if (activeFlows[id]) { if (activeFlows[id]) {
@ -352,7 +357,34 @@ function checkTypeInUse(id) {
} }
} }
function updateMissingTypes() {
var subflowInstanceRE = /^subflow:(.+)$/;
activeFlowConfig.missingTypes = [];
for (var id in activeFlowConfig.allNodes) {
if (activeFlowConfig.allNodes.hasOwnProperty(id)) {
var node = activeFlowConfig.allNodes[id];
if (node.type !== 'tab' && node.type !== 'subflow') {
var subflowDetails = subflowInstanceRE.exec(node.type);
if ( (subflowDetails && !activeFlowConfig.subflows[subflowDetails[1]]) || (!subflowDetails && !typeRegistry.get(node.type)) ) {
if (activeFlowConfig.missingTypes.indexOf(node.type) === -1) {
activeFlowConfig.missingTypes.push(node.type);
}
}
}
}
}
}
// function dumpActiveNodes() {
// console.log("--------")
// for (var i in activeFlowConfig.allNodes) {
// console.log(i,activeFlowConfig.allNodes[i].type,activeFlowConfig.allNodes[i].z)
// }
// console.log("--------")
// }
function addFlow(flow) { function addFlow(flow) {
//dumpActiveNodes();
/* /*
{ {
id:'', id:'',
@ -364,12 +396,10 @@ function addFlow(flow) {
// flow.id should not exist - it will be assigned by the runtime // flow.id should not exist - it will be assigned by the runtime
// all flow.{subflows|configs|nodes}.z will be set to flow.id // all flow.{subflows|configs|nodes}.z will be set to flow.id
// all nodes will have new ids assigned if there is a clash
// check all known types - fail if otherwise? // check all known types - fail if otherwise?
// //
// resolves with generated flow id // resolves with generated flow id
return when.promise(function(resolve,reject) {
var i,id,node; var i,id,node;
flow.id = redUtil.generateId(); flow.id = redUtil.generateId();
@ -378,55 +408,202 @@ function addFlow(flow) {
node = flow.nodes[i]; node = flow.nodes[i];
if (activeFlowConfig.allNodes[node.id]) { if (activeFlowConfig.allNodes[node.id]) {
// TODO nls // TODO nls
return reject(new Error('duplicate id')); return when.reject(new Error('duplicate id'));
} }
node.z = flow.id; node.z = flow.id;
} }
if (flow.configs) {
for (i=0;i<flow.configs.length;i++) {
node = flow.configs[i];
if (activeFlowConfig.allNodes[node.id]) {
// TODO nls
return when.reject(new Error('duplicate id'));
}
node.z = flow.id;
}
}
var tabNode = { var tabNode = {
type:'tab', type:'tab',
label:flow.label, label:flow.label,
id:flow.id id:flow.id
} }
var nodes = [tabNode].concat(flow.nodes); var nodes = [tabNode].concat(flow.nodes||[]).concat(flow.configs||[]);
var credentialSavePromise;
var credentialsChanged = false;
nodes.forEach(function(node) {
if (node.credentials) {
credentials.extract(node);
credentialsChanged = true;
}
});
if (credentialsChanged) {
credentialSavePromise = credentials.save();
} else {
credentialSavePromise = when.resolve();
}
var parsedConfig = flowUtil.parseConfig(clone(nodes)); var parsedConfig = flowUtil.parseConfig(clone(nodes));
// TODO: handle unknown type parsedConfig.missingTypes.forEach(function(type) {
for (id in parsedConfig.flows[flow.id]) { if (activeFlowConfig.missingTypes.indexOf(type) == -1) {
if (parsedConfig.flows[flow.id].hasOwnProperty(id)) { activeFlowConfig.missingTypes.push(type);
activeFlowConfig.allNodes[id] = parsedConfig.flows[flow.id][id]; }
})
activeFlowConfig.allNodes[tabNode.id] = tabNode;
for (id in parsedConfig.flows[flow.id].nodes) {
if (parsedConfig.flows[flow.id].nodes.hasOwnProperty(id)) {
activeFlowConfig.allNodes[id] = parsedConfig.flows[flow.id].nodes[id];
} }
} }
if (parsedConfig.flows[flow.id].configs) {
for (id in parsedConfig.flows[flow.id].configs) {
if (parsedConfig.flows[flow.id].configs.hasOwnProperty(id)) {
activeFlowConfig.allNodes[id] = parsedConfig.flows[flow.id].configs[id];
}
}
}
activeFlowConfig.flows[flow.id] = parsedConfig.flows[flow.id]; activeFlowConfig.flows[flow.id] = parsedConfig.flows[flow.id];
activeConfig = activeConfig.concat(nodes); activeConfig = activeConfig.concat(nodes);
// TODO: extract creds // TODO: extract creds
// TODO: save config return credentialSavePromise.then(function() {
return storage.saveFlows(activeConfig).then(function() {
start("flows",{added:flow.nodes.map(function(n) { return n.id})}).then(function() { return start("flows",{added:flow.nodes.map(function(n) { return n.id})}).then(function() {
//dumpActiveNodes();
// console.log(activeFlowConfig); // console.log(activeFlowConfig);
resolve(flow.id); return flow.id;
}) })
}) })
});
} }
function getFlow(id) { function getFlow(id) {
var flow = activeFlowConfig.flows[id]; var flow;
if (id === 'global') {
flow = activeFlowConfig;
} else {
flow = activeFlowConfig.flows[id];
}
if (!flow) { if (!flow) {
return null; return null;
} }
var result = { var result = {
id: id, id: id
label: flow.label,
nodes: []
}; };
if (flow.label) {
for (var i=0;i<activeConfig.length;i++) { result.label = flow.label;
if (activeConfig[i].z === id && activeConfig[i].type != 'tab') {
result.nodes.push(activeConfig[i]);
} }
if (flow.nodes) {
var nodeIds = Object.keys(flow.nodes);
if (nodeIds.length > 0) {
result.nodes = nodeIds.map(function(nodeId) {
return clone(flow.nodes[nodeId]);
})
}
}
if (flow.configs) {
var configIds = Object.keys(flow.configs);
result.configs = configIds.map(function(configId) {
return clone(flow.configs[configId]);
})
}
if (flow.subflows) {
var subflowIds = Object.keys(flow.subflows);
result.subflows = subflowIds.map(function(subflowId) {
var subflow = clone(flow.subflows[subflowId]);
var nodeIds = Object.keys(subflow.nodes);
subflow.nodes = nodeIds.map(function(id) {
return subflow.nodes[id];
});
if (subflow.configs) {
var configIds = Object.keys(subflow.configs);
subflow.configs = configIds.map(function(id) {
return subflow.configs[id];
})
}
delete subflow.instances;
return subflow;
});
} }
return result; return result;
} }
function updateFlow(id,newFlow) {
if (id === 'global') {
// TODO: handle global update
throw new Error('not allowed to update global');
}
var flow = activeFlowConfig.flows[id];
if (!flow) {
var e = new Error();
e.code = 404;
throw e;
}
var newConfig = clone(activeConfig);
newConfig = newConfig.filter(function(node) {
return node.z !== id && node.id !== id;
});
var tabNode = {
type:'tab',
label:newFlow.label,
id:id
}
var nodes = [tabNode].concat(newFlow.nodes||[]).concat(newFlow.configs||[]);
nodes.forEach(function(n) {
n.z = id;
});
newConfig = newConfig.concat(nodes);
return setConfig(newConfig,'flows');
// filter activeConfig to remove nodes
}
function removeFlow(id) {
if (id === 'global') {
// TODO: nls + error code
throw new Error('not allowed to remove global');
}
var flow = activeFlowConfig.flows[id];
if (!flow) {
var e = new Error();
e.code = 404;
throw e;
}
var diff = {
removed: [id].concat(Object.keys(flow.nodes)).concat(Object.keys(flow.configs)),
linked:[],
changed:[]
}
delete activeFlowConfig.flows[id];
diff.removed.forEach(function(id) {
delete activeFlowConfig.allNodes[id];
});
activeConfig = activeConfig.filter(function(node) {
return node.z !== id && node.id !== id;
});
var missingTypeCount = activeFlowConfig.missingTypes.length;
updateMissingTypes();
return credentials.clean(activeConfig).then(function() {
storage.saveFlows(activeConfig).then(function() {
stop("flows",diff).then(function() {
if (missingTypeCount > 0 && activeFlowConfig.missingTypes.length === 0) {
return start();
}
//dumpActiveNodes();
});
});
})
}
module.exports = { module.exports = {
init: init, init: init,
@ -470,11 +647,10 @@ module.exports = {
checkTypeInUse: checkTypeInUse, checkTypeInUse: checkTypeInUse,
addFlow: addFlow, addFlow: addFlow,
getFlow: getFlow, getFlow: getFlow,
updateFlow:null, updateFlow: updateFlow,
removeFlow:null, removeFlow: removeFlow,
disableFlow:null, disableFlow:null,
enableFlow:null enableFlow:null

View File

@ -76,7 +76,7 @@ module.exports = {
if (flow.missingTypes.indexOf(n.type) === -1) { if (flow.missingTypes.indexOf(n.type) === -1) {
flow.missingTypes.push(n.type); flow.missingTypes.push(n.type);
} }
} else { }
var container = null; var container = null;
if (flow.flows[n.z]) { if (flow.flows[n.z]) {
container = flow.flows[n.z]; container = flow.flows[n.z];
@ -101,7 +101,6 @@ module.exports = {
} }
} }
} }
}
}); });
config.forEach(function(n) { config.forEach(function(n) {
if (n.type !== 'subflow' && n.type !== 'tab') { if (n.type !== 'subflow' && n.type !== 'tab') {

View File

@ -531,6 +531,9 @@ describe('flows/index', function() {
storage.getFlows = function() { storage.getFlows = function() {
return when.resolve(originalConfig); return when.resolve(originalConfig);
} }
storage.setFlows = function() {
return when.resolve();
}
flows.init({},storage); flows.init({},storage);
flows.load().then(function() { flows.load().then(function() {
return flows.startFlows(); return flows.startFlows();
@ -539,7 +542,8 @@ describe('flows/index', function() {
label:'new flow', label:'new flow',
nodes:[ nodes:[
{id:"t2-1",x:10,y:10,z:"t1",type:"test",wires:[]}, {id:"t2-1",x:10,y:10,z:"t1",type:"test",wires:[]},
{id:"t2-2",x:10,y:10,z:"t1",type:"test",wires:[]}, {id:"t2-2",x:10,y:10,z:"t1",type:"test",wires:[]}
,
{id:"t2-3",z:"t1",type:"test"} {id:"t2-3",z:"t1",type:"test"}
] ]
}).then(function(id) { }).then(function(id) {

View File

@ -137,8 +137,7 @@ describe('flows/util', function() {
]; ];
var parsedConfig = flowUtil.parseConfig(originalConfig); var parsedConfig = flowUtil.parseConfig(originalConfig);
parsedConfig.missingTypes.should.eql(['missing']); parsedConfig.missingTypes.should.eql(['missing']);
var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"sf1","wires":[]},"t1-2":{"id":"t1-2","x":10,"y":10,"z":"t1","type":"missing","wires":[]}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"sf1","wires":[]}}}},"missingTypes":["missing"]}; var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"sf1","wires":[]},"t1-2":{"id":"t1-2","x":10,"y":10,"z":"t1","type":"missing","wires":[]}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"sf1","wires":[]},'t1-2': { id: 't1-2', x: 10, y: 10, z: 't1', type: 'missing', wires: [] }}}},"missingTypes":["missing"]};
redUtil.compareObjects(parsedConfig,expectedConfig).should.be.true; redUtil.compareObjects(parsedConfig,expectedConfig).should.be.true;
}); });