diff --git a/nodes/core/core/20-inject.js b/nodes/core/core/20-inject.js index ee6100b71..952528f3e 100644 --- a/nodes/core/core/20-inject.js +++ b/nodes/core/core/20-inject.js @@ -83,7 +83,7 @@ InjectNode.prototype.close = function() { } } -RED.app.post("/inject/:id", function(req,res) { +RED.httpAdmin.post("/inject/:id", function(req,res) { var node = RED.nodes.getNode(req.params.id); if (node != null) { try { diff --git a/nodes/core/core/58-debug.js b/nodes/core/core/58-debug.js index 27d3bbd6f..1b67cc8b0 100644 --- a/nodes/core/core/58-debug.js +++ b/nodes/core/core/58-debug.js @@ -80,7 +80,7 @@ DebugNode.send = function(msg) { DebugNode.activeConnections = []; -var path = RED.settings.httpRoot || "/"; +var path = RED.settings.httpAdminRoot || "/"; path = path + (path.slice(-1) == "/" ? "":"/") + "debug/ws"; DebugNode.wsServer = new ws.Server({server:RED.server,path:path}); @@ -104,7 +104,7 @@ DebugNode.logHandler.on("log",function(msg) { }); RED.nodes.addLogHandler(DebugNode.logHandler); -RED.app.post("/debug/:id/:state", function(req,res) { +RED.httpAdmin.post("/debug/:id/:state", function(req,res) { var node = RED.nodes.getNode(req.params.id); var state = req.params.state; if (node != null) { diff --git a/nodes/core/io/10-mqtt.js b/nodes/core/io/10-mqtt.js index f729d286b..410ca2a0b 100644 --- a/nodes/core/io/10-mqtt.js +++ b/nodes/core/io/10-mqtt.js @@ -33,7 +33,7 @@ RED.nodes.registerType("mqtt-broker",MQTTBrokerNode); var querystring = require('querystring'); -RED.app.get('/mqtt-broker/:id',function(req,res) { +RED.httpAdmin.get('/mqtt-broker/:id',function(req,res) { var credentials = RED.nodes.getCredentials(req.params.id); if (credentials) { res.send(JSON.stringify({user:credentials.user,hasPassword:(credentials.password&&credentials.password!="")})); @@ -42,12 +42,12 @@ RED.app.get('/mqtt-broker/:id',function(req,res) { } }); -RED.app.delete('/mqtt-broker/:id',function(req,res) { +RED.httpAdmin.delete('/mqtt-broker/:id',function(req,res) { RED.nodes.deleteCredentials(req.params.id); res.send(200); }); -RED.app.post('/mqtt-broker/:id',function(req,res) { +RED.httpAdmin.post('/mqtt-broker/:id',function(req,res) { var body = ""; req.on('data', function(chunk) { body+=chunk; diff --git a/nodes/core/io/21-httpin.html b/nodes/core/io/21-httpin.html index 2cc84a570..9be7d5a60 100644 --- a/nodes/core/io/21-httpin.html +++ b/nodes/core/io/21-httpin.html @@ -80,7 +80,7 @@ if (this.name) { return this.name; } else if (this.url) { - var root = document.location.pathname.slice(0,-1); + var root = RED.settings.httpNodeRoot.slice(0,-1); root += this.url; return "["+this.method+"] "+root; } else { diff --git a/nodes/core/io/21-httpin.js b/nodes/core/io/21-httpin.js index 32073c5bb..72d4d6e14 100644 --- a/nodes/core/io/21-httpin.js +++ b/nodes/core/io/21-httpin.js @@ -58,17 +58,17 @@ function HTTPIn(n) { else node.send({req:req,res:res}); } if (this.method == "get") { - RED.app.get(this.url,this.callback,this.errorHandler); + RED.httpNode.get(this.url,this.callback,this.errorHandler); } else if (this.method == "post") { - RED.app.post(this.url,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler); + RED.httpNode.post(this.url,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler); } else if (this.method == "put") { - RED.app.put(this.url,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler); + RED.httpNode.put(this.url,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler); } else if (this.method == "delete") { - RED.app.delete(this.url,this.callback,errorHandler); + RED.httpNode.delete(this.url,this.callback,errorHandler); } this.on("close",function() { - var routes = RED.app.routes[this.method]; + var routes = RED.httpNode.routes[this.method]; for (var i in routes) { if (routes[i].path == this.url) { routes.splice(i,1); diff --git a/nodes/core/io/22-websocket.html b/nodes/core/io/22-websocket.html index d6d15d20f..4052579cd 100644 --- a/nodes/core/io/22-websocket.html +++ b/nodes/core/io/22-websocket.html @@ -135,12 +135,12 @@ inputs:0, outputs:0, label: function() { - var root = document.location.pathname.slice(0,-1); + var root = RED.settings.httpNodeRoot.slice(0,-1); root += this.path; return root; }, oneditprepare: function() { - var root = document.location.pathname.slice(0,-1); + var root = RED.settings.httpNodeRoot.slice(0,-1); if (root == "") { $("#node-config-ws-tip").hide(); } else { diff --git a/nodes/core/io/22-websocket.js b/nodes/core/io/22-websocket.js index da2a3fe57..4638dd1ba 100644 --- a/nodes/core/io/22-websocket.js +++ b/nodes/core/io/22-websocket.js @@ -32,7 +32,7 @@ function WebSocketListenerNode(n) { node._inputNodes = []; // collection of nodes that want to receive events - var path = RED.settings.httpRoot || "/"; + var path = RED.settings.httpNodeRoot || "/"; path = path + (path.slice(-1) == "/" ? "":"/") + (node.path.charAt(0) == "/" ? node.path.substring(1) : node.path); // Workaround https://github.com/einaros/ws/pull/253 diff --git a/nodes/core/io/25-serial.js b/nodes/core/io/25-serial.js index de016fb05..3e1fa4878 100644 --- a/nodes/core/io/25-serial.js +++ b/nodes/core/io/25-serial.js @@ -185,7 +185,7 @@ var serialPool = function() { } }(); -RED.app.get("/serialports",function(req,res) { +RED.httpAdmin.get("/serialports",function(req,res) { serialp.list(function (err, ports) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.write(JSON.stringify(ports)); diff --git a/nodes/core/social/27-twitter.js b/nodes/core/social/27-twitter.js index 68852bada..fc377205c 100644 --- a/nodes/core/social/27-twitter.js +++ b/nodes/core/social/27-twitter.js @@ -263,7 +263,7 @@ var oa = new OAuth( var credentials = {}; -RED.app.get('/twitter/:id', function(req,res) { +RED.httpAdmin.get('/twitter/:id', function(req,res) { var credentials = RED.nodes.getCredentials(req.params.id); if (credentials) { res.send(JSON.stringify({sn:credentials.screen_name})); @@ -272,12 +272,12 @@ RED.app.get('/twitter/:id', function(req,res) { } }); -RED.app.delete('/twitter/:id', function(req,res) { +RED.httpAdmin.delete('/twitter/:id', function(req,res) { RED.nodes.deleteCredentials(req.params.id); res.send(200); }); -RED.app.get('/twitter/:id/auth', function(req, res){ +RED.httpAdmin.get('/twitter/:id/auth', function(req, res){ var credentials = {}; oa.getOAuthRequestToken({ oauth_callback: req.query.callback @@ -297,7 +297,7 @@ RED.app.get('/twitter/:id/auth', function(req, res){ }); }); -RED.app.get('/twitter/:id/auth/callback', function(req, res, next){ +RED.httpAdmin.get('/twitter/:id/auth/callback', function(req, res, next){ var credentials = RED.nodes.getCredentials(req.params.id); credentials.oauth_verifier = req.query.oauth_verifier; diff --git a/public/red/main.js b/public/red/main.js index 0ba4cbd47..816bd7168 100644 --- a/public/red/main.js +++ b/public/red/main.js @@ -122,6 +122,12 @@ var RED = function() { ] }); + function loadSettings() { + $.get('settings', function(data) { + RED.settings = data; + loadNodes(); + }); + } function loadNodes() { $.get('nodes', function(data) { $("body").append(data); @@ -157,7 +163,7 @@ var RED = function() { $(function() { RED.keyboard.add(/* ? */ 191,{shift:true},function(){showHelp();d3.event.preventDefault();}); - loadNodes(); + loadSettings(); }); return { diff --git a/red.js b/red.js index 38bcc345d..dcb030046 100644 --- a/red.js +++ b/red.js @@ -48,31 +48,57 @@ if (settings.https) { server = http.createServer(function(req,res){app(req,res);}); } +function formatRoot(root) { + if (root[0] != "/") { + root = "/" + root; + } + if (root.slice(-1) != "/") { + root = root + "/"; + } + return root; +} + settings.httpRoot = settings.httpRoot||"/"; -if (settings.httpRoot[0] != "/") { - settings.httpRoot = "/"+settings.httpRoot; -} -if (settings.httpRoot.slice(-1) != "/") { - settings.httpRoot = settings.httpRoot + "/"; -} +settings.httpAdminRoot = formatRoot(settings.httpAdminRoot || settings.httpRoot || "/"); +settings.httpAdminAuth = settings.httpAdminAuth || settings.httpAuth; + +settings.httpNodeRoot = formatRoot(settings.httpNodeRoot || settings.httpRoot || "/"); +settings.httpNodeAuth = settings.httpNodeAuth || settings.httpAuth; + settings.uiPort = settings.uiPort||1880; settings.uiHost = settings.uiHost||"0.0.0.0"; -if (settings.httpAuth) { - app.use(settings.httpRoot, +settings.flowFile = flowFile || settings.flowFile; + +RED.init(server,settings); + +if (settings.httpAdminAuth) { + app.use(settings.httpAdminRoot, express.basicAuth(function(user, pass) { - return user === settings.httpAuth.user && crypto.createHash('md5').update(pass,'utf8').digest('hex') === settings.httpAuth.pass; + return user === settings.httpAdminAuth.user && crypto.createHash('md5').update(pass,'utf8').digest('hex') === settings.httpAdminAuth.pass; }) ); } - -settings.flowFile = flowFile || settings.flowFile; - -var red = RED.init(server,settings); -app.use(settings.httpRoot,red); +if (settings.httpNodeAuth) { + app.use(settings.httpNodeRoot, + express.basicAuth(function(user, pass) { + return user === settings.httpNodeAuth.user && crypto.createHash('md5').update(pass,'utf8').digest('hex') === settings.httpNodeAuth.pass; + }) + ); +} +app.use(settings.httpAdminRoot,RED.httpAdmin); +app.use(settings.httpNodeRoot,RED.httpNode); if (settings.httpStatic) { + settings.httpStaticAuth = settings.httpStaticAuth || settings.httpAuth; + if (settings.httpStaticAuth) { + app.use("/", + express.basicAuth(function(user, pass) { + return user === settings.httpStaticAuth.user && crypto.createHash('md5').update(pass,'utf8').digest('hex') === settings.httpStaticAuth.pass; + }) + ); + } app.use("/",express.static(settings.httpStatic)); } @@ -80,7 +106,7 @@ RED.start(); var listenPath = 'http'+(settings.https?'s':'')+'://'+ (settings.uiHost == '0.0.0.0'?'127.0.0.1':settings.uiHost)+ - ':'+settings.uiPort+settings.httpRoot; + ':'+settings.uiPort+settings.httpAdminRoot; server.listen(settings.uiPort,settings.uiHost,function() { util.log('[red] Server now running at '+listenPath); diff --git a/red/red.js b/red/red.js index 49d19aa07..5e5641930 100644 --- a/red/red.js +++ b/red/red.js @@ -42,7 +42,9 @@ var RED = { events: events }; -RED.__defineGetter__("app", function() { return server.app }); +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 }); RED.__defineGetter__("settings", function() { return settings }); diff --git a/red/server.js b/red/server.js index e292a36ee..ec1509a71 100644 --- a/red/server.js +++ b/red/server.js @@ -14,11 +14,13 @@ * limitations under the License. **/ +var express = require('express'); var util = require('util'); var createUI = require("./ui"); var redNodes = require("./nodes"); var app = null; +var nodeApp = null; var server = null; var settings = null; var storage = null; @@ -28,6 +30,7 @@ function createServer(_server,_settings) { settings = _settings; storage = require("./storage"); app = createUI(settings); + nodeApp = express(); flowfile = settings.flowFile || 'flows_'+require('os').hostname()+'.json'; @@ -103,4 +106,5 @@ module.exports = { } module.exports.__defineGetter__("app", function() { return app }); +module.exports.__defineGetter__("nodeApp", function() { return nodeApp }); module.exports.__defineGetter__("server", function() { return server }); diff --git a/red/ui.js b/red/ui.js index 0592fc058..33449d34a 100644 --- a/red/ui.js +++ b/red/ui.js @@ -34,24 +34,30 @@ function setupUI(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(); - } + if (req.originalUrl.slice(-1) != "/") { + res.redirect(req.originalUrl+"/"); + } else { + req.next(); + } }); app.get("/icons/:icon",function(req,res) { - for (var p in icon_paths) { - if (fs.existsSync(icon_paths[p]+'/'+req.params.icon)) { - res.sendfile(icon_paths[p]+'/'+req.params.icon); - return; - } + for (var p in icon_paths) { + if (fs.existsSync(icon_paths[p]+'/'+req.params.icon)) { + res.sendfile(icon_paths[p]+'/'+req.params.icon); + return; } - //TODO: create a default icon - res.sendfile(path.resolve(__dirname + '/../public/icons/arrow-in.png')); + } + //TODO: create a default icon + res.sendfile(path.resolve(__dirname + '/../public/icons/arrow-in.png')); }); + app.get("/settings", function(req,res) { + var safeSettings = { + httpNodeRoot: settings.httpNodeRoot + }; + res.json(safeSettings); + }); app.use("/",express.static(__dirname + '/../public')); diff --git a/settings.js b/settings.js index 10a9c01aa..e941a19db 100644 --- a/settings.js +++ b/settings.js @@ -49,19 +49,41 @@ module.exports = { // The following property can be used to specify an additional directory to scan. //nodesDir: '/home/nol/.node-red/nodes', - // You can protect the user interface with a userid and password by using the following property - // the password must be an md5 hash eg.. 5f4dcc3b5aa765d61d8327deb882cf99 ('password') - //httpAuth: {user:"user",pass:"5f4dcc3b5aa765d61d8327deb882cf99"}, - // By default, the Node-RED UI is available at http://localhost:1880/ // The following property can be used to specifiy a different root path. - //httpRoot: '/admin', + //httpAdminRoot: '/admin', - // When httpRoot is used to move the UI to a different root path, the + // You can protect the user interface with a userid and password by using the following property. + // The password must be an md5 hash eg.. 5f4dcc3b5aa765d61d8327deb882cf99 ('password') + //httpAdminAuth: {user:"user",pass:"5f4dcc3b5aa765d61d8327deb882cf99"}, + + // Some nodes, such as HTTP In, can be used to listen for incoming http requests. + // By default, these are served relative to '/'. The following property + // can be used to specifiy a different root path. + //httpNodeRoot: '/nodes', + + // To password protect the node-defined HTTP endpoints, the following property + // can be used. + // The password must be an md5 hash eg.. 5f4dcc3b5aa765d61d8327deb882cf99 ('password') + //httpNodeAuth: {user:"user",pass:"5f4dcc3b5aa765d61d8327deb882cf99"}, + + // When httpAdminRoot is used to move the UI to a different root path, the // following property can be used to identify a directory of static content // that should be served at http://localhost:1880/. //httpStatic: '/home/nol/node-red-dashboard/', + // To password protect the static content, the following property can be used. + // The password must be an md5 hash eg.. 5f4dcc3b5aa765d61d8327deb882cf99 ('password') + //httpStaticAuth: {user:"user",pass:"5f4dcc3b5aa765d61d8327deb882cf99"}, + + // The following property can be used in place of 'httpAdminRoot' and 'httpNodeRoot', + // to apply the same root to both parts. + //httpRoot: '/red', + + // The following property can be used in place of 'httpAdminAuth' and 'httpNodeAuth', + // to apply the same authentication to both parts. + //httpAuth: {user:"user",pass:"5f4dcc3b5aa765d61d8327deb882cf99"}, + // The following property can be used to enable HTTPS // See http://nodejs.org/api/https.html#https_https_createserver_options_requestlistener // for details on its contents.