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

Reorganise how adminApp is setup

This commit is contained in:
Nick O'Leary 2014-11-04 11:34:49 +00:00
parent 67449eb65a
commit 72f9471f2b
8 changed files with 470 additions and 388 deletions

37
red/api/flows.js Normal file
View File

@ -0,0 +1,37 @@
/**
* 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 express = require('express');
var fs = require("fs");
var events = require("../events");
var path = require("path");
var redNodes = require("../nodes");
var settings = require("../settings");
module.exports = {
get: function(req,res) {
res.json(redNodes.getFlows());
},
post: function(req,res) {
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);
});
}
}

68
red/api/index.js Normal file
View File

@ -0,0 +1,68 @@
/**
* 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 express = require("express");
var util = require('util');
var ui = require("./ui");
var nodes = require("./nodes");
var flows = require("./flows");
var library = require("./library");
var settings = require("../settings");
var errorHandler = function(err,req,res,next) {
//TODO: standardize json response
res.send(400,err.toString());
}
function init(adminApp) {
library.init(adminApp);
adminApp.use(express.json());
// Editor
if (!settings.disableEditor) {
adminApp.get("/",ui.ensureSlash);
adminApp.get("/icons/:icon",ui.icon);
adminApp.get("/settings",ui.settings);
adminApp.use("/",ui.editor);
}
// Flows
adminApp.get("/flows",flows.get);
adminApp.post("/flows",flows.post);
// Nodes
adminApp.get("/nodes",nodes.getAll);
adminApp.post("/nodes",nodes.post);
adminApp.get("/nodes/:id",nodes.get);
adminApp.put("/nodes/:id",nodes.put);
adminApp.delete("/nodes/:id",nodes.delete);
// Library
adminApp.post(new RegExp("/library/flows\/(.*)"),library.post);
adminApp.get("/library/flows",library.getAll);
adminApp.get(new RegExp("/library/flows\/(.*)"),library.get);
adminApp.use(errorHandler);
}
module.exports = {
init: init
}

111
red/api/library.js Normal file
View File

@ -0,0 +1,111 @@
/**
* 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 util = require("util");
var redApp = null;
var storage = require("../storage");
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: function(app) {
redApp = app;
},
register: createLibrary,
getAll: function(req,res) {
storage.getAllFlows().then(function(flows) {
res.json(flows);
});
},
get: 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);
});
},
post: function(req,res) {
//TODO: do content-type properly
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);
});
});
}
}

156
red/api/nodes.js Normal file
View File

@ -0,0 +1,156 @@
/**
* 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 express = require('express');
var fs = require("fs");
var path = require("path");
var when = require('when');
var util = require('util');
var events = require("../events");
var redNodes = require("../nodes");
var comms = require("../comms");
var server = require("../server");
var settings = require("../settings");
module.exports = {
getAll: function(req,res) {
if (req.get("accept") == "application/json") {
res.json(redNodes.getNodeList());
} else {
res.send(redNodes.getNodeConfigs());
}
},
post: 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(server.reportAddedModules);
} else if (node.module) {
var module = redNodes.getNodeModuleInfo(node.module);
if (module) {
res.send(400,"Module already loaded");
return;
}
promise = server.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());
}
});
},
delete: 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 = server.uninstallModule(id);
}
} else {
promise = when.resolve([redNodes.removeNode(id)]).then(server.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());
}
},
get: 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);
}
},
put: 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());
}
}
}

View File

@ -15,37 +15,29 @@
**/ **/
var express = require('express'); var express = require('express');
var fs = require("fs"); var fs = require("fs");
var events = require("./events");
var path = require("path"); var path = require("path");
var icon_paths = [path.resolve(__dirname + '/../public/icons')]; var events = require("../events");
var settings = require("../settings");
var settings; // settings has to be global, otherwise variable not in scope for express var icon_paths = [path.resolve(__dirname + '/../../public/icons')];
var iconCache = {};
//TODO: create a default icon
var defaultIcon = path.resolve(__dirname + '/../../public/icons/arrow-in.png');
events.on("node-icon-dir",function(dir) { events.on("node-icon-dir",function(dir) {
icon_paths.push(path.resolve(dir)); icon_paths.push(path.resolve(dir));
}); });
module.exports = {
function setupUI(_settings,app) { ensureSlash: function(req,res,next) {
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) != "/") { if (req.originalUrl.slice(-1) != "/") {
res.redirect(req.originalUrl+"/"); res.redirect(req.originalUrl+"/");
} else { } else {
req.next(); next();
} }
}); },
icon: function(req,res) {
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]) { if (iconCache[req.params.icon]) {
res.sendfile(iconCache[req.params.icon]); // if not found, express prints this to the console and serves 404 res.sendfile(iconCache[req.params.icon]); // if not found, express prints this to the console and serves 404
} else { } else {
@ -59,19 +51,13 @@ function setupUI(_settings,app) {
} }
res.sendfile(defaultIcon); res.sendfile(defaultIcon);
} }
}); },
settings: function(req,res) {
app.get("/settings", function(req,res) {
var safeSettings = { var safeSettings = {
httpNodeRoot: settings.httpNodeRoot, httpNodeRoot: settings.httpNodeRoot,
version: settings.version version: settings.version
}; };
res.json(safeSettings); res.json(safeSettings);
}); },
editor: express.static(__dirname + '/../../public')
app.use("/",express.static(__dirname + '/../public')); };
return app;
}
module.exports = setupUI;

View File

@ -1,117 +0,0 @@
/**
* 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;

View File

@ -16,7 +16,7 @@
var server = require("./server"); var server = require("./server");
var nodes = require("./nodes"); var nodes = require("./nodes");
var library = require("./library"); var library = require("./api/library");
var comms = require("./comms"); var comms = require("./comms");
var log = require("./log"); var log = require("./log");
var util = require("./util"); var util = require("./util");
@ -36,14 +36,13 @@ var RED = {
userSettings.version = this.version(); userSettings.version = this.version();
settings.init(userSettings); settings.init(userSettings);
server.init(httpServer,settings); server.init(httpServer,settings);
library.init();
return server.app; return server.app;
}, },
start: server.start, start: server.start,
stop: server.stop, stop: server.stop,
nodes: nodes, nodes: nodes,
library: library, library: { register: library.register },
credentials: credentials, credentials: credentials,
events: events, events: events,
log: log, log: log,

View File

@ -1,5 +1,5 @@
/** /**
* Copyright 2013 IBM Corp. * Copyright 2013, 2014 IBM Corp.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -19,7 +19,6 @@ var util = require('util');
var when = require('when'); var when = require('when');
var exec = require('child_process').exec; var exec = require('child_process').exec;
var createUI = require("./ui");
var redNodes = require("./nodes"); var redNodes = require("./nodes");
var comms = require("./comms"); var comms = require("./comms");
var storage = require("./storage"); var storage = require("./storage");
@ -38,177 +37,80 @@ function createServer(_server,_settings) {
nodeApp = express(); nodeApp = express();
app = express(); app = express();
if (settings.httpAdminRoot !== false) { if (settings.httpAdminRoot !== false) {
require("./api").init(app);
if (!settings.disableEditor) {
createUI(settings,app);
}
app.get("/flows",function(req,res) {
res.json(redNodes.getFlows());
});
app.post("/flows",
express.json(),
function(req,res) {
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");
}
);
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());
}
}
);
} }
} }
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 reportAddedModules(info) { function reportAddedModules(info) {
comms.publish("node/added",info,false); comms.publish("node/added",info,false);
if (info.length > 0) { if (info.length > 0) {
@ -292,72 +194,7 @@ function uninstallModule(module) {
}); });
} }
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() { function stop() {
redNodes.stopFlows(); redNodes.stopFlows();
@ -367,7 +204,12 @@ function stop() {
module.exports = { module.exports = {
init: createServer, init: createServer,
start: start, start: start,
stop: stop stop: stop,
reportAddedModules: reportAddedModules,
reportRemovedModules: reportRemovedModules,
installModule: installModule,
uninstallModule: uninstallModule
} }
module.exports.__defineGetter__("app", function() { return app }); module.exports.__defineGetter__("app", function() { return app });