diff options
author | Timoney, Daniel (dt5972) <dtimoney@att.com> | 2017-02-15 10:37:53 -0500 |
---|---|---|
committer | Timoney, Daniel (dt5972) <dtimoney@att.com> | 2017-02-15 10:40:37 -0500 |
commit | 324ee36fe31763e507b422ab0a88e4230045e205 (patch) | |
tree | d0b04520f6657601c918ce63fd27575977624187 /dgbuilder/red | |
parent | f0c97e8db427481e28c0a16b789bc73801b35e47 (diff) |
Initial commit for OpenECOMP SDN-C OA&M
Change-Id: I7ab579fd0d206bf356f36d52dcdf4f71f1fa2680
Signed-off-by: Timoney, Daniel (dt5972) <dtimoney@att.com>
Former-commit-id: 2a9f0edd09581f907e62ec4689b5ac94dd5382ba
Diffstat (limited to 'dgbuilder/red')
-rw-r--r-- | dgbuilder/red/cli/lib/config.js | 53 | ||||
-rw-r--r-- | dgbuilder/red/cli/lib/request.js | 51 | ||||
-rwxr-xr-x | dgbuilder/red/cli/nr-cli.js | 151 | ||||
-rw-r--r-- | dgbuilder/red/comms.js | 132 | ||||
-rw-r--r-- | dgbuilder/red/events.js | 19 | ||||
-rw-r--r-- | dgbuilder/red/library.js | 117 | ||||
-rw-r--r-- | dgbuilder/red/log.js | 39 | ||||
-rw-r--r-- | dgbuilder/red/nodes/Node.js | 147 | ||||
-rw-r--r-- | dgbuilder/red/nodes/credentials.js | 208 | ||||
-rw-r--r-- | dgbuilder/red/nodes/flows.js | 220 | ||||
-rw-r--r-- | dgbuilder/red/nodes/index.js | 134 | ||||
-rw-r--r-- | dgbuilder/red/nodes/registry.js | 693 | ||||
-rw-r--r-- | dgbuilder/red/red.js | 68 | ||||
-rw-r--r-- | dgbuilder/red/server.js | 1317 | ||||
-rw-r--r-- | dgbuilder/red/settings.js | 84 | ||||
-rw-r--r-- | dgbuilder/red/sla.js | 249 | ||||
-rw-r--r-- | dgbuilder/red/storage/index.js | 107 | ||||
-rw-r--r-- | dgbuilder/red/storage/localfilesystem.js | 309 | ||||
-rw-r--r-- | dgbuilder/red/ui.js | 77 | ||||
-rw-r--r-- | dgbuilder/red/util.js | 43 |
20 files changed, 4218 insertions, 0 deletions
diff --git a/dgbuilder/red/cli/lib/config.js b/dgbuilder/red/cli/lib/config.js new file mode 100644 index 00000000..3cd5244d --- /dev/null +++ b/dgbuilder/red/cli/lib/config.js @@ -0,0 +1,53 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var path = require("path"); +var fs = require("fs"); + +var userHome = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE; + +var configDir = path.join(userHome,".nodered"); +var configFile = path.join(configDir,"config.json"); + +var config; + +function load() { + if (config == null) { + try { + config = JSON.parse(fs.readFileSync(configFile)); + } catch(err) { + config = {}; + } + } +} + +function save() { + try { + fs.mkdirSync(configDir); + } catch(err) { + if (err.code != "EEXIST") { + throw err; + } + } + fs.writeFileSync(configFile,JSON.stringify(config,null,4)); +} +module.exports = { + unload: function() { + config = null; + } +}; +module.exports.__defineGetter__('target',function() { load(); return config.target|| "http://localhost:1880" }); +module.exports.__defineSetter__('target',function(v) { load(); config.target = v; save();}); diff --git a/dgbuilder/red/cli/lib/request.js b/dgbuilder/red/cli/lib/request.js new file mode 100644 index 00000000..fbbe3dc6 --- /dev/null +++ b/dgbuilder/red/cli/lib/request.js @@ -0,0 +1,51 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var when = require("when"); +var request = require("request"); +var config = require("./config"); + +module.exports = function(path, options) { + var basePath = config.target; + return when.promise(function(resolve,reject) { + options.headers = options.headers||{}; + options.headers['Accept'] = 'application/json'; + if (options.method == 'PUT' || options.method == "POST") { + options.headers['content-type'] = 'application/json'; + } + options.url = basePath+path; + + // Pull out the request function so we can stub it in the tests + var requestFunc = request.get; + + if (options.method == 'PUT') { + requestFunc = request.put; + } else if (options.method == 'POST') { + requestFunc = request.post; + } else if (options.method == 'DELETE') { + requestFunc = request.del; + } + requestFunc(options, function(error,response,body) { + if (!error && response.statusCode == 200) { + resolve(JSON.parse(body)); + } else if (error) { + reject(error.toString()); + } else { + reject(response.statusCode+": "+body) + } + }); + }); +} diff --git a/dgbuilder/red/cli/nr-cli.js b/dgbuilder/red/cli/nr-cli.js new file mode 100755 index 00000000..6d62f05f --- /dev/null +++ b/dgbuilder/red/cli/nr-cli.js @@ -0,0 +1,151 @@ +#!/usr/bin/env node +;(function() { +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require("util"); +var request = require("request"); +var colors = require('colors'); +var apiRequest = require("./lib/request"); +var config = require("./lib/config"); + +var commands = { + "target": function() { + var target = process.argv[3]; + if (target) { + if (!/^https?:\/\/.+/.test(target)) { + console.warn("Invalid target url"); + return; + } + if (target.slice(-1) == "/") { + target = target.slice(0,target.length-1); + } + var oldTarget = config.target; + config.target = target; + } else { + console.log("Target: ".yellow+config.target); + } + + }, + "nodes": function() { + apiRequest('/nodes',{}).then(logNodeList).otherwise(logFailure); + }, + "node": function() { + apiRequest('/nodes/'+process.argv[3],{}).then(logNodeList).otherwise(logFailure); + }, + "enable-node": function() { + apiRequest('/nodes/'+process.argv[3],{ + method: "PUT", + body: JSON.stringify({enabled:true}) + }).then(logNodeList).otherwise(logFailure); + }, + "disable-node": function() { + apiRequest('/nodes/'+process.argv[3],{ + method: "PUT", + body: JSON.stringify({enabled:false}) + }).then(logNodeList).otherwise(logFailure); + }, + "install": function() { + apiRequest('/nodes',{ + method: "POST", + body: JSON.stringify({module:process.argv[3]}) + }).then(logNodeList).otherwise(logFailure); + }, + "remove": function() { + apiRequest('/nodes/'+process.argv[3],{ + method: "DELETE" + }).then(logNodeList).otherwise(logFailure); + }, + "search": function() { + var options = { + method: "GET", + url: 'https://registry.npmjs.org/-/_view/byKeyword?startkey=["node-red"]&endkey=["node-red",{}]&group_level=3' , + headers: { + 'Accept': 'application/json', + } + }; + request(options, function (error, response, body) { + if (!error && response.statusCode == 200) { + var info = (JSON.parse(body)).rows; + var filter = null; + if (process.argv[3]) { + filter = new RegExp(process.argv[3]); + } + for (var i=0;i<info.length;i++) { + var n = info[i]; + if (!filter || filter.test(n.key[1]) || filter.test(n.key[2])) { + console.log(n.key[1] + (" - "+ n.key[2]).grey); + } + } + } else if (error) { + console.log(error.toString().red); + } else { + console.log((response.statusCode+": "+body).red); + } + }); + } +} + +function logNodeList(nodes) { + if (!util.isArray(nodes)) { + nodes = [nodes]; + } + for (var i=0;i<nodes.length;i++) { + var n = nodes[i]; + console.log(formatNodeInfo(n)) + } +} + +function logFailure(msg) { + console.log(msg.red); +} + +function formatBoolean(v,c) { + if (v) { + return ("["+c+"]"); + } else { + return ("[ ]"); + } +} + +function formatNodeInfo(n) { + var inError = n.hasOwnProperty("err"); + + var str = formatBoolean(n.enabled,"X")+formatBoolean(n.loaded,"L")+" "; + str += n.id; + if (n.enabled && n.loaded) { + str = str.green; + } else if (n.enabled && n.err) { + str = str.red; + } else { + str = str.yellow; + } + if (n.module) { + str += " ["+n.module+"]"; + } + str += " "+n.types.join(", "); + if (n.err) { + str+=" "+n.err.red; + } + return str; +} + +if (commands[process.argv[2]]) { + commands[process.argv[2]].call(); +} + + +})(); diff --git a/dgbuilder/red/comms.js b/dgbuilder/red/comms.js new file mode 100644 index 00000000..5828c996 --- /dev/null +++ b/dgbuilder/red/comms.js @@ -0,0 +1,132 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var ws = require("ws"); +var util = require("util"); + +var server; +var settings; + +var wsServer; +var activeConnections = []; + +var retained = {}; + +var heartbeatTimer; +var lastSentTime; + + +function init(_server,_settings) { + server = _server; + settings = _settings; +} + +function start() { + + if (!settings.disableEditor) { + var webSocketKeepAliveTime = settings.webSocketKeepAliveTime || 15000; + var path = settings.httpAdminRoot || "/"; + path = path + (path.slice(-1) == "/" ? "":"/") + "comms"; + wsServer = new ws.Server({server:server,path:path}); + + wsServer.on('connection',function(ws) { + activeConnections.push(ws); + ws.on('close',function() { + for (var i=0;i<activeConnections.length;i++) { + if (activeConnections[i] === ws) { + activeConnections.splice(i,1); + break; + } + } + }); + ws.on('message', function(data,flags) { + var msg = null; + try { + msg = JSON.parse(data); + } catch(err) { + util.log("[red:comms] received malformed message : "+err.toString()); + return; + } + if (msg.subscribe) { + handleRemoteSubscription(ws,msg.subscribe); + } + }); + ws.on('error', function(err) { + util.log("[red:comms] error : "+err.toString()); + }); + }); + + wsServer.on('error', function(err) { + util.log("[red:comms] server error : "+err.toString()); + }); + + lastSentTime = Date.now(); + + heartbeatTimer = setInterval(function() { + var now = Date.now(); + if (now-lastSentTime > webSocketKeepAliveTime) { + publish("hb",lastSentTime); + } + }, webSocketKeepAliveTime); + } +} + +function stop() { + if (heartbeatTimer) { + clearInterval(heartbeatTimer); + } + if (wsServer) { + wsServer.close(); + } +} + +function publish(topic,data,retain) { + if (retain) { + retained[topic] = data; + } else { + delete retained[topic]; + } + lastSentTime = Date.now(); + activeConnections.forEach(function(conn) { + publishTo(conn,topic,data); + }); +} + +function publishTo(ws,topic,data) { + var msg = JSON.stringify({topic:topic,data:data}); + try { + ws.send(msg); + } catch(err) { + util.log("[red:comms] send error : "+err.toString()); + } +} + +function handleRemoteSubscription(ws,topic) { + var re = new RegExp("^"+topic.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$"); + for (var t in retained) { + if (re.test(t)) { + publishTo(ws,t,retained[t]); + } + } +} + + +module.exports = { + init:init, + start:start, + stop:stop, + publish:publish, +} diff --git a/dgbuilder/red/events.js b/dgbuilder/red/events.js new file mode 100644 index 00000000..89590494 --- /dev/null +++ b/dgbuilder/red/events.js @@ -0,0 +1,19 @@ +/** + * Copyright 2013 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var events = require("events"); + +module.exports = new events.EventEmitter(); diff --git a/dgbuilder/red/library.js b/dgbuilder/red/library.js new file mode 100644 index 00000000..cc4199f2 --- /dev/null +++ b/dgbuilder/red/library.js @@ -0,0 +1,117 @@ +/** + * Copyright 2013 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require("util"); + +var redApp = null; +var storage = null; + +function init() { + redApp = require("./server").app; + storage = require("./storage"); + + // -------- Flow Library -------- + redApp.post(new RegExp("/library/flows\/(.*)"), function(req,res) { + var fullBody = ''; + req.on('data', function(chunk) { + fullBody += chunk.toString(); + }); + req.on('end', function() { + storage.saveFlow(req.params[0],fullBody).then(function() { + res.send(204); + }).otherwise(function(err) { + util.log("[red] Error loading flow '"+req.params[0]+"' : "+err); + if (err.message.indexOf('forbidden') === 0) { + res.send(403); + return; + } + res.send(500); + }); + }); + }); + + redApp.get("/library/flows",function(req,res) { + storage.getAllFlows().then(function(flows) { + res.json(flows); + }); + }); + + redApp.get(new RegExp("/library/flows\/(.*)"), function(req,res) { + storage.getFlow(req.params[0]).then(function(data) { + res.set('Content-Type', 'application/json'); + res.send(data); + }).otherwise(function(err) { + if (err) { + util.log("[red] Error loading flow '"+req.params[0]+"' : "+err); + if (err.message.indexOf('forbidden') === 0) { + res.send(403); + return; + } + } + res.send(404); + }); + }); + + // ------------------------------ +} + +function createLibrary(type) { + + redApp.get(new RegExp("/library/"+type+"($|\/(.*))"),function(req,res) { + var path = req.params[1]||""; + storage.getLibraryEntry(type,path).then(function(result) { + if (typeof result === "string") { + res.writeHead(200, {'Content-Type': 'text/plain'}); + res.write(result); + res.end(); + } else { + res.json(result); + } + }).otherwise(function(err) { + if (err) { + util.log("[red] Error loading library entry '"+path+"' : "+err); + if (err.message.indexOf('forbidden') === 0) { + res.send(403); + return; + } + } + res.send(404); + }); + }); + + redApp.post(new RegExp("/library/"+type+"\/(.*)"),function(req,res) { + var path = req.params[0]; + var fullBody = ''; + req.on('data', function(chunk) { + fullBody += chunk.toString(); + }); + req.on('end', function() { + storage.saveLibraryEntry(type,path,req.query,fullBody).then(function() { + res.send(204); + }).otherwise(function(err) { + util.log("[red] Error saving library entry '"+path+"' : "+err); + if (err.message.indexOf('forbidden') === 0) { + res.send(403); + return; + } + res.send(500); + }); + }); + }); +} + +module.exports.init = init; +module.exports.register = createLibrary; diff --git a/dgbuilder/red/log.js b/dgbuilder/red/log.js new file mode 100644 index 00000000..236e6df0 --- /dev/null +++ b/dgbuilder/red/log.js @@ -0,0 +1,39 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require("util"); +var EventEmitter = require("events").EventEmitter; + +var logHandlers = []; + +var ConsoleLogHandler = new EventEmitter(); +ConsoleLogHandler.on("log",function(msg) { + util.log("["+msg.level+"] ["+msg.type+":"+(msg.name||msg.id)+"] "+msg.msg); +}); + +var log = module.exports = { + addHandler: function(func) { + logHandlers.push(func); + }, + + log: function(msg) { + logHandlers.forEach(function(handler) { + handler.emit("log",msg); + }); + } +} + +log.addHandler(ConsoleLogHandler); diff --git a/dgbuilder/red/nodes/Node.js b/dgbuilder/red/nodes/Node.js new file mode 100644 index 00000000..0e6fc525 --- /dev/null +++ b/dgbuilder/red/nodes/Node.js @@ -0,0 +1,147 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require("util"); +var EventEmitter = require("events").EventEmitter; +var clone = require("clone"); +var when = require("when"); + +var flows = require("./flows"); +var comms = require("../comms"); + +function Node(n) { + this.id = n.id; + flows.add(this); + this.type = n.type; + if (n.name) { + this.name = n.name; + } + this.wires = n.wires||[]; +} + +util.inherits(Node,EventEmitter); + +Node.prototype._on = Node.prototype.on; + +Node.prototype.on = function(event,callback) { + var node = this; + if (event == "close") { + if (callback.length == 1) { + this.close = function() { + return when.promise(function(resolve) { + callback.call(node,function() { + resolve(); + }); + }); + } + } else { + this.close = callback; + } + } else { + this._on(event,callback); + } +} + +Node.prototype.close = function() { +} + +Node.prototype.send = function(msg) { + // instanceof doesn't work for some reason here + if (msg == null) { + return; + } else if (!util.isArray(msg)) { + msg = [msg]; + } + for (var i=0;i<this.wires.length;i++) { + var wires = this.wires[i]; + if (i < msg.length) { + if (msg[i] != null) { + var msgs = msg[i]; + if (!util.isArray(msg[i])) { + msgs = [msg[i]]; + } + //if (wires.length == 1) { + // // Single recipient, don't need to clone the message + // var node = flows.get(wires[0]); + // if (node) { + // for (var k in msgs) { + // var mm = msgs[k]; + // node.receive(mm); + // } + // } + //} else { + // Multiple recipients, must send message copies + for (var j=0;j<wires.length;j++) { + var node = flows.get(wires[j]); + if (node) { + for (var k=0;k<msgs.length;k++) { + var mm = msgs[k]; + // Temporary fix for #97 + // TODO: remove this http-node-specific fix somehow + var req = mm.req; + var res = mm.res; + delete mm.req; + delete mm.res; + var m = clone(mm); + if (req) { + m.req = req; + mm.req = req; + } + if (res) { + m.res = res; + mm.res = res; + } + node.receive(m); + } + } + } + //} + } + } + } +} + +Node.prototype.receive = function(msg) { + this.emit("input",msg); +} + +function log_helper(self, level, msg) { + var o = {level:level, id:self.id, type:self.type, msg:msg}; + if (self.name) { + o.name = self.name; + } + self.emit("log",o); +} + +Node.prototype.log = function(msg) { + log_helper(this, 'log', msg); +} + +Node.prototype.warn = function(msg) { + log_helper(this, 'warn', msg); +} + +Node.prototype.error = function(msg) { + log_helper(this, 'error', msg); +} + +/** + * status: { fill:"red|green", shape:"dot|ring", text:"blah" } + */ +Node.prototype.status = function(status) { + comms.publish("status/"+this.id,status,true); +} +module.exports = Node; diff --git a/dgbuilder/red/nodes/credentials.js b/dgbuilder/red/nodes/credentials.js new file mode 100644 index 00000000..22e78d81 --- /dev/null +++ b/dgbuilder/red/nodes/credentials.js @@ -0,0 +1,208 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require("util"); +var when = require("when"); + +var credentialCache = {}; +var storage = null; +var credentialsDef = {}; +var redApp = null; + +/** + * Adds an HTTP endpoint to allow look up of credentials for a given node id. + */ +function registerEndpoint(type) { + redApp.get('/credentials/' + type + '/:id', function (req, res) { + // TODO: This could be a generic endpoint with the type value + // parameterised. + // + // TODO: It should verify the given node id is of the type specified - + // but that would add a dependency from this module to the + // registry module that knows about node types. + var nodeType = type; + var nodeID = req.params.id; + + var credentials = credentialCache[nodeID]; + if (credentials === undefined) { + res.json({}); + return; + } + var definition = credentialsDef[nodeType]; + + var sendCredentials = {}; + for (var cred in definition) { + if (definition.hasOwnProperty(cred)) { + if (definition[cred].type == "password") { + var key = 'has_' + cred; + sendCredentials[key] = credentials[cred] != null && credentials[cred] !== ''; + continue; + } + sendCredentials[cred] = credentials[cred] || ''; + } + } + res.json(sendCredentials); + + }); +} + + +module.exports = { + init: function (_storage) { + storage = _storage; + // TODO: this should get passed in init function call rather than + // required directly. + redApp = require("../server").app; + }, + + /** + * Loads the credentials from storage. + */ + load: function () { + return storage.getCredentials().then(function (creds) { + credentialCache = creds; + }).otherwise(function (err) { + util.log("[red] Error loading credentials : " + err); + }); + }, + + /** + * Adds a set of credentials for the given node id. + * @param id the node id for the credentials + * @param creds an object of credential key/value pairs + * @return a promise for the saving of credentials to storage + */ + add: function (id, creds) { + credentialCache[id] = creds; + return storage.saveCredentials(credentialCache); + }, + + /** + * Gets the credentials for the given node id. + * @param id the node id for the credentials + * @return the credentials + */ + get: function (id) { + return credentialCache[id]; + }, + + /** + * Deletes the credentials for the given node id. + * @param id the node id for the credentials + * @return a promise for the saving of credentials to storage + */ + delete: function (id) { + delete credentialCache[id]; + storage.saveCredentials(credentialCache); + }, + + /** + * Deletes any credentials for nodes that no longer exist + * @param getNode a function that can return a node for a given id + * @return a promise for the saving of credentials to storage + */ + clean: function (getNode) { + var deletedCredentials = false; + for (var c in credentialCache) { + if (credentialCache.hasOwnProperty(c)) { + var n = getNode(c); + if (!n) { + deletedCredentials = true; + delete credentialCache[c]; + } + } + } + if (deletedCredentials) { + return storage.saveCredentials(credentialCache); + } else { + return when.resolve(); + } + }, + + /** + * Registers a node credential definition. + * @param type the node type + * @param definition the credential definition + */ + register: function (type, definition) { + var dashedType = type.replace(/\s+/g, '-'); + credentialsDef[dashedType] = definition; + registerEndpoint(dashedType); + }, + + /** + * Extracts and stores any credential updates in the provided node. + * The provided node may have a .credentials property that contains + * new credentials for the node. + * This function loops through the credentials in the definition for + * the node-type and applies any of the updates provided in the node. + * + * This function does not save the credentials to disk as it is expected + * to be called multiple times when a new flow is deployed. + * + * @param node the node to extract credentials from + */ + extract: function(node) { + var nodeID = node.id; + var nodeType = node.type; + var newCreds = node.credentials; + if (newCreds) { + var savedCredentials = credentialCache[nodeID] || {}; + + var dashedType = nodeType.replace(/\s+/g, '-'); + var definition = credentialsDef[dashedType]; + + if (!definition) { + util.log('Credential Type ' + nodeType + ' is not registered.'); + return; + } + + for (var cred in definition) { + if (definition.hasOwnProperty(cred)) { + if (newCreds[cred] === undefined) { + continue; + } + if (definition[cred].type == "password" && newCreds[cred] == '__PWRD__') { + continue; + } + if (0 === newCreds[cred].length || /^\s*$/.test(newCreds[cred])) { + delete savedCredentials[cred]; + continue; + } + savedCredentials[cred] = newCreds[cred]; + } + } + credentialCache[nodeID] = savedCredentials; + } + }, + + /** + * Saves the credentials to storage + * @return a promise for the saving of credentials to storage + */ + save: function () { + return storage.saveCredentials(credentialCache); + }, + + /** + * Gets the credential definition for the given node type + * @param type the node type + * @return the credential definition + */ + getDefinition: function (type) { + return credentialsDef[type]; + } +} diff --git a/dgbuilder/red/nodes/flows.js b/dgbuilder/red/nodes/flows.js new file mode 100644 index 00000000..b0b5d514 --- /dev/null +++ b/dgbuilder/red/nodes/flows.js @@ -0,0 +1,220 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require("util"); +var when = require("when"); + +var typeRegistry = require("./registry"); +var credentials = require("./credentials"); +var log = require("../log"); +var events = require("../events"); + +var storage = null; + +var nodes = {}; +var activeConfig = []; +var missingTypes = []; + +events.on('type-registered',function(type) { + if (missingTypes.length > 0) { + var i = missingTypes.indexOf(type); + if (i != -1) { + missingTypes.splice(i,1); + util.log("[red] Missing type registered: "+type); + if (missingTypes.length === 0) { + parseConfig(); + } + } + } +}); + +/** + * Parses the current activeConfig and creates the required node instances + */ +function parseConfig() { + var i; + var nt; + missingTypes = []; + + // Scan the configuration for any unknown node types + for (i=0;i<activeConfig.length;i++) { + var type = activeConfig[i].type; + // TODO: remove workspace in next release+1 + if (type != "workspace" && type != "tab") { + nt = typeRegistry.get(type); + if (!nt && missingTypes.indexOf(type) == -1) { + missingTypes.push(type); + } + } + } + // Abort if there are any missing types + if (missingTypes.length > 0) { + util.log("[red] Waiting for missing types to be registered:"); + for (i=0;i<missingTypes.length;i++) { + util.log("[red] - "+missingTypes[i]); + } + return; + } + + util.log("[red] Starting flows"); + events.emit("nodes-starting"); + + // Instantiate each node in the flow + for (i=0;i<activeConfig.length;i++) { + var nn = null; + // TODO: remove workspace in next release+1 + if (activeConfig[i].type != "workspace" && activeConfig[i].type != "tab") { + nt = typeRegistry.get(activeConfig[i].type); + if (nt) { + try { + nn = new nt(activeConfig[i]); + } + catch (err) { + util.log("[red] "+activeConfig[i].type+" : "+err); + } + } + // console.log(nn); + if (nn === null) { + util.log("[red] unknown type: "+activeConfig[i].type); + } + } + } + // Clean up any orphaned credentials + credentials.clean(flowNodes.get); + events.emit("nodes-started"); +} + +/** + * Stops the current activeConfig + */ +function stopFlows() { + if (activeConfig&&activeConfig.length > 0) { + util.log("[red] Stopping flows"); + } + return flowNodes.clear(); +} + +var flowNodes = module.exports = { + init: function(_storage) { + storage = _storage; + }, + + /** + * Load the current activeConfig from storage and start it running + * @return a promise for the loading of the config + */ + load: function() { + return storage.getFlows().then(function(flows) { + return credentials.load().then(function() { + activeConfig = flows; + if (activeConfig && activeConfig.length > 0) { + parseConfig(); + } + }); + }).otherwise(function(err) { + util.log("[red] Error loading flows : "+err); + }); + }, + + /** + * Add a node to the current active set + * @param n the node to add + */ + add: function(n) { + nodes[n.id] = n; + n.on("log",log.log); + }, + + /** + * Get a node + * @param i the node id + * @return the node + */ + get: function(i) { + return nodes[i]; + }, + + /** + * Stops all active nodes and clears the active set + * @return a promise for the stopping of all active nodes + */ + clear: function() { + return when.promise(function(resolve) { + events.emit("nodes-stopping"); + var promises = []; + for (var n in nodes) { + if (nodes.hasOwnProperty(n)) { + try { + var p = nodes[n].close(); + if (p) { + promises.push(p); + } + } catch(err) { + nodes[n].error(err); + } + } + } + when.settle(promises).then(function() { + events.emit("nodes-stopped"); + nodes = {}; + resolve(); + }); + }); + }, + + /** + * Provides an iterator over the active set of nodes + * @param cb a function to be called for each node in the active set + */ + each: function(cb) { + for (var n in nodes) { + if (nodes.hasOwnProperty(n)) { + cb(nodes[n]); + } + } + }, + + /** + * @return the active configuration + */ + getFlows: function() { + return activeConfig; + }, + + /** + * Sets the current active config. + * @param config the configuration to enable + * @return a promise for the starting of the new flow + */ + setFlows: function (config) { + // Extract any credential updates + for (var i=0; i<config.length; i++) { + var node = config[i]; + if (node.credentials) { + credentials.extract(node); + delete node.credentials; + } + } + return credentials.save() + .then(function() { return storage.saveFlows(config);}) + .then(function() { return stopFlows();}) + .then(function () { + activeConfig = config; + parseConfig(); + }); + }, + stopFlows: stopFlows +}; diff --git a/dgbuilder/red/nodes/index.js b/dgbuilder/red/nodes/index.js new file mode 100644 index 00000000..3d5ad719 --- /dev/null +++ b/dgbuilder/red/nodes/index.js @@ -0,0 +1,134 @@ +/** + * Copyright 2013, 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +var registry = require("./registry"); +var credentials = require("./credentials"); +var flows = require("./flows"); +var Node = require("./Node"); + +/** + * Registers a node constructor + * @param type - the string type name + * @param constructor - the constructor function for this node type + * @param opts - optional additional options for the node + */ +function registerType(type,constructor,opts) { + if (opts && opts.credentials) { + credentials.register(type,opts.credentials); + } + registry.registerType(type,constructor); +} + +/** + * Called from a Node's constructor function, invokes the super-class + * constructor and attaches any credentials to the node. + * @param node the node object being created + * @param def the instance definition for the node + */ +function createNode(node,def) { + Node.call(node,def); + var creds = credentials.get(node.id); + if (creds) { + node.credentials = creds; + } +} + +function init(_settings,storage) { + credentials.init(storage); + flows.init(storage); + registry.init(_settings); +} + +function checkTypeInUse(id) { + var nodeInfo = registry.getNodeInfo(id); + if (!nodeInfo) { + throw new Error("Unrecognised id: "+info); + } + var inUse = {}; + flows.each(function(n) { + inUse[n.type] = (inUse[n.type]||0)+1; + }); + var nodesInUse = []; + nodeInfo.types.forEach(function(t) { + if (inUse[t]) { + nodesInUse.push(t); + } + }); + if (nodesInUse.length > 0) { + var msg = nodesInUse.join(", "); + throw new Error("Type in use: "+msg); + } +} + +function removeNode(id) { + checkTypeInUse(id); + return registry.removeNode(id); +} + +function removeModule(module) { + var info = registry.getNodeModuleInfo(module); + for (var i=0;i<info.nodes.length;i++) { + checkTypeInUse(info.nodes[i]); + } + return registry.removeModule(module); +} + + +function disableNode(id) { + checkTypeInUse(id); + return registry.disableNode(id); +} + +module.exports = { + // Lifecycle + init: init, + load: registry.load, + + // Node registry + createNode: createNode, + getNode: flows.get, + + addNode: registry.addNode, + removeNode: removeNode, + + addModule: registry.addModule, + removeModule: removeModule, + + enableNode: registry.enableNode, + disableNode: disableNode, + + // Node type registry + registerType: registerType, + getType: registry.get, + getNodeInfo: registry.getNodeInfo, + getNodeModuleInfo: registry.getNodeModuleInfo, + getNodeList: registry.getNodeList, + getNodeConfigs: registry.getNodeConfigs, + getNodeConfig: registry.getNodeConfig, + clearRegistry: registry.clear, + cleanNodeList: registry.cleanNodeList, + + // Flow handling + loadFlows: flows.load, + stopFlows: flows.stopFlows, + setFlows: flows.setFlows, + getFlows: flows.getFlows, + + // Credentials + addCredentials: credentials.add, + getCredentials: credentials.get, + deleteCredentials: credentials.delete +} + diff --git a/dgbuilder/red/nodes/registry.js b/dgbuilder/red/nodes/registry.js new file mode 100644 index 00000000..f2073aff --- /dev/null +++ b/dgbuilder/red/nodes/registry.js @@ -0,0 +1,693 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require("util"); +var when = require("when"); +var whenNode = require('when/node'); +var fs = require("fs"); +var path = require("path"); +var crypto = require("crypto"); +var UglifyJS = require("uglify-js"); + +var events = require("../events"); + +var Node; +var settings; + +function filterNodeInfo(n) { + var r = { + id: n.id, + name: n.name, + types: n.types, + enabled: n.enabled + } + if (n.hasOwnProperty("loaded")) { + r.loaded = n.loaded; + } + if (n.hasOwnProperty("module")) { + r.module = n.module; + } + if (n.hasOwnProperty("err")) { + r.err = n.err.toString(); + } + return r; +} + +var registry = (function() { + var nodeConfigCache = null; + var nodeConfigs = {}; + var nodeList = []; + var nodeConstructors = {}; + var nodeTypeToId = {}; + var nodeModules = {}; + + function saveNodeList() { + var nodeList = {}; + + for (var i in nodeConfigs) { + if (nodeConfigs.hasOwnProperty(i)) { + var nodeConfig = nodeConfigs[i]; + var n = filterNodeInfo(nodeConfig); + n.file = nodeConfig.file; + delete n.loaded; + delete n.err; + delete n.file; + delete n.id; + nodeList[i] = n; + } + } + if (settings.available()) { + return settings.set("nodes",nodeList); + } else { + return when.reject("Settings unavailable"); + } + } + + return { + init: function() { + if (settings.available()) { + nodeConfigs = settings.get("nodes")||{}; + // Restore the node id property to individual entries + for (var id in nodeConfigs) { + if (nodeConfigs.hasOwnProperty(id)) { + nodeConfigs[id].id = id; + } + } + } else { + nodeConfigs = {}; + } + nodeModules = {}; + nodeTypeToId = {}; + nodeConstructors = {}; + nodeList = []; + nodeConfigCache = null; + }, + + addNodeSet: function(id,set) { + if (!set.err) { + set.types.forEach(function(t) { + nodeTypeToId[t] = id; + }); + } + + if (set.module) { + nodeModules[set.module] = nodeModules[set.module]||{nodes:[]}; + nodeModules[set.module].nodes.push(id); + } + + nodeConfigs[id] = set; + nodeList.push(id); + nodeConfigCache = null; + }, + removeNode: function(id) { + var config = nodeConfigs[id]; + if (!config) { + throw new Error("Unrecognised id: "+id); + } + delete nodeConfigs[id]; + var i = nodeList.indexOf(id); + if (i > -1) { + nodeList.splice(i,1); + } + config.types.forEach(function(t) { + delete nodeConstructors[t]; + delete nodeTypeToId[t]; + }); + config.enabled = false; + config.loaded = false; + nodeConfigCache = null; + return filterNodeInfo(config); + }, + removeModule: function(module) { + if (!settings.available()) { + throw new Error("Settings unavailable"); + } + var nodes = nodeModules[module]; + if (!nodes) { + throw new Error("Unrecognised module: "+module); + } + var infoList = []; + for (var i=0;i<nodes.nodes.length;i++) { + infoList.push(registry.removeNode(nodes.nodes[i])); + } + delete nodeModules[module]; + saveNodeList(); + return infoList; + }, + getNodeInfo: function(typeOrId) { + if (nodeTypeToId[typeOrId]) { + return filterNodeInfo(nodeConfigs[nodeTypeToId[typeOrId]]); + } else if (nodeConfigs[typeOrId]) { + return filterNodeInfo(nodeConfigs[typeOrId]); + } + return null; + }, + getNodeList: function() { + var list = []; + for (var id in nodeConfigs) { + if (nodeConfigs.hasOwnProperty(id)) { + list.push(filterNodeInfo(nodeConfigs[id])) + } + } + return list; + }, + registerNodeConstructor: function(type,constructor) { + if (nodeConstructors[type]) { + throw new Error(type+" already registered"); + } + //TODO: Ensure type is known - but doing so will break some tests + // that don't have a way to register a node template ahead + // of registering the constructor + util.inherits(constructor,Node); + nodeConstructors[type] = constructor; + events.emit("type-registered",type); + }, + + + /** + * Gets all of the node template configs + * @return all of the node templates in a single string + */ + getAllNodeConfigs: function() { + if (!nodeConfigCache) { + var result = ""; + var script = ""; + for (var i=0;i<nodeList.length;i++) { + var config = nodeConfigs[nodeList[i]]; + if (config.enabled && !config.err) { + result += config.config; + script += config.script; + } + } + if (script.length > 0) { + result += '<script type="text/javascript">'; + result += UglifyJS.minify(script, {fromString: true}).code; + result += '</script>'; + } + nodeConfigCache = result; + } + return nodeConfigCache; + }, + + getNodeConfig: function(id) { + var config = nodeConfigs[id]; + if (config) { + var result = config.config; + if (config.script) { + result += '<script type="text/javascript">'+config.script+'</script>'; + } + return result; + } else { + return null; + } + }, + + getNodeConstructor: function(type) { + var config = nodeConfigs[nodeTypeToId[type]]; + if (!config || (config.enabled && !config.err)) { + return nodeConstructors[type]; + } + return null; + }, + + clear: function() { + nodeConfigCache = null; + nodeConfigs = {}; + nodeList = []; + nodeConstructors = {}; + nodeTypeToId = {}; + }, + + getTypeId: function(type) { + return nodeTypeToId[type]; + }, + + getModuleInfo: function(type) { + return nodeModules[type]; + }, + + enableNodeSet: function(id) { + if (!settings.available()) { + throw new Error("Settings unavailable"); + } + var config = nodeConfigs[id]; + if (config) { + delete config.err; + config.enabled = true; + if (!config.loaded) { + // TODO: honour the promise this returns + loadNodeModule(config); + } + nodeConfigCache = null; + saveNodeList(); + } else { + throw new Error("Unrecognised id: "+id); + } + return filterNodeInfo(config); + }, + + disableNodeSet: function(id) { + if (!settings.available()) { + throw new Error("Settings unavailable"); + } + var config = nodeConfigs[id]; + if (config) { + // TODO: persist setting + config.enabled = false; + nodeConfigCache = null; + saveNodeList(); + } else { + throw new Error("Unrecognised id: "+id); + } + return filterNodeInfo(config); + }, + + saveNodeList: saveNodeList, + + cleanNodeList: function() { + var removed = false; + for (var id in nodeConfigs) { + if (nodeConfigs.hasOwnProperty(id)) { + if (nodeConfigs[id].module && !nodeModules[nodeConfigs[id].module]) { + registry.removeNode(id); + removed = true; + } + } + } + if (removed) { + saveNodeList(); + } + } + } +})(); + + + +function init(_settings) { + Node = require("./Node"); + settings = _settings; + registry.init(); +} + +/** + * Synchronously walks the directory looking for node files. + * Emits 'node-icon-dir' events for an icon dirs found + * @param dir the directory to search + * @return an array of fully-qualified paths to .js files + */ +function getNodeFiles(dir) { + var result = []; + var files = []; + try { + files = fs.readdirSync(dir); + } catch(err) { + return result; + } + files.sort(); + files.forEach(function(fn) { + var stats = fs.statSync(path.join(dir,fn)); + if (stats.isFile()) { + if (/\.js$/.test(fn)) { + var valid = true; + if (settings.nodesExcludes) { + for (var i=0;i<settings.nodesExcludes.length;i++) { + if (settings.nodesExcludes[i] == fn) { + valid = false; + break; + } + } + } + valid = valid && fs.existsSync(path.join(dir,fn.replace(/\.js$/,".html"))) + + if (valid) { + result.push(path.join(dir,fn)); + } + } + } else if (stats.isDirectory()) { + // Ignore /.dirs/, /lib/ /node_modules/ + if (!/^(\..*|lib|icons|node_modules|test)$/.test(fn)) { + result = result.concat(getNodeFiles(path.join(dir,fn))); + } else if (fn === "icons") { + events.emit("node-icon-dir",path.join(dir,fn)); + } + } + }); + return result; +} + +/** + * Scans the node_modules path for nodes + * @param moduleName the name of the module to be found + * @return a list of node modules: {dir,package} + */ +function scanTreeForNodesModules(moduleName) { + var dir = __dirname+"/../../nodes"; + var results = []; + var up = path.resolve(path.join(dir,"..")); + while (up !== dir) { + var pm = path.join(dir,"node_modules"); + try { + var files = fs.readdirSync(pm); + for (var i=0;i<files.length;i++) { + var fn = files[i]; + if (!registry.getModuleInfo(fn)) { + if (!moduleName || fn == moduleName) { + var pkgfn = path.join(pm,fn,"package.json"); + try { + var pkg = require(pkgfn); + if (pkg['node-red']) { + var moduleDir = path.join(pm,fn); + results.push({dir:moduleDir,package:pkg}); + } + } catch(err) { + if (err.code != "MODULE_NOT_FOUND") { + // TODO: handle unexpected error + } + } + if (fn == moduleName) { + break; + } + } + } + } + } catch(err) { + } + + dir = up; + up = path.resolve(path.join(dir,"..")); + } + return results; +} + +/** + * Loads the nodes provided in an npm package. + * @param moduleDir the root directory of the package + * @param pkg the module's package.json object + */ +function loadNodesFromModule(moduleDir,pkg) { + var nodes = pkg['node-red'].nodes||{}; + var results = []; + var iconDirs = []; + for (var n in nodes) { + if (nodes.hasOwnProperty(n)) { + var file = path.join(moduleDir,nodes[n]); + try { + results.push(loadNodeConfig(file,pkg.name,n)); + } catch(err) { + } + var iconDir = path.join(moduleDir,path.dirname(nodes[n]),"icons"); + if (iconDirs.indexOf(iconDir) == -1) { + if (fs.existsSync(iconDir)) { + events.emit("node-icon-dir",iconDir); + iconDirs.push(iconDir); + } + } + } + } + return results; +} + + +/** + * Loads a node's configuration + * @param file the fully qualified path of the node's .js file + * @param name the name of the node + * @return the node object + * { + * id: a unqiue id for the node file + * name: the name of the node file, or label from the npm module + * file: the fully qualified path to the node's .js file + * template: the fully qualified path to the node's .html file + * config: the non-script parts of the node's .html file + * script: the script part of the node's .html file + * types: an array of node type names in this file + * } + */ +function loadNodeConfig(file,module,name) { + var id = crypto.createHash('sha1').update(file).digest("hex"); + if (module && name) { + var newid = crypto.createHash('sha1').update(module+":"+name).digest("hex"); + var existingInfo = registry.getNodeInfo(id); + if (existingInfo) { + // For a brief period, id for modules were calculated incorrectly. + // To prevent false-duplicates, this removes the old id entry + registry.removeNode(id); + registry.saveNodeList(); + } + id = newid; + + } + var info = registry.getNodeInfo(id); + + var isEnabled = true; + + if (info) { + if (info.hasOwnProperty("loaded")) { + throw new Error(file+" already loaded"); + } + isEnabled = info.enabled; + } + + var node = { + id: id, + file: file, + template: file.replace(/\.js$/,".html"), + enabled: isEnabled, + loaded:false + } + + if (module) { + node.name = module+":"+name; + node.module = module; + } else { + node.name = path.basename(file) + } + try { + var content = fs.readFileSync(node.template,'utf8'); + + var types = []; + + var regExp = /<script ([^>]*)data-template-name=['"]([^'"]*)['"]/gi; + var match = null; + + while((match = regExp.exec(content)) !== null) { + types.push(match[2]); + } + node.types = types; + node.config = content; + + // TODO: parse out the javascript portion of the template + node.script = ""; + + for (var i=0;i<node.types.length;i++) { + if (registry.getTypeId(node.types[i])) { + node.err = node.types[i]+" already registered"; + break; + } + } + } catch(err) { + node.types = []; + if (err.code === 'ENOENT') { + node.err = "Error: "+file+" does not exist"; + } else { + node.err = err.toString(); + } + } + registry.addNodeSet(id,node); + return node; +} + +/** + * Loads all palette nodes + * @param defaultNodesDir optional parameter, when set, it overrides the default + * location of nodeFiles - used by the tests + * @return a promise that resolves on completion of loading + */ +function load(defaultNodesDir,disableNodePathScan) { + return when.promise(function(resolve,reject) { + // Find all of the nodes to load + var nodeFiles; + if(defaultNodesDir) { + nodeFiles = getNodeFiles(path.resolve(defaultNodesDir)); + } else { + nodeFiles = getNodeFiles(__dirname+"/../../nodes"); + } + + if (settings.nodesDir) { + var dir = settings.nodesDir; + if (typeof settings.nodesDir == "string") { + dir = [dir]; + } + for (var i=0;i<dir.length;i++) { + nodeFiles = nodeFiles.concat(getNodeFiles(dir[i])); + } + } + var nodes = []; + nodeFiles.forEach(function(file) { + try { + nodes.push(loadNodeConfig(file)); + } catch(err) { + // + } + }); + + // TODO: disabling npm module loading if defaultNodesDir set + // This indicates a test is being run - don't want to pick up + // unexpected nodes. + // Urgh. + if (!disableNodePathScan) { + // Find all of the modules containing nodes + var moduleFiles = scanTreeForNodesModules(); + moduleFiles.forEach(function(moduleFile) { + nodes = nodes.concat(loadNodesFromModule(moduleFile.dir,moduleFile.package)); + }); + } + var promises = []; + nodes.forEach(function(node) { + if (!node.err) { + promises.push(loadNodeModule(node)); + } + }); + + //resolve([]); + when.settle(promises).then(function(results) { + // Trigger a load of the configs to get it precached + registry.getAllNodeConfigs(); + + if (settings.available()) { + resolve(registry.saveNodeList()); + } else { + resolve(); + } + }); + }); +} + +/** + * Loads the specified node into the runtime + * @param node a node info object - see loadNodeConfig + * @return a promise that resolves to an update node info object. The object + * has the following properties added: + * err: any error encountered whilst loading the node + * + */ +function loadNodeModule(node) { + var nodeDir = path.dirname(node.file); + var nodeFn = path.basename(node.file); + if (!node.enabled) { + return when.resolve(node); + } + try { + var loadPromise = null; + var r = require(node.file); + if (typeof r === "function") { + var promise = r(require('../red')); + if (promise != null && typeof promise.then === "function") { + loadPromise = promise.then(function() { + node.enabled = true; + node.loaded = true; + return node; + }).otherwise(function(err) { + node.err = err; + return node; + }); + } + } + if (loadPromise == null) { + node.enabled = true; + node.loaded = true; + loadPromise = when.resolve(node); + } + return loadPromise; + } catch(err) { + node.err = err; + return when.resolve(node); + } +} + +function loadNodeList(nodes) { + var promises = []; + nodes.forEach(function(node) { + if (!node.err) { + promises.push(loadNodeModule(node)); + } else { + promises.push(node); + } + }); + + return when.settle(promises).then(function(results) { + return registry.saveNodeList().then(function() { + var list = results.map(function(r) { + return filterNodeInfo(r.value); + }); + return list; + }); + }); +} + +function addNode(file) { + if (!settings.available()) { + throw new Error("Settings unavailable"); + } + var nodes = []; + try { + nodes.push(loadNodeConfig(file)); + } catch(err) { + return when.reject(err); + } + return loadNodeList(nodes); +} + +function addModule(module) { + if (!settings.available()) { + throw new Error("Settings unavailable"); + } + var nodes = []; + if (registry.getModuleInfo(module)) { + return when.reject(new Error("Module already loaded")); + } + var moduleFiles = scanTreeForNodesModules(module); + if (moduleFiles.length === 0) { + var err = new Error("Cannot find module '" + module + "'"); + err.code = 'MODULE_NOT_FOUND'; + return when.reject(err); + } + moduleFiles.forEach(function(moduleFile) { + nodes = nodes.concat(loadNodesFromModule(moduleFile.dir,moduleFile.package)); + }); + return loadNodeList(nodes); +} + +module.exports = { + init:init, + load:load, + clear: registry.clear, + registerType: registry.registerNodeConstructor, + get: registry.getNodeConstructor, + getNodeInfo: registry.getNodeInfo, + getNodeModuleInfo: registry.getModuleInfo, + getNodeList: registry.getNodeList, + getNodeConfigs: registry.getAllNodeConfigs, + getNodeConfig: registry.getNodeConfig, + addNode: addNode, + removeNode: registry.removeNode, + enableNode: registry.enableNodeSet, + disableNode: registry.disableNodeSet, + + addModule: addModule, + removeModule: registry.removeModule, + cleanNodeList: registry.cleanNodeList +} diff --git a/dgbuilder/red/red.js b/dgbuilder/red/red.js new file mode 100644 index 00000000..2735e0da --- /dev/null +++ b/dgbuilder/red/red.js @@ -0,0 +1,68 @@ +/** + * Copyright 2013 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var server = require("./server"); +var nodes = require("./nodes"); +var library = require("./library"); +var comms = require("./comms"); +var log = require("./log"); +var util = require("./util"); +var fs = require("fs"); +var settings = require("./settings"); +var credentials = require("./nodes/credentials"); + +var path = require('path'); + +process.env.NODE_RED_HOME = process.env.NODE_RED_HOME || path.resolve(__dirname+"/.."); + +var events = require("events"); + +var RED = { + + init: function(httpServer,userSettings) { + userSettings.version = this.version(); + settings.init(userSettings); + server.init(httpServer,settings); + library.init(); + return server.app; + }, + + start: server.start, + stop: server.stop, + nodes: nodes, + library: library, + credentials: credentials, + events: events, + log: log, + comms: comms, + settings:settings, + util: util, + version: function () { + var p = require(path.join(process.env.NODE_RED_HOME,"package.json")); + if (fs.existsSync(path.join(process.env.NODE_RED_HOME,".git"))) { + return p.version+".git"; + } else { + return p.version; + } + } +}; + +RED.__defineGetter__("app", function() { console.log("Deprecated use of RED.app - use RED.httpAdmin instead"); return server.app }); +RED.__defineGetter__("httpAdmin", function() { return server.app }); +RED.__defineGetter__("httpNode", function() { return server.nodeApp }); +RED.__defineGetter__("server", function() { return server.server }); + +module.exports = RED; diff --git a/dgbuilder/red/server.js b/dgbuilder/red/server.js new file mode 100644 index 00000000..01a769e7 --- /dev/null +++ b/dgbuilder/red/server.js @@ -0,0 +1,1317 @@ +/** + * Copyright 2013 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var express = require('express'); +var util = require('util'); +var multer = require('multer'); +var when = require('when'); +var exec = require('child_process').exec; + +var createUI = require("./ui"); +var redNodes = require("./nodes"); +var comms = require("./comms"); +var storage = require("./storage"); +var fs=require('fs'); +var path = require("path"); +var app = null; +var nodeApp = null; +var server = null; +var settings = null; + +var flowShareUsers = require("../flowShareUsers"); + +//console.dir(flowShareUsers); + +function createServer(_server,_settings) { + server = _server; + settings = _settings; + + comms.init(_server,_settings); + + nodeApp = express(); + app = express(); + + if (settings.httpAdminRoot !== false) { + + + if (!settings.disableEditor) { + createUI(settings,app); + } + + var slaActions = require("./sla"); + + app.get("/flows",function(req,res) { + res.json(redNodes.getFlows()); + }); + + app.get("/loadJSFiles",function(req,res) { + var appDir = path.dirname(require.main.filename); + var generatedJSDir=appDir + "/generatedJS"; + var glob = require("glob") + glob(generatedJSDir + "/**/*.js", null, function (er, files) { + // files is an array of filenames. + // If the `nonull` option is set, and nothing + // was found, then files is ["**/*.js"] + // er is an error object or null. + //console.dir(files); + var sliValuesObj =[]; + for(var i=0;files!= null && i<files.length;i++){ + var f = files[i].replace( new RegExp(generatedJSDir + "/", "g" ), "" ); + console.log("loading file " + f); + try{ + sliValuesObj.push(require(files[i])); + //console.dir(sliValuesObj); + }catch(err){ + console.log("Error:Could not load file " + files[i]); + } + } + res.json({"sliValuesObj" : sliValuesObj}); + }); + }); + + app.get("/loadSelectedModules",function(req,res) { + var appDir = path.dirname(require.main.filename); + var userDir = appDir + "/" + settings.userDir; + var generatedJSDir=appDir + "/generatedJS"; + //console.dir(req); + var selectedModulesStr = req.query.selectedModules; + var selectedModules = []; + if(selectedModulesStr != undefined && selectedModulesStr != null){ + selectedModules = selectedModulesStr.split(","); + } + console.log(selectedModules); + var loaded_modules = {"selected_modules" :selectedModules}; + var file = userDir + "/selected_modules"; + var content = "module.exports=\n" + JSON.stringify(loaded_modules); + try{ + fs.writeFileSync(file, content, 'utf8'); + }catch(err){ + console.log("could not write to file " + file); + } + var sliValuesObj =[]; + for(var i=0;selectedModules!= null && i<selectedModules.length;i++){ + var f = generatedJSDir + "/" + selectedModules[i] + "_inputs.js"; + try{ + delete require.cache[require.resolve(f)] + require.resolve(); + }catch(err){ + console.log("error deleting loaded module " + f + " from cache"); + } + //console.log("loading file " + f); + try{ + sliValuesObj.push(require(f)); + }catch(err){ + console.log("Error:Could not load file " + f); + } + } + //console.dir(sliValuesObj); + res.json({"sliValuesObj" : sliValuesObj}); + }); + + app.get("/initialLoadSelectedModules",function(req,res) { + var appDir = path.dirname(require.main.filename); + var userDir = appDir + "/" + settings.userDir; + var generatedJSDir=appDir + "/generatedJS"; + var file = userDir + "/selected_modules"; + var sliValuesObj =[]; + var selected_modules = []; + var selectedModules; + try{ + selectedModules = require(file); + selected_modules=selectedModules["selected_modules"]; + //console.log("selected_modules are "); + //console.dir(selected_modules); + }catch(err){ + console.log("Could not load the file " + file); + } + for(var i=0;selected_modules!= null && i<selected_modules.length;i++){ + var f = generatedJSDir + "/" + selected_modules[i] + "_inputs.js"; + console.log("loading file " + f); + try{ + sliValuesObj.push(require(f)); + }catch(err){ + console.log("Error:Could not load file " + f); + } + } + res.json({"sliValuesObj" : sliValuesObj}); + }); + + app.get("/listAvailableModules",function(req,res) { + var appDir = path.dirname(require.main.filename); + var userDir = appDir + "/" + settings.userDir; + var generatedJSDir=appDir + "/generatedJS"; + var glob = require("glob") + var file = userDir + "/selected_modules"; + var selected_modules = []; + var selectedModules; + try{ + delete require.cache[require.resolve(file)] + require.resolve(); + }catch(err){ + console.log("error deleting loaded module " + file + " from cache"); + } + try{ + selectedModules = require(file); + selected_modules=selectedModules["selected_modules"]; + console.log("selected_modules are "); + //console.dir(selected_modules); + }catch(err){ + console.log("Could not load the file " + file); + } + glob(generatedJSDir + "/**/*.js", null, function (er, files) { + var filesList =[]; + for(var i=0;files!= null && i<files.length;i++){ + var f = files[i].replace( new RegExp(generatedJSDir + "/", "g" ), "" ); + f = f.replace("_inputs.js",""); + if(selected_modules != undefined && selected_modules != null && selected_modules.indexOf(f) != -1){ + filesList.push(f + ":checked"); + }else{ + filesList.push(f + ":unchecked"); + } + } + res.json({"files" : filesList}); + }); + }); + + app.get("/listSLA",function(req,res) { + var appDir = path.dirname(require.main.filename); + var userDir = appDir + "/" + settings.userDir; + var settingsFile = userDir + "/customSettings.js"; + var jsonObj = require(settingsFile); + slaActions.listSLA(jsonObj,req,res); + }); + + app.get("/listCurrentDGs",function(req,res) { + var appDir = path.dirname(require.main.filename); + var userDir = appDir + "/" + settings.userDir; + var settingsFile = userDir + "/customSettings.js"; + var jsonObj = require(settingsFile); + slaActions.listCurrentDGs(jsonObj,req,res); + }); + + app.get("/activateDG",function(req,res) { + var appDir = path.dirname(require.main.filename); + var userDir = appDir + "/" + settings.userDir; + var settingsFile = userDir + "/customSettings.js"; + var jsonObj = require(settingsFile); + slaActions.activateDG(jsonObj,req,res); + }); + + app.get("/deActivateDG",function(req,res) { + var appDir = path.dirname(require.main.filename); + var userDir = appDir + "/" + settings.userDir; + var settingsFile = userDir + "/customSettings.js"; + var jsonObj = require(settingsFile); + slaActions.deActivateDG(jsonObj,req,res); + }); + + app.get("/deleteDG",function(req,res) { + var appDir = path.dirname(require.main.filename); + var userDir = appDir + "/" + settings.userDir; + var settingsFile = userDir + "/customSettings.js"; + var jsonObj = require(settingsFile); + slaActions.deleteDG(jsonObj,req,res); + }); + + app.get("/getCurrentSettings",function(req,res) { + var appDir = path.dirname(require.main.filename); + var userDir = appDir + "/" + settings.userDir; + //console.log("userDir:" + userDir); + var settingsFile = userDir + "/customSettings.js"; + var jsonObj = require(settingsFile); + res.json(jsonObj); + }); + + app.get("/getCommitsInfo", function(req,res) { + var appDir = path.dirname(require.main.filename); + var userDir = appDir + "/" + settings.userDir; + //console.dir(req); + var filePath = req.query.filePath; + var fullFilePath = userDir + "/codecloud/" + filePath ; + //console.log("fullFilePath:" + fullFilePath); + var exec = require('child_process').exec; + var commandToExec = appDir + "/git_scripts/gitlog " + fullFilePath ; + console.log("commandToExec:" + commandToExec); + var child = exec(commandToExec ,function (error,stdout,stderr){ + if(error){ + console.log("Error occured:" + error); + if(stderr){ + //console.log("stderr:" + stderr); + res.send(500,{'error':error,'stderr':stderr}); + }else{ + res.send(500,{'error':error}); + } + //console.log("stdout :" + stdout); + }else{ + if(stderr){ + console.log("stderr:" + stderr); + } + if(stdout){ + //console.log("output:" + stdout); + res.send(200,{'stdout':stdout,'stderr':stderr}); + } + } + }); + }); + + app.get("/importCodeCloudFlow", + function(req,res) { + var appDir = path.dirname(require.main.filename); + var userDir = appDir + "/" + settings.userDir; + //console.dir(req); + var commitId = req.query.commitId; + var filePath = req.query.filePath; + var fullFilePath = userDir + "/codecloud/" + filePath ; + //console.log("fullFilePath:" + fullFilePath); + var exec = require('child_process').exec; + var commandToExec = appDir + "/git_scripts/gitckout " + commitId + " " + fullFilePath ; + console.log("commandToExec:" + commandToExec); + var child = exec(commandToExec ,{maxBuffer: 1024 * 1024 * 16}, function (error,stdout,stderr){ + if(error){ + console.log("Error occured:" + error); + if(stderr){ + //console.log("stderr:" + stderr); + res.send(500,{'error':error,'stderr':stderr}); + }else{ + res.send(500,{'error':error}); + } + }else{ + if(stderr){ + console.log("stderr:" + stderr); + } + if(stdout){ + //console.log("output:" + stdout); + res.send(200,{'stdout':stdout,'stderr':stderr}); + } + } + }); + }); + + app.get("/importGitLocalFlow", + function(req,res) { + var appDir = path.dirname(require.main.filename); + var gitLocalRepository = settings.gitLocalRepository; + //console.dir(req); + var filePath = req.query.filePath; + var fullFilePath = gitLocalRepository +"/" + filePath ; + //console.log("fullFilePath:" + fullFilePath); + var exec = require('child_process').exec; + var commandToExec = "cat " + fullFilePath ; + console.log("commandToExec:" + commandToExec); + var child = exec(commandToExec ,{maxBuffer: 1024 * 1024 * 16}, function (error,stdout,stderr){ + if(error){ + console.log("Error occured:" + error); + if(stderr){ + //console.log("stderr:" + stderr); + res.send(500,{'error':error,'stderr':stderr}); + }else{ + res.send(500,{'error':error}); + } + }else{ + if(stderr){ + console.log("stderr:" + stderr); + } + if(stdout){ + //console.log("output:" + stdout); + res.send(200,{'stdout':stdout,'stderr':stderr}); + } + } + }); + }); + + + app.get("/gitcheckout", function(req,res) { + var appDir = path.dirname(require.main.filename); + var gitLocalRepository = settings.gitLocalRepository; + //console.dir(req); + var branch = req.query.branch; + //console.log("fullFilePath:" + fullFilePath); + var exec = require('child_process').exec; + var commandToExec = appDir + "/git_scripts/gitcheckout " + gitLocalRepository + " " + branch ; + console.log("commandToExec:" + commandToExec); + var child = exec(commandToExec ,function (error,stdout,stderr){ + if(error){ + console.log("Error occured:" + error); + if(stderr){ + console.log("stderr:" + stderr); + res.json({"output":stderr}); + }else{ + res.json({"output":error}); + } + }else{ + if(stderr){ + console.log("stderr:" + stderr); + } + if(stdout){ + res.json({"output": stderr + " " + stdout}); + } + } + }); + }); + + app.get("/gitpull", function(req,res) { + var appDir = path.dirname(require.main.filename); + var gitLocalRepository = settings.gitLocalRepository; + //console.dir(req); + var branch = req.query.branch; + //console.log("fullFilePath:" + fullFilePath); + var exec = require('child_process').exec; + var commandToExec = appDir + "/git_scripts/gitpull " + gitLocalRepository ; + console.log("commandToExec:" + commandToExec); + var child = exec(commandToExec ,function (error,stdout,stderr){ + if(error){ + console.log("Error occured:" + error); + if(stderr){ + console.log("stderr:" + stderr); + res.json({"output":stderr}); + }else{ + res.json({"output":error}); + } + }else{ + if(stderr){ + console.log("stderr:" + stderr); + } + if(stdout){ + res.json({"output": stderr + " " + stdout}); + } + } + }); + }); + + app.get("/gitstatus", function(req,res) { + var appDir = path.dirname(require.main.filename); + var gitLocalRepository = settings.gitLocalRepository; + //console.dir(req); + var branch = req.query.branch; + //console.log("fullFilePath:" + fullFilePath); + var exec = require('child_process').exec; + var commandToExec = appDir + "/git_scripts/gitstatus " + gitLocalRepository ; + console.log("commandToExec:" + commandToExec); + var child = exec(commandToExec ,function (error,stdout,stderr){ + if(error){ + console.log("Error occured:" + error); + if(stderr){ + console.log("stderr:" + stderr); + res.json({"output":stderr}); + }else{ + res.json({"output":error}); + } + }else{ + if(stderr){ + console.log("stderr:" + stderr); + } + if(stdout){ + res.json({"output": stderr + " " + stdout}); + } + } + }); + }); + + app.post("/getSharedFlow", + express.json(), + function(req,res) { + var qs = require('querystring'); + var body = ''; + req.on('data', function (data) { + body += data; + }); + req.on('end', function () { + var post = qs.parse(body); + //console.log("body:" + body); + fs.readFile(post.filePath, 'utf8', function (err,data) { + if (err) { + return console.log(err); + } + res.json(data); + //console.log(data); + }); + //res.sendFile(body.filePath); + }); + }); + + app.post("/downloadYang", + express.json(), + function(req,res) { + var qs = require('querystring'); + var body = ''; + req.on('data', function (data) { + body += data; + }); + req.on('end', function () { + var post = qs.parse(body); + var fileName = post.fileName; + var appDir = path.dirname(require.main.filename); + var yangDir = appDir + "/yangFiles" ; + var fullPathToFile = yangDir + "/" + fileName; + res.setHeader('Content-disposition', 'attachment; filename=' + fileName); + res.setHeader('Content-type', 'application/yang'); + res.download(fullPathToFile); + }); + }); + + function writeToFile(fullPathToFileName,str){ + try{ + fs.writeFileSync(fullPathToFileName,str); + }catch(e){ + console.log("Error:" + e); + } + } + function getCurrentDate(){ + var d = new Date(); + var mm = d.getMonth() + 1; + var dd = d.getDate(); + var yyyy = d.getYear() + 1900; + var hr = d.getHours(); + var min = d.getMinutes(); + var sec = d.getSeconds(); + if(mm<10) mm = "0" + mm; + if(dd<10) dd = "0" + dd; + if(hr<10) hr = "0" + hr; + if(min<10) min = "0" + min; + if(sec<10) sec = "0" + sec; + var formatedValue = mm + "-" + dd + "-" + yyyy + "_" + hr + "" + min + "" + sec; + return formatedValue; + } + + + app.post("/downloadXml", + express.json({'limit':'16mb'}), + function(req,res) { + //console.log("Received request and processing:" + new Date()); + var qs = require('querystring'); + var body = ''; + //var msecs1= Date.now(); + req.on('data', function (data) { + body += data; + }); + req.on('end', function () { + var appDir = path.dirname(require.main.filename); + var xmlDir = appDir + "/" + settings.xmlPath; + //var msecs2= Date.now(); + //console.log("Time taken to get request body:" + (msecs2 - msecs1)); + var msecs3= Date.now(); + var post = qs.parse(body); + var msecs4= Date.now(); + //console.log("Time taken to parse body:" + (msecs4 - msecs3)); + var xml = post['flowXml']; + //var pd = require('pretty-data').pd; + //var formatted_xml = pd.xml(xml); + var moduleName = post['moduleName']; + var methodName = post['methodName']; + if(moduleName == "" || methodName == ""){ + res.send({"ERROR":"ServiceLogic Module Name and method name are required."}); + }else{ + //var formatted_date = getCurrentDate(); + //var fileNameForServer=moduleName + "_" +methodName+ "_" + formatted_date + ".xml"; + //var fileName=moduleName + "_method_" +methodName+ ".xml"; + var fileName=moduleName + "_" +methodName+ ".xml"; + var file = xmlDir + "/" + fileName; + + //var msecs2= Date.now(); + writeToFile(file,xml); + //var msecs3= Date.now(); + //console.log("Time taken to write File:" + (msecs3 - msecs2)); + res.setHeader('Content-disposition', 'attachment; filename=' + fileName); + res.setHeader('Content-type', 'text/xml'); + res.end(xml); + //console.log("Response sent:" + new Date()); + } + }); + }); + + app.post("/downloadJson", + express.json({'limit':'16mb'}), + function(req,res) { + var appDir = path.dirname(require.main.filename); + var sharedDir = appDir + "/" + settings.sharedDir; + var qs = require('querystring'); + var body = ''; + req.on('data', function (data) { + body += data; + }); + req.on('end', function () { + var post = qs.parse(body); + var jsonStr = post['flowJson']; + var moduleName = post['moduleName']; + var methodName = post['methodName']; + //console.log("jsonStr:" + jsonStr); + if(moduleName == "" || methodName == ""){ + res.send({"ERROR":"ServiceLogic Module Name and method name are required."}); + }else{ + var formatted_date = getCurrentDate(); + //console.log("moduleName:" + moduleName); + //console.log("methodName:" + methodName); + + //var fileName=moduleName + "_method_" +methodName + ".json"; + //var renameOldfileTo=moduleName + "_method_" +methodName+ "_" + formatted_date + ".json"; + var fileName=moduleName + "_" +methodName + ".json"; + var renameOldfileTo=moduleName + "_" +methodName+ "_" + formatted_date + ".json"; + var file = sharedDir + "/" + fileName; + //console.log("fileName:" + fileName); + var renameFilePath = sharedDir + "/backups/" + renameOldfileTo; + //console.log("localfile:" + localfile); + fs.rename(file,renameFilePath, function (err) { + if(err){ + console.log('Error :' + err); + } + //write the newer version + writeToFile(file,jsonStr); + res.setHeader('Content-disposition', 'attachment; filename=' + fileName); + res.setHeader('Content-type', 'application/json'); + //res.download(file); + res.end(jsonStr); + }); + } + }); + }); + + app.post("/flows", + express.json({'limit':'16mb'}), + function(req,res) { + //console.log("Processing Request"); + var flows = req.body; + redNodes.setFlows(flows).then(function() { + res.send(204); + }).otherwise(function(err) { + util.log("[red] Error saving flows : "+err); + res.send(500,err.message); + }); + }, + function(error,req,res,next) { + res.send(400,"Invalid Flow. Error " + error); + } + ); + + app.get("/nodes",function(req,res) { + if (req.get("accept") == "application/json") { + res.json(redNodes.getNodeList()); + } else { + res.send(redNodes.getNodeConfigs()); + } + }); + + app.post("/nodes", + express.json(), + function(req,res) { + if (!settings.available()) { + res.send(400,new Error("Settings unavailable").toString()); + return; + } + var node = req.body; + var promise; + if (node.file) { + promise = redNodes.addNode(node.file).then(reportAddedModules); + } else if (node.module) { + var module = redNodes.getNodeModuleInfo(node.module); + if (module) { + res.send(400,"Module already loaded"); + return; + } + promise = installModule(node.module); + } else { + res.send(400,"Invalid request"); + return; + } + promise.then(function(info) { + res.json(info); + }).otherwise(function(err) { + if (err.code === 404) { + res.send(404); + } else { + res.send(400,err.toString()); + } + }); + }, + function(err,req,res,next) { + console.log(err.toString()); + res.send(400,err); + } + ); + + app.delete("/nodes/:id", + function(req,res) { + if (!settings.available()) { + res.send(400,new Error("Settings unavailable").toString()); + return; + } + var id = req.params.id; + var removedNodes = []; + try { + var node = redNodes.getNodeInfo(id); + var promise = null; + if (!node) { + var module = redNodes.getNodeModuleInfo(id); + if (!module) { + res.send(404); + return; + } else { + promise = uninstallModule(id); + } + } else { + promise = when.resolve([redNodes.removeNode(id)]).then(reportRemovedModules); + } + + promise.then(function(removedNodes) { + res.json(removedNodes); + }).otherwise(function(err) { + console.log(err.stack); + res.send(400,err.toString()); + }); + } catch(err) { + res.send(400,err.toString()); + } + }, + function(err,req,res,next) { + res.send(400,err); + } + ); + + app.get("/nodes/:id", function(req,res) { + var id = req.params.id; + var result = null; + if (req.get("accept") == "application/json") { + result = redNodes.getNodeInfo(id); + } else { + result = redNodes.getNodeConfig(id); + } + if (result) { + res.send(result); + } else { + res.send(404); + } + }); + + app.put("/nodes/:id", + express.json(), + function(req,res) { + if (!settings.available()) { + res.send(400,new Error("Settings unavailable").toString()); + return; + } + var body = req.body; + if (!body.hasOwnProperty("enabled")) { + res.send(400,"Invalid request"); + return; + } + try { + var info; + var id = req.params.id; + var node = redNodes.getNodeInfo(id); + if (!node) { + res.send(404); + } else if (!node.err && node.enabled === body.enabled) { + res.json(node); + } else { + if (body.enabled) { + info = redNodes.enableNode(id); + } else { + info = redNodes.disableNode(id); + } + if (info.enabled == body.enabled && !info.err) { + comms.publish("node/"+(body.enabled?"enabled":"disabled"),info,false); + util.log("[red] "+(body.enabled?"Enabled":"Disabled")+" node types:"); + for (var i=0;i<info.types.length;i++) { + util.log("[red] - "+info.types[i]); + } + } else if (body.enabled && info.err) { + util.log("[red] Failed to enable node:"); + util.log("[red] - "+info.name+" : "+info.err); + } + res.json(info); + } + } catch(err) { + res.send(400,err.toString()); + } + } + ); + app.get("/getCodeCloudFlows",function(req,res) { + var userDir=settings.userDir; + var codeCloudDir=userDir + "/codecloud"; + var glob = require("glob") + glob(codeCloudDir + "/**/*.json", null, function (er, files) { + // files is an array of filenames. + // If the `nonull` option is set, and nothing + // was found, then files is ["**/*.js"] + // er is an error object or null. + //console.dir(files); + var filesList =[]; + for(var i=0;files!= null && i<files.length;i++){ + var f = files[i].replace( new RegExp(codeCloudDir + "/", "g" ), "" ); + filesList.push(f); + + } + res.json({"files" : filesList}); + }); + }); + + app.get("/getCurrentGitBranch",function(req,res) { + var appDir = path.dirname(require.main.filename); + var userDir=settings.userDir; + var settingsFile = appDir + "/" + userDir + "/customSettings.js"; + //console.log("settingsFile:" + settingsFile); + var jsonObj = require(settingsFile); + var gitLocalRepository=jsonObj.gitLocalRepository; + if(gitLocalRepository == undefined || gitLocalRepository == null || gitLocalRepository == ''){ + res.json({"output" : "GIT_LOCAL_REPOSITORY_NOT_SET"}); + return; + } + var exec = require('child_process').exec; + var commandToExec = appDir + "/git_scripts/gitcurbranch " + gitLocalRepository ; + console.log("commandToExec:" + commandToExec); + var child = exec(commandToExec ,function (error,stdout,stderr){ + if(error){ + console.log("Error occured:" + error); + if(stderr){ + console.log("stderr:" + stderr); + res.json({"output":stderr}); + }else{ + res.json({"output":error}); + } + }else{ + if(stderr){ + console.log("stderr:" + stderr); + } + if(stdout){ + res.json({"output":stdout}); + } + } + }); + + }); + + app.get("/getGitLocalFlows",function(req,res) { + var appDir = path.dirname(require.main.filename); + var userDir=settings.userDir; + var settingsFile = appDir + "/" + userDir + "/customSettings.js"; + //console.log("settingsFile:" + settingsFile); + var jsonObj = require(settingsFile); + var performGitPull = jsonObj.performGitPull; + if(performGitPull == undefined || performGitPull == null) { + performGitPull="N"; + } + var gitLocalRepository=jsonObj.gitLocalRepository; + if(gitLocalRepository == undefined || gitLocalRepository == null || gitLocalRepository == ''){ + res.json({"files" : ["GIT_LOCAL_REPOSITORY_NOT_SET"]}); + return; + + } + + if(performGitPull == "Y"){ + var exec = require('child_process').exec; + var commandToExec = appDir + "/git_scripts/gitpull " + gitLocalRepository ; + console.log("commandToExec:" + commandToExec); + var child = exec(commandToExec ,function (error,stdout,stderr){ + if(error){ + console.log("Error occured:" + error); + if(stderr){ + console.log("stderr:" + stderr); + res.json({"files":[]}); + }else{ + res.json({"files":[]}); + } + }else{ + if(stderr){ + console.log("stderr:" + stderr); + } + if(stdout){ + var glob = require("glob") + glob(gitLocalRepository + "/**/*.json", null, function (er, files) { + // files is an array of filenames. + // If the `nonull` option is set, and nothing + // was found, then files is ["**/*.js"] + // er is an error object or null. + //console.dir(files); + var filesList =[]; + for(var i=0;files!= null && i<files.length;i++){ + var f = files[i].replace( new RegExp(gitLocalRepository + "/", "g" ), "" ); + filesList.push(f); + + } + res.json({"files" : filesList}); + }); + } + } + }); + }else{//git pull not requested + var glob = require("glob") + glob(gitLocalRepository + "/**/*.json", null, function (er, files) { + // files is an array of filenames. + // If the `nonull` option is set, and nothing + // was found, then files is ["**/*.js"] + // er is an error object or null. + //console.dir(files); + var filesList =[]; + for(var i=0;files!= null && i<files.length;i++){ + var f = files[i].replace( new RegExp(gitLocalRepository + "/", "g" ), "" ); + filesList.push(f); + + } + res.json({"files" : filesList}); + }); + } + + }); + + app.get("/flowShareUsers",function(req,res) { + res.json(flowShareUsers); + }); + app.get("/getRelease",function(req,res) { + var userDir = settings.userDir; + //var release = userDir.replace(/releases/g,"release"); + res.json({"release" : userDir}); + }); + app.post("/getFiles/:id",function(req,res) { + var id = req.params.id; + //console.log("id:" + id); + var userDir=settings.userDir; + var flowDir= userDir + "/../" + id + "/flows/shared"; + //console.log("flowDir:" + flowDir); + fs.readdir(flowDir,function(err, files){ + if(err){ + res.json({"files": []}); + }else{ + var onlyFilesArr =[]; + if(files != null && files.length>0){ + files.sort(function(a,b){ + //console.log("file1:" + a); + //console.log("file2:" + b); + var fileStat1=fs.statSync(flowDir+ "/" + a); + var fileStat2=fs.statSync(flowDir+ "/" + b); + if(fileStat1.mtime > fileStat2.mtime){ + return 1; + }else if(fileStat1.mtime < fileStat2.mtime){ + return -1; + }else{ + return 0; + } + }); + for(var i=0;i<files.length;i++){ + var fileStat=fs.statSync(flowDir+ "/" + files[i]); + if(fileStat.isFile()){ + onlyFilesArr.push({"filePath":flowDir+ "/" + files[i],"name":files[i]}); + } + } + res.json(onlyFilesArr); + }else{ + res.json({"files": []}); + } + } + }); + }); + + app.post("/updateConfiguration", + express.json(), + function(req,res) { + var qs = require('querystring'); + //console.log("Received the request:"); + var body =""; + req.on('data', function (data) { + body += data; + }); + req.on('end',function(){ + var post = qs.parse(body); + var dbHost = post["dbHost"]; + var dbPort = post["dbPort"]; + var dbName = post["dbName"]; + var dbUser = post["dbUser"]; + var dbPassword = post["dbPassword"]; + var gitLocalRepository = post["gitLocalRepository"]; + var performGitPull = post["performGitPull"]; + var appDir = path.dirname(require.main.filename); + var userDir = appDir + "/" + settings.userDir; + console.log("userDir:" + userDir); + try{ + var settingsFile = userDir + "/customSettings.js"; + var jsonObj = require(settingsFile); + jsonObj.flowFile = jsonObj.flowFile.replace(appDir + "/",''); + jsonObj.dbHost = dbHost; + jsonObj.dbPort = dbPort; + jsonObj.dbName = dbName; + jsonObj.dbUser = dbUser; + jsonObj.dbPassword = dbPassword; + jsonObj.gitLocalRepository = gitLocalRepository; + jsonObj.performGitPull = performGitPull; + var updatedSettings = jsonObj; + + var settingsStr= "module.exports=" + JSON.stringify(updatedSettings,null,4); + //console.log("settingsStr:" + settingsStr); + fs.writeFileSync(settingsFile,settingsStr); + var svcLogicPropStr = "" ; + svcLogicPropStr += "org.openecomp.sdnc.sli.dbtype=jdbc" + "\n"; + svcLogicPropStr += "org.openecomp.sdnc.sli.jdbc.url=jdbc:mysql://" + dbHost + ":" + dbPort + "/" + dbName + "\n"; + svcLogicPropStr += "org.openecomp.sdnc.sli.jdbc.database=" + dbName + "\n"; + svcLogicPropStr += "org.openecomp.sdnc.sli.jdbc.user=" + dbUser + "\n"; + svcLogicPropStr += "org.openecomp.sdnc.sli.jdbc.password=" + dbPassword; + + //create svclogic.properties file in the conf dir + var svcPropFile = userDir + "/conf/svclogic.properties"; + fs.writeFileSync(svcPropFile,svcLogicPropStr); + + res.send({"status": "success"}); + }catch(e){ + console.log("Error:" + e); + res.send({"status": "error"}); + } + }); + } + ); + + app.post("/deleteYangFile", + express.json(), + function(req,res) { + var qs = require('querystring'); + //console.log("Received the request:"); + var body =""; + req.on('data', function (data) { + body += data; + }); + req.on('end',function(){ + var post = qs.parse(body); + //console.dir(body); + var fileName = post["fileName"]; + var appDir = path.dirname(require.main.filename); + var yangFilePath = appDir + "/yangFiles/" + fileName; + try{ + fs.unlinkSync(yangFilePath); + res.send({"status" :"SUCCESS"}); + }catch(err){ + console.log("error" + err); + res.send({"status" :"ERROR"}); + } + //console.log("prevPassword:" + settings.httpAuth.pass ); + }); + } + ); + + app.post("/updatePassword", + express.json(), + function(req,res) { + var qs = require('querystring'); + //console.log("Received the request:"); + var body =""; + req.on('data', function (data) { + body += data; + }); + req.on('end',function(){ + var post = qs.parse(body); + //console.dir(body); + var password = post["password"]; + //console.log("prevPassword:" + settings.httpAuth.pass ); + //console.log("New password:" + password); + var crypto = require("crypto"); + var cryptPasswd = crypto.createHash('md5').update(password,'utf8').digest('hex') + var appDir = path.dirname(require.main.filename); + var userDir = appDir + "/" + settings.userDir; + //console.log("userDir:" + userDir); + /*var newSettings = settings; + newSettings.httpAuth.pass = cryptPasswd; + var updatedSettings = JSON.stringify(settings,null,4); + var settingsStr = "module.exports=" + updatedSettings; + console.log(updatedSettings); + */ + try{ + var settingsFile = userDir + "/customSettings.js"; + //console.log("settingsFile:" + settingsFile); + //var buf = fs.readFileSync(settingsFile, "utf8"); + var jsonObj = require(settingsFile); + //console.log("jsonObj:" + JSON.stringify(jsonObj)); + jsonObj.httpAuth.pass = cryptPasswd; + jsonObj.httpAdminAuth.pass = cryptPasswd; + jsonObj.httpNodeAuth.pass = cryptPasswd; + jsonObj.flowFile = jsonObj.flowFile.replace(appDir + "/",''); + var updatedSettings = jsonObj; + /* + delete updatedSettings.httpRoot; + delete updatedSettings.disableEditor; + delete updatedSettings.httpAdminRoot; + delete updatedSettings.httpAdminAuth; + delete updatedSettings.httpNodeRoot; + delete updatedSettings.httpNodeAuth; + delete updatedSettings.uiHost; + delete updatedSettings.version; + */ + var settingsStr= "module.exports=" + JSON.stringify(updatedSettings,null,4); + //console.log("settingsStr:" + settingsStr); + fs.writeFileSync(settingsFile,settingsStr); + settings.httpAuth.pass = cryptPasswd; + res.send({"status": "success"}); + }catch(e){ + console.log("Error:" + e); + res.send({"status": "error"}); + } + }); + } + ); + + var appDir = path.dirname(require.main.filename); + var yangDir = appDir + "/yangFiles" ; + var diskStorage = multer.diskStorage({ + destination: function (req, file, callback) { + callback(null, yangDir); + }, + filename: function (req, file, callback) { + //callback(null, file.fieldname + '-' + Date.now()); + callback(null, file.originalname); + } + }); + var upload = multer({ storage : diskStorage}).single('yangFile'); + + app.post('/api/uploadyang',function(req,res){ + upload(req,res,function(err) { + if(err) { + console.log(err); + return res.end("Error uploading file." + err); + } + //console.dir(req); + var fileName = req.file.originalname; + var yangFileFullPath = appDir + "/yangFiles/" + fileName; + console.log("yangFileFullPath:" + yangFileFullPath); + var commandToExec =""; + if(fileName != null){ + var matchedArr = fileName.match(/.zip$/); + if(matchedArr != null && matchedArr.length >0){ + console.log("uploaded zip file" + fileName); + commandToExec = appDir + "/tools/generate_props_from_yangs_zip.sh " + yangFileFullPath ; + }else{ + commandToExec = appDir + "/tools/generate_props_from_yang.sh " + yangFileFullPath ; + console.log("uploaded file" + fileName); + } + } + var exec = require('child_process').exec; + console.log("commandToExec:" + commandToExec); + var child = exec(commandToExec ,function (error,stdout,stderr){ + if(error){ + console.log("Error occured:" + error); + var msg = "File " + fileName + " could not be processed successfully."; + if(stderr){ + console.log("stderr:" + stderr); + res.json({"sliValuesObj" : [],"message":msg}); + }else{ + res.json({"sliValuesObj" : [],"message":msg}); + } + }else{ + if(stderr){ + console.log("stderr:" + stderr); + } + if(stdout){ + console.log("stdout:" + stdout); + } + var msg = "File " + fileName + " processed successfully."; + var generatedJSDir=appDir + "/generatedJS"; + var sliValuesObj =[]; + //var glob = require("glob"); + //glob(generatedJSDir + "/**/*.js", null, function (er, files) { + /* + var sliValuesObj =[]; + for(var i=0;files!= null && i<files.length;i++){ + var f = files[i].replace( new RegExp(generatedJSDir + "/", "g" ), "" ); + console.log("loading file " + f); + try{ + sliValuesObj.push(require(files[i])); + //console.dir(sliValuesObj); + }catch(err){ + console.log("Error:Could not load file " + files[i]); + } + } + res.json({"sliValuesObj" : sliValuesObj,"message":msg}); + }); + */ + res.json({"sliValuesObj" : sliValuesObj,"message":msg}); + } + }); + }); + }); + app.get("/getYangFiles",function(req,res) { + var appDir = path.dirname(require.main.filename); + var yangFilesDir=appDir + "/yangFiles"; + var glob = require("glob") + glob(yangFilesDir + "/**/*.yang", null, function (er, files) { + var filesList =[]; + for(var i=0;files!= null && i<files.length;i++){ + var f = files[i].replace( new RegExp(yangFilesDir + "/", "g" ), "" ); + filesList.push(f); + + } + res.json({"files" : filesList}); + }); + }); + } +} + +function reportAddedModules(info) { + comms.publish("node/added",info,false); + if (info.length > 0) { + util.log("[red] Added node types:"); + for (var i=0;i<info.length;i++) { + for (var j=0;j<info[i].types.length;j++) { + util.log("[red] - "+ + (info[i].module?info[i].module+":":"")+ + info[i].types[j]+ + (info[i].err?" : "+info[i].err:"") + ); + } + } + } + return info; +} + +function reportRemovedModules(removedNodes) { + comms.publish("node/removed",removedNodes,false); + util.log("[red] Removed node types:"); + for (var j=0;j<removedNodes.length;j++) { + for (var i=0;i<removedNodes[j].types.length;i++) { + util.log("[red] - "+(removedNodes[i].module?removedNodes[i].module+":":"")+removedNodes[j].types[i]); + } + } + return removedNodes; +} + +function installModule(module) { + //TODO: ensure module is 'safe' + return when.promise(function(resolve,reject) { + if (/[\s;]/.test(module)) { + reject(new Error("Invalid module name")); + return; + } + util.log("[red] Installing module: "+module); + var child = exec('npm install --production '+module, function(err, stdin, stdout) { + if (err) { + var lookFor404 = new RegExp(" 404 .*"+module+"$","m"); + if (lookFor404.test(stdout)) { + util.log("[red] Installation of module "+module+" failed: module not found"); + var e = new Error(); + e.code = 404; + reject(e); + } else { + util.log("[red] Installation of module "+module+" failed:"); + util.log("------------------------------------------"); + console.log(err.toString()); + util.log("------------------------------------------"); + reject(new Error("Install failed")); + } + } else { + util.log("[red] Installed module: "+module); + resolve(redNodes.addModule(module).then(reportAddedModules)); + } + }); + }); +} + +function uninstallModule(module) { + var list = redNodes.removeModule(module); + return when.promise(function(resolve,reject) { + if (/[\s;]/.test(module)) { + reject(new Error("Invalid module name")); + return; + } + util.log("[red] Removing module: "+module); + var child = exec('npm remove '+module, function(err, stdin, stdout) { + if (err) { + util.log("[red] Removal of module "+module+" failed:"); + util.log("------------------------------------------"); + console.log(err.toString()); + util.log("------------------------------------------"); + reject(new Error("Removal failed")); + } else { + util.log("[red] Removed module: "+module); + reportRemovedModules(list); + resolve(list); + } + }); + }); +} + +function start() { + var defer = when.defer(); + + storage.init(settings).then(function() { + settings.load(storage).then(function() { + console.log("\nWelcome to Node-RED\n===================\n"); + if (settings.version) { + util.log("[red] Version: "+settings.version); + } + util.log("[red] Loading palette nodes"); + redNodes.init(settings,storage); + redNodes.load().then(function() { + var i; + var nodes = redNodes.getNodeList(); + var nodeErrors = nodes.filter(function(n) { return n.err!=null;}); + var nodeMissing = nodes.filter(function(n) { return n.module && n.enabled && !n.loaded && !n.err;}); + if (nodeErrors.length > 0) { + util.log("------------------------------------------"); + if (settings.verbose) { + for (i=0;i<nodeErrors.length;i+=1) { + util.log("["+nodeErrors[i].name+"] "+nodeErrors[i].err); + } + } else { + util.log("[red] Failed to register "+nodeErrors.length+" node type"+(nodeErrors.length==1?"":"s")); + util.log("[red] Run with -v for details"); + } + util.log("------------------------------------------"); + } + if (nodeMissing.length > 0) { + util.log("[red] Missing node modules:"); + var missingModules = {}; + for (i=0;i<nodeMissing.length;i++) { + var missing = nodeMissing[i]; + missingModules[missing.module] = (missingModules[missing.module]||[]).concat(missing.types); + } + var promises = []; + for (i in missingModules) { + if (missingModules.hasOwnProperty(i)) { + util.log("[red] - "+i+": "+missingModules[i].join(", ")); + if (settings.autoInstallModules) { + installModule(i).otherwise(function(err) { + // Error already reported. Need the otherwise handler + // to stop the error propagating any further + }); + } + } + } + if (!settings.autoInstallModules) { + util.log("[red] Removing modules from config"); + redNodes.cleanNodeList(); + } + } + defer.resolve(); + + redNodes.loadFlows(); + }).otherwise(function(err) { + console.log(err); + }); + comms.start(); + }); + }).otherwise(function(err) { + defer.reject(err); + }); + + return defer.promise; +} + +function stop() { + redNodes.stopFlows(); + comms.stop(); +} + +module.exports = { + init: createServer, + start: start, + stop: stop +} + +module.exports.__defineGetter__("app", function() { return app }); +module.exports.__defineGetter__("nodeApp", function() { return nodeApp }); +module.exports.__defineGetter__("server", function() { return server }); diff --git a/dgbuilder/red/settings.js b/dgbuilder/red/settings.js new file mode 100644 index 00000000..4994953f --- /dev/null +++ b/dgbuilder/red/settings.js @@ -0,0 +1,84 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var when = require("when"); + +var assert = require("assert"); + +var userSettings = null; +var globalSettings = null; +var storage = null; + +var persistentSettings = { + init: function(settings) { + userSettings = settings; + + for (var i in settings) { + if (settings.hasOwnProperty(i)) { + (function() { + var j = i; + persistentSettings.__defineGetter__(j,function() { return userSettings[j]; }); + persistentSettings.__defineSetter__(j,function() { throw new Error("Property '"+i+"' is read-only"); }); + })(); + } + } + globalSettings = null; + }, + load: function(_storage) { + storage = _storage; + return storage.getSettings().then(function(_settings) { + globalSettings = _settings; + }); + }, + get: function(prop) { + if (userSettings.hasOwnProperty(prop)) { + return userSettings[prop]; + } + if (globalSettings === null) { + throw new Error("Settings not available"); + } + return globalSettings[prop]; + }, + + set: function(prop,value) { + if (userSettings.hasOwnProperty(prop)) { + throw new Error("Property '"+prop+"' is read-only"); + } + if (globalSettings === null) { + throw new Error("Settings not available"); + } + var current = globalSettings[prop]; + globalSettings[prop] = value; + try { + assert.deepEqual(current,value); + return when.resolve(); + } catch(err) { + return storage.saveSettings(globalSettings); + } + }, + + available: function() { + return (globalSettings !== null); + }, + + reset: function() { + userSettings = null; + globalSettings = null; + storage = null; + } +} + +module.exports = persistentSettings; diff --git a/dgbuilder/red/sla.js b/dgbuilder/red/sla.js new file mode 100644 index 00000000..1729ecbb --- /dev/null +++ b/dgbuilder/red/sla.js @@ -0,0 +1,249 @@ +exports.listSLA = function(settings,req,res) { +try{ + var mysql = require('mysql'); + + //console.dir(settings); + + var connection = mysql.createConnection( + { + host : settings.dbHost, + port : settings.dbPort, + user : settings.dbUser, + password : settings.dbPassword, + database : settings.dbName + }); + + var rows=[]; + + var sqlQuery = "SELECT module,rpc,version,mode,active FROM SVC_LOGIC"; + //console.log("sqlQuery:" + sqlQuery); + connection.query(sqlQuery, function(err, rows) { + if(!err) { + if ( rows.length > 0 ) + { + res.send({ 'rows': rows,'dbHost':settings.dbHost } ); + }else{ + res.send({'rows': [],'dbHost':settings.dbHost}); + } + } else { + console.log("error:" + err); + res.send({error: "Connection to DB failed.",'dbHost':settings.dbHost}); + } + //console.dir(rows); + connection.end(); + }); //end query +}catch(error){ + console.log(error); + res.send({'error': "Connection to DB failed.",'dbHost':settings.dbHost}); +} +} + +exports.listCurrentDGs = function(settings,req,res) { + var _module = req.query.module; + var rpc = req.query.rpc; + console.log("_module:" + _module); + console.log("rpc:" + rpc); +try{ + var mysql = require('mysql'); + + //console.dir(settings); + + var connection = mysql.createConnection( + { + host : settings.dbHost, + port : settings.dbPort, + user : settings.dbUser, + password : settings.dbPassword, + database : settings.dbName + }); + + var rows=[]; + + var sqlQuery = "SELECT module,rpc,version,mode,active FROM SVC_LOGIC where module ='" + _module + "' and rpc ='" + rpc + "'"; + console.log("sqlQuery:" + sqlQuery); + connection.query(sqlQuery, function(err, rows) { + if(!err) { + if ( rows.length > 0 ) + { + res.send({ 'rows': rows,'dbHost':settings.dbHost } ); + }else{ + res.send({'rows': [],'dbHost':settings.dbHost}); + } + } else { + console.log("error:" + err); + res.send({error: "Connection to DB failed.",'dbHost':settings.dbHost}); + } + //console.dir(rows); + connection.end(); + }); //end query +}catch(error){ + console.log(error); + res.send({'error': "Connection to DB failed.",'dbHost':settings.dbHost}); +} +} + +exports.activateDG = function(settings,req,res){ + var _module = req.query.module; + var rpc = req.query.rpc; + var version = req.query.version; + var mode = req.query.mode; + var displayOnlyCurrent = req.query.displayOnlyCurrent; + +try{ + var mysql = require('mysql'); + + var connection = mysql.createConnection( + { + host : settings.dbHost, + port : settings.dbPort, + user : settings.dbUser, + password : settings.dbPassword, + database : settings.dbName + }); + + var rows=[]; + + var updateStmt = "UPDATE SVC_LOGIC SET active=\'Y\' WHERE module=\'" + + _module + "' AND rpc=\'" + + rpc + "' AND version=\'" + + version + "' AND mode=\'" + + mode + "'"; + + connection.query(updateStmt, function(err, result) { + var nextUpdateStmt = "UPDATE SVC_LOGIC SET active=\'N\' WHERE module=\'" + + _module + "' AND rpc=\'" + + rpc + "' AND version !=\'" + + version + "'"; + connection.query(nextUpdateStmt, function(err, result) { + var query = "SELECT module,rpc,version,mode,active FROM SVC_LOGIC"; + if(displayOnlyCurrent == 'true'){ + query = "SELECT module,rpc,version,mode,active FROM SVC_LOGIC WHERE module=\'" + + _module + "' and rpc=\'" + rpc + "'"; + } + connection.query(query, function(err, rows) { + if(!err) { + if ( rows.length > 0 ) + { + res.send({ 'rows': rows,'dbHost':settings.dbHost } ); + }else{ + res.send({'rows': [],'dbHost':settings.dbHost}); + } + } else { + res.send({'error': "Connection to DB failed.",'dbHost':settings.dbHost}); + } + connection.end(); + }); //end query + }); //end query + }); //end query +}catch(error){ + res.send({'error': "Connection to DB failed.",'dbHost':settings.dbHost}); +} +} + + +exports.deActivateDG = function(settings,req,res){ + + var _module = req.query.module; + var rpc = req.query.rpc; + var version = req.query.version; + var mode = req.query.mode; + var displayOnlyCurrent = req.query.displayOnlyCurrent; + +try{ + var mysql = require('mysql'); + + var connection = mysql.createConnection( + { + host : settings.dbHost, + port : settings.dbPort, + user : settings.dbUser, + password : settings.dbPassword, + database : settings.dbName + }); + + var rows=[]; + + var updateStmt = "UPDATE SVC_LOGIC SET active=\'N\' WHERE module=\'" + + _module + "' AND rpc=\'" + + rpc + "' AND version=\'" + + version + "' AND mode=\'" + + mode + "'"; + + connection.query(updateStmt, function(err, result) { + var query = "SELECT module,rpc,version,mode,active FROM SVC_LOGIC"; + if(displayOnlyCurrent == 'true'){ + query = "SELECT module,rpc,version,mode,active FROM SVC_LOGIC WHERE module=\'" + + _module + "' and rpc=\'" + rpc + "'"; + } + connection.query(query, function(err, rows) { + if(!err) { + if ( rows.length > 0 ) + { + res.send({ 'rows': rows,'dbHost':settings.dbHost } ); + }else{ + res.send({'rows': [],'dbHost':settings.dbHost}); + } + } else { + res.send({'error': "Connection to DB failed.",'dbHost':settings.dbHost}); + } + connection.end(); + }); //end query + }); //end query +}catch(error){ + res.send({'error': "Connection to DB failed.",'dbHost':settings.dbHost}); +} +} + +exports.deleteDG = function(settings,req,res){ + + var _module = req.query.module; + var rpc = req.query.rpc; + var version = req.query.version; + var mode = req.query.mode; + var displayOnlyCurrent = req.query.displayOnlyCurrent; + +try{ + var mysql = require('mysql'); + + var connection = mysql.createConnection( + { + host : settings.dbHost, + port : settings.dbPort, + user : settings.dbUser, + password : settings.dbPassword, + database : settings.dbName + }); + + var rows=[]; + + var deleteStmt = "DELETE FROM SVC_LOGIC WHERE module=\'" + + _module + "' AND rpc=\'" + + rpc + "' AND version=\'" + + version + "' AND mode=\'" + + mode + "'"; + console.log(deleteStmt); + + connection.query(deleteStmt, function(err, result) { + var query = "SELECT module,rpc,version,mode,active FROM SVC_LOGIC"; + if(displayOnlyCurrent == 'true'){ + query = "SELECT module,rpc,version,mode,active FROM SVC_LOGIC WHERE module=\'" + + _module + "' and rpc=\'" + rpc + "'"; + } + connection.query(query, function(err, rows) { + if(!err) { + if ( rows.length > 0 ) + { + res.send({ 'rows': rows,'dbHost':settings.dbHost } ); + }else{ + res.send({'rows': [],'dbHost':settings.dbHost}); + } + } else { + res.send({'error': "Connection to DB failed.",'dbHost':settings.dbHost}); + } + connection.end(); + }); //end query + }); //end query +}catch(error){ + res.send({'error': "Connection to DB failed.",'dbHost':settings.dbHost}); +} +} diff --git a/dgbuilder/red/storage/index.js b/dgbuilder/red/storage/index.js new file mode 100644 index 00000000..ba939627 --- /dev/null +++ b/dgbuilder/red/storage/index.js @@ -0,0 +1,107 @@ +/** + * Copyright 2013, 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var when = require('when'); + +var storageModule; +var settingsAvailable; + +function moduleSelector(aSettings) { + var toReturn; + if (aSettings.storageModule) { + if (typeof aSettings.storageModule === "string") { + // TODO: allow storage modules to be specified by absolute path + toReturn = require("./"+aSettings.storageModule); + } else { + toReturn = aSettings.storageModule; + } + } else { + toReturn = require("./localfilesystem"); + } + return toReturn; +} + +function is_malicious(path) { + return path.indexOf('../') != -1 || path.indexOf('..\\') != -1; +} + +var storageModuleInterface = { + init: function(settings) { + try { + storageModule = moduleSelector(settings); + settingsAvailable = storageModule.hasOwnProperty("getSettings") && storageModule.hasOwnProperty("saveSettings"); + } catch (e) { + return when.reject(e); + } + return storageModule.init(settings); + }, + getFlows: function() { + return storageModule.getFlows(); + }, + saveFlows: function(flows) { + return storageModule.saveFlows(flows); + }, + getCredentials: function() { + return storageModule.getCredentials(); + }, + saveCredentials: function(credentials) { + return storageModule.saveCredentials(credentials); + }, + getSettings: function() { + if (settingsAvailable) { + return storageModule.getSettings(); + } else { + return when.resolve(null); + } + }, + saveSettings: function(settings) { + if (settingsAvailable) { + return storageModule.saveSettings(settings); + } else { + return when.resolve(); + } + }, + /* Library Functions */ + getAllFlows: function() { + return storageModule.getAllFlows(); + }, + getFlow: function(fn) { + if (is_malicious(fn)) { + return when.reject(new Error('forbidden flow name')); + } + return storageModule.getFlow(fn); + }, + saveFlow: function(fn, data) { + if (is_malicious(fn)) { + return when.reject(new Error('forbidden flow name')); + } + return storageModule.saveFlow(fn, data); + }, + getLibraryEntry: function(type, path) { + if (is_malicious(path)) { + return when.reject(new Error('forbidden flow name')); + } + return storageModule.getLibraryEntry(type, path); + }, + saveLibraryEntry: function(type, path, meta, body) { + if (is_malicious(path)) { + return when.reject(new Error('forbidden flow name')); + } + return storageModule.saveLibraryEntry(type, path, meta, body); + } +} + +module.exports = storageModuleInterface; diff --git a/dgbuilder/red/storage/localfilesystem.js b/dgbuilder/red/storage/localfilesystem.js new file mode 100644 index 00000000..48255332 --- /dev/null +++ b/dgbuilder/red/storage/localfilesystem.js @@ -0,0 +1,309 @@ +/** + * Copyright 2013, 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var fs = require('fs'); +var when = require('when'); +var nodeFn = require('when/node/function'); +var keys = require('when/keys'); +var util = require('util'); +var fspath = require("path"); +var mkdirp = require("mkdirp"); + +var promiseDir = nodeFn.lift(mkdirp); + +var settings; +var flowsFile; +var flowsFullPath; +var flowsPrev; +var credentialsFile; +var oldCredentialsFile; +var userDir; +var libDir; +var libFlowsDir; +var globalSettingsFile; + +function listFiles(dir) { + var dirs = {}; + var files = []; + var dirCount = 0; + return nodeFn.call(fs.readdir, dir).then(function (contents) { + contents.sort().forEach(function(fn) { + var stats = fs.lstatSync(dir+"/"+fn); + if (stats.isDirectory()) { + dirCount += 1; + dirs[fn] = listFiles(dir+"/"+fn) + } else { + files.push(fn.split(".")[0]); + } + }) + var result = {}; + if (dirCount > 0) { result.d = keys.all(dirs); } + if (files.length > 0) { result.f = when.resolve(files); } + return keys.all(result); + }) +} + +function getFileMeta(root,path) { + var fn = fspath.join(root,path); + var fd = fs.openSync(fn,"r"); + var size = fs.fstatSync(fd).size; + var meta = {}; + var read = 0; + var length = 10; + var remaining = ""; + var buffer = Buffer(length); + while(read < size) { + read+=fs.readSync(fd,buffer,0,length); + var data = remaining+buffer.toString(); + var parts = data.split("\n"); + remaining = parts.splice(-1); + for (var i=0;i<parts.length;i+=1) { + var match = /^\/\/ (\w+): (.*)/.exec(parts[i]); + if (match) { + meta[match[1]] = match[2]; + } else { + read = size; + break; + } + } + } + fs.closeSync(fd); + return meta; +} + +function getFileBody(root,path) { + var body = ""; + var fn = fspath.join(root,path); + var fd = fs.openSync(fn,"r"); + var size = fs.fstatSync(fd).size; + var scanning = true; + var read = 0; + var length = 50; + var remaining = ""; + var buffer = Buffer(length); + while(read < size) { + var thisRead = fs.readSync(fd,buffer,0,length); + read += thisRead; + if (scanning) { + var data = remaining+buffer.slice(0,thisRead).toString(); + var parts = data.split("\n"); + remaining = parts.splice(-1)[0]; + for (var i=0;i<parts.length;i+=1) { + if (! /^\/\/ \w+: /.test(parts[i])) { + scanning = false; + body += parts[i]+"\n"; + } + } + if (! /^\/\/ \w+: /.test(remaining)) { + scanning = false; + } + if (!scanning) { + body += remaining; + } + } else { + body += buffer.slice(0,thisRead).toString(); + } + } + fs.closeSync(fd); + return body; +} + +var localfilesystem = { + init: function(_settings) { + settings = _settings; + userDir = settings.userDir || process.env.NODE_RED_HOME; + + if (settings.flowFile) { + flowsFile = settings.flowFile; + flowsFullPath = flowsFile; + } else { + flowsFile = 'flows_'+require('os').hostname()+'.json'; + flowsFullPath = fspath.join(userDir,flowsFile); + } + var fsext = fspath.extname(flowsFile); + credentialsFile = fspath.join(userDir,fspath.basename(flowsFile,fsext)+"_cred"+fsext); + oldCredentialsFile = fspath.join(userDir,"credentials.json"); + flowsPrev = fspath.join(userDir,"flows.backup"); + + libDir = fspath.join(userDir,"lib"); + libFlowsDir = fspath.join(libDir,"flows"); + + + globalSettingsFile = fspath.join(userDir,".config.json"); + + return promiseDir(libFlowsDir); + }, + + getFlows: function() { + var defer = when.defer(); + fs.exists(flowsFullPath, function(exists) { + if (exists) { + util.log("[red] Loading flows : "+flowsFile); + defer.resolve(nodeFn.call(fs.readFile,flowsFullPath,'utf8').then(function(data) { + return JSON.parse(data); + })); + } else { + util.log("[red] Flows file not found : "+flowsFile ); + defer.resolve([]); + } + }); + return defer.promise; + }, + + saveFlows: function(flows) { + if (fs.existsSync(flowsFullPath)) { + fs.renameSync(flowsFullPath,flowsPrev); + } + + var flowData; + + if (settings.flowFilePretty) { + flowData = JSON.stringify(flows,null,4); + } else { + flowData = JSON.stringify(flows); + } + console.log("Writing to file:" + flowsFullPath); + return nodeFn.call(fs.writeFile, flowsFullPath, flowData); + }, + + getCredentials: function() { + var defer = when.defer(); + fs.exists(credentialsFile, function(exists) { + if (exists) { + defer.resolve(nodeFn.call(fs.readFile, credentialsFile, 'utf8').then(function(data) { + return JSON.parse(data) + })); + } else { + fs.exists(oldCredentialsFile, function(exists) { + if (exists) { + defer.resolve(nodeFn.call(fs.readFile, oldCredentialsFile, 'utf8').then(function(data) { + return JSON.parse(data) + })); + } else { + defer.resolve({}); + } + }); + } + }); + return defer.promise; + }, + + saveCredentials: function(credentials) { + var credentialData; + if (settings.flowFilePretty) { + credentialData = JSON.stringify(credentials,null,4); + } else { + credentialData = JSON.stringify(credentials); + } + + return nodeFn.call(fs.writeFile, credentialsFile, credentialData) + }, + + getSettings: function() { + if (fs.existsSync(globalSettingsFile)) { + return nodeFn.call(fs.readFile,globalSettingsFile,'utf8').then(function(data) { + if (data) { + try { + return JSON.parse(data); + } catch(err) { + util.log("[red] Corrupted config detected - resetting"); + return {}; + } + } else { + return {}; + } + }); + } + return when.resolve({}); + }, + saveSettings: function(settings) { + return nodeFn.call(fs.writeFile,globalSettingsFile,JSON.stringify(settings,null,1),'utf8'); + }, + + + getAllFlows: function() { + return listFiles(libFlowsDir); + }, + + getFlow: function(fn) { + var defer = when.defer(); + var file = fspath.join(libFlowsDir,fn+".json"); + fs.exists(file, function(exists) { + if (exists) { + defer.resolve(nodeFn.call(fs.readFile,file,'utf8')); + } else { + defer.reject(); + } + }); + return defer.promise; + }, + + saveFlow: function(fn,data) { + var file = fspath.join(libFlowsDir,fn+".json"); + return promiseDir(fspath.dirname(file)).then(function () { + return nodeFn.call(fs.writeFile, file, data); + }); + }, + + getLibraryEntry: function(type,path) { + var root = fspath.join(libDir,type); + var rootPath = fspath.join(libDir,type,path); + return promiseDir(root).then(function () { + return nodeFn.call(fs.lstat, rootPath).then(function(stats) { + if (stats.isFile()) { + return getFileBody(root,path); + } + if (path.substr(-1) == '/') { + path = path.substr(0,path.length-1); + } + return nodeFn.call(fs.readdir, rootPath).then(function(fns) { + var dirs = []; + var files = []; + fns.sort().filter(function(fn) { + var fullPath = fspath.join(path,fn); + var absoluteFullPath = fspath.join(root,fullPath); + if (fn[0] != ".") { + var stats = fs.lstatSync(absoluteFullPath); + if (stats.isDirectory()) { + dirs.push(fn); + } else { + var meta = getFileMeta(root,fullPath); + meta.fn = fn; + files.push(meta); + } + } + }); + return dirs.concat(files); + }); + }); + }); + }, + + saveLibraryEntry: function(type,path,meta,body) { + var fn = fspath.join(libDir, type, path); + var headers = ""; + for (var i in meta) { + if (meta.hasOwnProperty(i)) { + headers += "// "+i+": "+meta[i]+"\n"; + } + } + return promiseDir(fspath.dirname(fn)).then(function () { + nodeFn.call(fs.writeFile, fn, headers+body); + }); + } +}; + +module.exports = localfilesystem; diff --git a/dgbuilder/red/ui.js b/dgbuilder/red/ui.js new file mode 100644 index 00000000..16580bf1 --- /dev/null +++ b/dgbuilder/red/ui.js @@ -0,0 +1,77 @@ +/** + * Copyright 2013, 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +var express = require('express'); +var fs = require("fs"); +var events = require("./events"); +var path = require("path"); + +var icon_paths = [path.resolve(__dirname + '/../public/icons')]; + +var settings; // settings has to be global, otherwise variable not in scope for express + +events.on("node-icon-dir",function(dir) { + icon_paths.push(path.resolve(dir)); +}); + + +function setupUI(_settings,app) { + + settings = _settings; + + // Need to ensure the url ends with a '/' so the static serving works + // with relative paths + app.get("/",function(req,res) { + if (req.originalUrl.slice(-1) != "/") { + res.redirect(req.originalUrl+"/"); + } else { + req.next(); + } + }); + + var iconCache = {}; + //TODO: create a default icon + var defaultIcon = path.resolve(__dirname + '/../public/icons/arrow-in.png'); + + app.get("/icons/:icon",function(req,res) { + if (iconCache[req.params.icon]) { + res.sendfile(iconCache[req.params.icon]); // if not found, express prints this to the console and serves 404 + } else { + for (var p=0;p<icon_paths.length;p++) { + var iconPath = path.join(icon_paths[p],req.params.icon); + if (fs.existsSync(iconPath)) { + res.sendfile(iconPath); + iconCache[req.params.icon] = iconPath; + return; + } + } + res.sendfile(defaultIcon); + } + }); + + app.get("/settings", function(req,res) { + var safeSettings = { + httpNodeRoot: settings.httpNodeRoot, + version: settings.version + }; + res.json(safeSettings); + }); + + app.use("/",express.static(__dirname + '/../public')); + + return app; +} + +module.exports = setupUI; diff --git a/dgbuilder/red/util.js b/dgbuilder/red/util.js new file mode 100644 index 00000000..7ca72b0e --- /dev/null +++ b/dgbuilder/red/util.js @@ -0,0 +1,43 @@ +/** + * Copyright 2014 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. + **/ + +function ensureString(o) { + if (Buffer.isBuffer(o)) { + return o.toString(); + } else if (typeof o === "object") { + return JSON.stringify(o); + } else if (typeof o === "string") { + return o; + } + return ""+o; +} + +function ensureBuffer(o) { + if (Buffer.isBuffer(o)) { + return o; + } else if (typeof o === "object") { + o = JSON.stringify(o); + } else if (typeof o !== "string") { + o = ""+o; + } + return new Buffer(o); +} + +module.exports = { + ensureString: ensureString, + ensureBuffer: ensureBuffer, +}; + |