From c4b1795396417d0a4acd4a8814e16f80500ea5cd Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 9 Dec 2015 21:51:46 +0000 Subject: [PATCH] Add add/update/delete flow apis --- red/api/flow.js | 61 ++++- red/api/index.js | 3 + red/runtime/nodes/credentials.js | 2 +- red/runtime/nodes/flows/Flow.js | 3 +- red/runtime/nodes/flows/index.js | 254 +++++++++++++++++---- red/runtime/nodes/flows/util.js | 41 ++-- test/red/runtime/nodes/flows/index_spec.js | 6 +- test/red/runtime/nodes/flows/util_spec.js | 3 +- 8 files changed, 305 insertions(+), 68 deletions(-) diff --git a/red/api/flow.js b/red/api/flow.js index d9e71964a..31bc076ac 100644 --- a/red/api/flow.js +++ b/red/api/flow.js @@ -26,7 +26,64 @@ module.exports = { }, get: function(req,res) { var id = req.params.id; - log.audit({event: "flow.get"},req); - res.json(redNodes.getFlow(id)); + var flow = 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()}); + } + } } } diff --git a/red/api/index.js b/red/api/index.js index d71f8ad9d..570f32be1 100644 --- a/red/api/index.js +++ b/red/api/index.js @@ -106,6 +106,9 @@ function init(_server,runtime) { adminApp.post("/flows",needsPermission("flows.write"),flows.post); 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 adminApp.get("/nodes",needsPermission("nodes.read"),nodes.getAll); diff --git a/red/runtime/nodes/credentials.js b/red/runtime/nodes/credentials.js index aeab852b6..76f167144 100644 --- a/red/runtime/nodes/credentials.js +++ b/red/runtime/nodes/credentials.js @@ -121,6 +121,7 @@ module.exports = { var nodeType = node.type; var newCreds = node.credentials; if (newCreds) { + delete node.credentials; var savedCredentials = credentialCache[nodeID] || {}; var dashedType = nodeType.replace(/\s+/g, '-'); var definition = credentialsDef[dashedType]; @@ -145,7 +146,6 @@ module.exports = { } } credentialCache[nodeID] = savedCredentials; - delete node.credentials; } }, diff --git a/red/runtime/nodes/flows/Flow.js b/red/runtime/nodes/flows/Flow.js index d4b8eaec9..edeea1943 100644 --- a/red/runtime/nodes/flows/Flow.js +++ b/red/runtime/nodes/flows/Flow.js @@ -47,7 +47,7 @@ function Flow(global,flow) { } } } - if (diff) { + if (diff && diff.rewired) { for (var j=0;j 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; } +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 = { init: init, @@ -470,11 +647,10 @@ module.exports = { checkTypeInUse: checkTypeInUse, - addFlow: addFlow, getFlow: getFlow, - updateFlow:null, - removeFlow:null, + updateFlow: updateFlow, + removeFlow: removeFlow, disableFlow:null, enableFlow:null diff --git a/red/runtime/nodes/flows/util.js b/red/runtime/nodes/flows/util.js index 736078b86..e7feefe96 100644 --- a/red/runtime/nodes/flows/util.js +++ b/red/runtime/nodes/flows/util.js @@ -76,29 +76,28 @@ module.exports = { if (flow.missingTypes.indexOf(n.type) === -1) { flow.missingTypes.push(n.type); } - } else { - var container = null; - if (flow.flows[n.z]) { - container = flow.flows[n.z]; - } else if (flow.subflows[n.z]) { - container = flow.subflows[n.z]; + } + var container = null; + if (flow.flows[n.z]) { + container = flow.flows[n.z]; + } else if (flow.subflows[n.z]) { + container = flow.subflows[n.z]; + } + if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) { + if (subflowDetails) { + var subflowType = subflowDetails[1] + n.subflow = subflowType; + flow.subflows[subflowType].instances.push(n) } - if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) { - if (subflowDetails) { - var subflowType = subflowDetails[1] - n.subflow = subflowType; - flow.subflows[subflowType].instances.push(n) - } - if (container) { - container.nodes[n.id] = n; - } + if (container) { + container.nodes[n.id] = n; + } + } else { + if (container) { + container.configs[n.id] = n; } else { - if (container) { - container.configs[n.id] = n; - } else { - flow.configs[n.id] = n; - flow.configs[n.id]._users = []; - } + flow.configs[n.id] = n; + flow.configs[n.id]._users = []; } } } diff --git a/test/red/runtime/nodes/flows/index_spec.js b/test/red/runtime/nodes/flows/index_spec.js index 1d3af9a98..3a18bd73d 100644 --- a/test/red/runtime/nodes/flows/index_spec.js +++ b/test/red/runtime/nodes/flows/index_spec.js @@ -531,6 +531,9 @@ describe('flows/index', function() { storage.getFlows = function() { return when.resolve(originalConfig); } + storage.setFlows = function() { + return when.resolve(); + } flows.init({},storage); flows.load().then(function() { return flows.startFlows(); @@ -539,7 +542,8 @@ describe('flows/index', function() { label:'new flow', nodes:[ {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"} ] }).then(function(id) { diff --git a/test/red/runtime/nodes/flows/util_spec.js b/test/red/runtime/nodes/flows/util_spec.js index 916a398a4..f3b50ae6b 100644 --- a/test/red/runtime/nodes/flows/util_spec.js +++ b/test/red/runtime/nodes/flows/util_spec.js @@ -137,8 +137,7 @@ describe('flows/util', function() { ]; var parsedConfig = flowUtil.parseConfig(originalConfig); 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; });