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

Add disableEditor option

Closes #409
This commit is contained in:
Nick O'Leary 2014-09-22 14:33:26 +01:00
parent e48cbafbd6
commit 55c830b812
6 changed files with 262 additions and 232 deletions

3
red.js
View File

@ -99,11 +99,14 @@ if (settings.httpRoot === false) {
settings.httpNodeRoot = false; settings.httpNodeRoot = false;
} else { } else {
settings.httpRoot = settings.httpRoot||"/"; settings.httpRoot = settings.httpRoot||"/";
settings.disableEditor = settings.disableEditor||false;
} }
if (settings.httpAdminRoot !== false) { if (settings.httpAdminRoot !== false) {
settings.httpAdminRoot = formatRoot(settings.httpAdminRoot || settings.httpRoot || "/"); settings.httpAdminRoot = formatRoot(settings.httpAdminRoot || settings.httpRoot || "/");
settings.httpAdminAuth = settings.httpAdminAuth || settings.httpAuth; settings.httpAdminAuth = settings.httpAdminAuth || settings.httpAuth;
} else {
settings.disableEditor = true;
} }
if (settings.httpNodeRoot !== false) { if (settings.httpNodeRoot !== false) {

View File

@ -36,55 +36,61 @@ function init(_server,_settings) {
function start() { function start() {
var webSocketKeepAliveTime = settings.webSocketKeepAliveTime || 15000; if (!settings.disableEditor) {
var path = settings.httpAdminRoot || "/"; var webSocketKeepAliveTime = settings.webSocketKeepAliveTime || 15000;
path = path + (path.slice(-1) == "/" ? "":"/") + "comms"; var path = settings.httpAdminRoot || "/";
wsServer = new ws.Server({server:server,path:path}); path = path + (path.slice(-1) == "/" ? "":"/") + "comms";
wsServer = new ws.Server({server:server,path:path});
wsServer.on('connection',function(ws) {
activeConnections.push(ws); wsServer.on('connection',function(ws) {
ws.on('close',function() { activeConnections.push(ws);
for (var i=0;i<activeConnections.length;i++) { ws.on('close',function() {
if (activeConnections[i] === ws) { for (var i=0;i<activeConnections.length;i++) {
activeConnections.splice(i,1); if (activeConnections[i] === ws) {
break; 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());
});
}); });
ws.on('message', function(data,flags) {
var msg = null; wsServer.on('error', function(err) {
try { util.log("[red:comms] server error : "+err.toString());
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()); lastSentTime = Date.now();
});
}); heartbeatTimer = setInterval(function() {
var now = Date.now();
wsServer.on('error', function(err) { if (now-lastSentTime > webSocketKeepAliveTime) {
util.log("[red:comms] server error : "+err.toString()); publish("hb",lastSentTime);
}); }
}, webSocketKeepAliveTime);
lastSentTime = Date.now(); }
heartbeatTimer = setInterval(function() {
var now = Date.now();
if (now-lastSentTime > webSocketKeepAliveTime) {
publish("hb",lastSentTime);
}
}, webSocketKeepAliveTime);
} }
function stop() { function stop() {
clearInterval(heartbeatTimer); if (heartbeatTimer) {
wsServer.close(); clearInterval(heartbeatTimer);
}
if (wsServer) {
wsServer.close();
}
} }
function publish(topic,data,retain) { function publish(topic,data,retain) {

View File

@ -22,185 +22,192 @@ var exec = require('child_process').exec;
var createUI = require("./ui"); 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 app = null; var app = null;
var nodeApp = null; var nodeApp = null;
var server = null; var server = null;
var settings = null; var settings = null;
var storage = null;
function createServer(_server,_settings) { function createServer(_server,_settings) {
server = _server; server = _server;
settings = _settings; settings = _settings;
comms.init(_server,_settings); comms.init(_server,_settings);
storage = require("./storage");
app = createUI(settings);
nodeApp = express(); nodeApp = express();
app = express();
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 (settings.httpAdminRoot !== false) {
if (req.get("accept") == "application/json") {
res.json(redNodes.getNodeList());
} else { if (!settings.disableEditor) {
res.send(redNodes.getNodeConfigs()); createUI(settings,app);
} }
});
app.get("/flows",function(req,res) {
app.post("/nodes", res.json(redNodes.getFlows());
express.json(), });
function(req,res) {
if (!settings.available()) { app.post("/flows",
res.send(400,new Error("Settings unavailable").toString()); express.json(),
return; 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");
} }
var node = req.body; );
var promise;
if (node.file) { app.get("/nodes",function(req,res) {
promise = redNodes.addNode(node.file).then(reportAddedModules); if (req.get("accept") == "application/json") {
} else if (node.module) { res.json(redNodes.getNodeList());
var module = redNodes.getNodeModuleInfo(node.module); } else {
if (module) { res.send(redNodes.getNodeConfigs());
res.send(400,"Module already loaded"); }
});
app.post("/nodes",
express.json(),
function(req,res) {
if (!settings.available()) {
res.send(400,new Error("Settings unavailable").toString());
return; return;
} }
promise = installModule(node.module); var node = req.body;
} else { var promise;
res.send(400,"Invalid request"); if (node.file) {
return; promise = redNodes.addNode(node.file).then(reportAddedModules);
} } else if (node.module) {
promise.then(function(info) { var module = redNodes.getNodeModuleInfo(node.module);
res.json(info); if (module) {
}).otherwise(function(err) { res.send(400,"Module already loaded");
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; return;
} else {
promise = uninstallModule(id);
} }
promise = installModule(node.module);
} else { } else {
promise = when.resolve([redNodes.removeNode(id)]).then(reportRemovedModules); res.send(400,"Invalid request");
return;
} }
promise.then(function(info) {
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); 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;
} }
} catch(err) { var id = req.params.id;
res.send(400,err.toString()); 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 reportAddedModules(info) { function reportAddedModules(info) {
comms.publish("node/added",info,false); comms.publish("node/added",info,false);
@ -330,7 +337,6 @@ function start() {
}); });
} }
} }
} }
defer.resolve(); defer.resolve();

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.
@ -15,7 +15,6 @@
**/ **/
var express = require('express'); var express = require('express');
var fs = require("fs"); var fs = require("fs");
var app = express();
var events = require("./events"); var events = require("./events");
var path = require("path"); var path = require("path");
@ -24,13 +23,13 @@ var icon_paths = [path.resolve(__dirname + '/../public/icons')];
var settings; // settings has to be global, otherwise variable not in scope for express var settings; // settings has to be global, otherwise variable not in scope for express
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));
}); });
function setupUI(_settings) { function setupUI(_settings,app) {
settings = _settings; // TODO confirm if settings are needed settings = _settings;
// Need to ensure the url ends with a '/' so the static serving works // Need to ensure the url ends with a '/' so the static serving works
// with relative paths // with relative paths

View File

@ -55,6 +55,7 @@ module.exports = {
// By default, the Node-RED UI is available at http://localhost:1880/ // By default, the Node-RED UI is available at http://localhost:1880/
// The following property can be used to specifiy a different root path. // The following property can be used to specifiy a different root path.
// If set to false, this is disabled.
//httpAdminRoot: '/admin', //httpAdminRoot: '/admin',
// You can protect the user interface with a userid and password by using the following property. // You can protect the user interface with a userid and password by using the following property.
@ -63,7 +64,8 @@ module.exports = {
// Some nodes, such as HTTP In, can be used to listen for incoming http requests. // 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 // By default, these are served relative to '/'. The following property
// can be used to specifiy a different root path. // can be used to specifiy a different root path. If set to false, this is
// disabled.
//httpNodeRoot: '/nodes', //httpNodeRoot: '/nodes',
// To password protect the node-defined HTTP endpoints, the following property // To password protect the node-defined HTTP endpoints, the following property
@ -88,6 +90,11 @@ module.exports = {
// to apply the same authentication to both parts. // to apply the same authentication to both parts.
//httpAuth: {user:"user",pass:"5f4dcc3b5aa765d61d8327deb882cf99"}, //httpAuth: {user:"user",pass:"5f4dcc3b5aa765d61d8327deb882cf99"},
// The following property can be used to disable the editor. The admin API
// is not affected by this option. To disable both the editor and the admin
// API, use either the httpRoot or httpAdminRoot properties
//disableEditor: false,
// The following property can be used to enable HTTPS // The following property can be used to enable HTTPS
// See http://nodejs.org/api/https.html#https_https_createserver_options_requestlistener // See http://nodejs.org/api/https.html#https_https_createserver_options_requestlistener
// for details on its contents. // for details on its contents.

View File

@ -15,10 +15,13 @@
**/ **/
var request = require("supertest"); var request = require("supertest");
var express = require("express"); var express = require("express");
var redUI = require("../../red/ui");
describe("red/ui icon handler", function() { describe("red/ui icon handler", function() {
it('returns the default icon when getting an unknown icon', function(done) { it('returns the default icon when getting an unknown icon', function(done) {
var app = require("../../red/ui")(); var app = express();
redUI({},app);
request(app) request(app)
.get("/icons/youwonthaveme.png") .get("/icons/youwonthaveme.png")
.expect('Content-Type', /image\/png/) .expect('Content-Type', /image\/png/)
@ -32,7 +35,8 @@ describe("red/ui icon handler", function() {
}); });
it('returns an icon from disk', function(done) { it('returns an icon from disk', function(done) {
var app = require("../../red/ui")(); var app = express();
redUI({},app);
request(app) request(app)
.get("/icons/arduino.png") .get("/icons/arduino.png")
.expect('Content-Type', /image\/png/) .expect('Content-Type', /image\/png/)
@ -82,7 +86,8 @@ describe("icon cache handler", function() {
* the default PNG would be served * the default PNG would be served
*/ */
it('returns an icon using icon cache', function(done) { it('returns an icon using icon cache', function(done) {
var app = require("../../red/ui")(); var app = express();
redUI({},app);
events.emit("node-icon-dir", tempDir); events.emit("node-icon-dir", tempDir);
request(app) request(app)
.get("/icons/cacheMe.png") .get("/icons/cacheMe.png")
@ -117,41 +122,45 @@ describe("red/ui settings handler", function() {
httpNodeRoot: "testHttpNodeRoot", httpNodeRoot: "testHttpNodeRoot",
version: "testVersion", version: "testVersion",
}; };
var app = require("../../red/ui")(settings); var app = express();
redUI(settings,app);
request(app) request(app)
.get("/settings") .get("/settings")
.expect('Content-Type', /application\/json/) .expect('Content-Type', /application\/json/)
.expect(200, "{\n \"httpNodeRoot\": \"testHttpNodeRoot\",\n \"version\": \"testVersion\"\n}") .expect(200, "{\n \"httpNodeRoot\": \"testHttpNodeRoot\",\n \"version\": \"testVersion\"\n}")
.end(function(err, res){ .end(function(err, res){
if (err){ if (err){
return done(err); return done(err);
} }
done(); done();
}); });
}); });
}); });
describe("red/ui root handler", function() { describe("red/ui root handler", function() {
it('server up the main page', function(done) { it('server up the main page', function(done) {
var app = require("../../red/ui")(); var app = express();
redUI({},app);
request(app) request(app)
.get("/") .get("/")
.expect('Content-Type', /text\/html/) .expect('Content-Type', /text\/html/)
.expect(200) .expect(200)
.end(function(err, res){ .end(function(err, res){
if (err){ if (err){
return done(err); return done(err);
} }
done(); done();
}); });
}); });
it('redirects to path ending with /', function(done) { it('redirects to path ending with /', function(done) {
var app = express().use('/root', require("../../red/ui")()); var rapp = express();
redUI({},rapp);
var app = express().use('/root', rapp);
request(app) request(app)
.get("/root") .get("/root")