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

WIP: create new runtime-api

This commit is contained in:
Nick O'Leary 2018-04-15 11:18:10 +01:00
parent e8a637498d
commit e8e8f70c27
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
22 changed files with 1422 additions and 611 deletions

17
jsdoc.json Normal file
View File

@ -0,0 +1,17 @@
{
"tags": {
"allowUnknownTags": false,
"dictionaries": ["jsdoc"]
},
"source": {
"include": [
"./red/runtime-api"
]
},
"templates": {
"systemName": "Node-RED Runtime API",
"theme":"yeti",
"footer": "",
"copyright": "Released under the Apache License v2.0"
}
}

View File

@ -12,7 +12,8 @@
"scripts": { "scripts": {
"start": "node red.js", "start": "node red.js",
"test": "grunt", "test": "grunt",
"build": "grunt build" "build": "grunt build",
"doc": "jsdoc --pedantic --recurse -c jsdoc.json -t ./node_modules/ink-docstrap/template"
}, },
"bin": { "bin": {
"node-red": "./red.js", "node-red": "./red.js",
@ -49,12 +50,14 @@
"fs.notify": "0.0.4", "fs.notify": "0.0.4",
"hash-sum": "1.0.2", "hash-sum": "1.0.2",
"i18next": "1.10.6", "i18next": "1.10.6",
"ink-docstrap": "^1.3.2",
"is-utf8": "0.2.1", "is-utf8": "0.2.1",
"js-yaml": "3.10.0", "js-yaml": "3.10.0",
"json-stringify-safe": "5.0.1", "json-stringify-safe": "5.0.1",
"jsonata": "1.5.0", "jsonata": "1.5.0",
"media-typer": "0.3.0", "media-typer": "0.3.0",
"memorystore": "1.6.0", "memorystore": "1.6.0",
"mime": "1.4.1",
"mqtt": "2.15.1", "mqtt": "2.15.1",
"multer": "1.3.0", "multer": "1.3.0",
"mustache": "2.3.0", "mustache": "2.3.0",
@ -99,6 +102,7 @@
"grunt-simple-mocha": "~0.4.1", "grunt-simple-mocha": "~0.4.1",
"grunt-webdriver": "^2.0.3", "grunt-webdriver": "^2.0.3",
"istanbul": "0.4.5", "istanbul": "0.4.5",
"jsdoc": "3.5.5",
"mocha": "^5.1.1", "mocha": "^5.1.1",
"should": "^8.4.0", "should": "^8.4.0",
"sinon": "1.17.7", "sinon": "1.17.7",

View File

@ -14,72 +14,56 @@
* limitations under the License. * limitations under the License.
**/ **/
var log; var runtimeAPI;
var redNodes; var apiUtils = require("../util");
module.exports = { module.exports = {
init: function(runtime) { init: function(_runtimeAPI) {
redNodes = runtime.nodes; runtimeAPI = _runtimeAPI;
log = runtime.log;
}, },
get: function(req,res) { get: function(req,res) {
var id = req.params.id; var opts = {
var flow = redNodes.getFlow(id); user: req.user,
if (flow) { id: req.params.id
log.audit({event: "flow.get",id:id},req);
res.json(flow);
} else {
log.audit({event: "flow.get",id:id,error:"not_found"},req);
res.status(404).end();
} }
runtimeAPI.flows.getFlow(opts).then(function(result) {
return res.json(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
}, },
post: function(req,res) { post: function(req,res) {
var flow = req.body; var opts = {
redNodes.addFlow(flow).then(function(id) { user: req.user,
log.audit({event: "flow.add",id:id},req); flow: req.body
res.json({id:id}); }
runtimeAPI.flows.addFlow(opts).then(function(id) {
return res.json({id:id});
}).catch(function(err) { }).catch(function(err) {
log.audit({event: "flow.add",error:err.code||"unexpected_error",message:err.toString()},req); apiUtils.rejectHandler(req,res,err);
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
}) })
}, },
put: function(req,res) { put: function(req,res) {
var id = req.params.id; var opts = {
var flow = req.body; user: req.user,
try { id: req.params.id,
redNodes.updateFlow(id,flow).then(function() { flow: req.body
log.audit({event: "flow.update",id:id},req); }
res.json({id:id}); runtimeAPI.flows.updateFlow(opts).then(function(id) {
return res.json({id:id});
}).catch(function(err) { }).catch(function(err) {
log.audit({event: "flow.update",error:err.code||"unexpected_error",message:err.toString()},req); apiUtils.rejectHandler(req,res,err);
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
}) })
} catch(err) {
if (err.code === 404) {
log.audit({event: "flow.update",id:id,error:"not_found"},req);
res.status(404).end();
} else {
log.audit({event: "flow.update",error:err.code||"unexpected_error",message:err.toString()},req);
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
}
}
}, },
delete: function(req,res) { delete: function(req,res) {
var id = req.params.id; var opts = {
try { user: req.user,
redNodes.removeFlow(id).then(function() { id: req.params.id
log.audit({event: "flow.remove",id:id},req); }
runtimeAPI.flows.deleteFlow(opts).then(function() {
res.status(204).end(); res.status(204).end();
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
}) })
} catch(err) {
if (err.code === 404) {
log.audit({event: "flow.remove",id:id,error:"not_found"},req);
res.status(404).end();
} else {
log.audit({event: "flow.remove",id:id,error:err.code||"unexpected_error",message:err.toString()},req);
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
}
}
} }
} }

View File

@ -14,72 +14,56 @@
* limitations under the License. * limitations under the License.
**/ **/
var log; var runtimeAPI;
var redNodes;
module.exports = { module.exports = {
init: function(runtime) { init: function(_runtimeAPI) {
redNodes = runtime.nodes; runtimeAPI = _runtimeAPI;
log = runtime.log;
}, },
get: function(req,res) { get: function(req,res) {
var version = req.get("Node-RED-API-Version")||"v1"; var version = req.get("Node-RED-API-Version")||"v1";
if (version === "v1") { if (!/^v[12]$/.test(version)) {
log.audit({event: "flows.get",version:"v1"},req); return res.status(500).json({code:"invalid_api_version", message:"Invalid API Version requested"});
res.json(redNodes.getFlows().flows);
} else if (version === "v2") {
log.audit({event: "flows.get",version:"v2"},req);
res.json(redNodes.getFlows());
} else {
log.audit({event: "flows.get",version:version,error:"invalid_api_version"},req);
res.status(400).json({code:"invalid_api_version", message:"Invalid API Version requested"});
} }
var opts = {
user: req.user
}
runtimeAPI.flows.getFlows(opts).then(function(result) {
if (version === "v1") {
res.json(result.flows);
} else if (version === "v2") {
res.json(result);
}
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
}, },
post: function(req,res) { post: function(req,res) {
var version = req.get("Node-RED-API-Version")||"v1"; var version = req.get("Node-RED-API-Version")||"v1";
if (!/^v[12]$/.test(version)) { if (!/^v[12]$/.test(version)) {
log.audit({event: "flows.set",version:version,error:"invalid_api_version"},req); return res.status(500).json({code:"invalid_api_version", message:"Invalid API Version requested"});
res.status(400).json({code:"invalid_api_version", message:"Invalid API Version requested"});
return;
} }
var flows = req.body; var opts = {
var deploymentType = req.get("Node-RED-Deployment-Type")||"full"; user: req.user,
log.audit({event: "flows.set",type:deploymentType,version:version},req); deploymentType: req.get("Node-RED-Deployment-Type")||"full"
if (deploymentType === 'reload') { }
redNodes.loadFlows().then(function(flowId) {
if (opts.deploymentType !== 'reload') {
if (version === "v1") {
opts.flows = {flows: req.body}
} else {
opts.flows = req.body;
}
}
runtimeAPI.flows.setFlows(opts).then(function(result) {
if (version === "v1") { if (version === "v1") {
res.status(204).end(); res.status(204).end();
} else { } else {
res.json({rev:flowId}); res.json(result);
} }
}).catch(function(err) { }).catch(function(err) {
log.warn(log._("api.flows.error-reload",{message:err.message})); apiUtils.rejectHandler(req,res,err);
log.warn(err.stack); })
res.status(500).json({error:"unexpected_error", message:err.message});
});
} else {
var flowConfig = flows;
if (version === "v2") {
flowConfig = flows.flows;
if (flows.hasOwnProperty('rev')) {
var currentVersion = redNodes.getFlows().rev;
if (currentVersion !== flows.rev) {
//TODO: log warning
return res.status(409).json({code:"version_mismatch"});
}
}
}
redNodes.setFlows(flowConfig,deploymentType).then(function(flowId) {
if (version === "v1") {
res.status(204).end();
} else if (version === "v2") {
res.json({rev:flowId});
}
}).catch(function(err) {
log.warn(log._("api.flows.error-save",{message:err.message}));
log.warn(err.stack);
res.status(500).json({error:err.code || "unexpected_error", message:err.message});
});
}
} }
} }

View File

@ -24,10 +24,10 @@ var auth = require("../auth");
var apiUtil = require("../util"); var apiUtil = require("../util");
module.exports = { module.exports = {
init: function(runtime) { init: function(runtimeAPI) {
flows.init(runtime); flows.init(runtimeAPI);
flow.init(runtime); flow.init(runtimeAPI);
nodes.init(runtime); nodes.init(runtimeAPI);
var needsPermission = auth.needsPermission; var needsPermission = auth.needsPermission;

View File

@ -14,230 +14,133 @@
* limitations under the License. * limitations under the License.
**/ **/
var when = require("when");
var apiUtils = require("../util"); var apiUtils = require("../util");
var redNodes;
var log; var runtimeAPI;
var settings;
module.exports = { module.exports = {
init: function(runtime) { init: function(_runtimeAPI) {
redNodes = runtime.nodes; runtimeAPI = _runtimeAPI;
log = runtime.log;
settings = runtime.settings;
}, },
getAll: function(req,res) { getAll: function(req,res) {
var opts = {
user: req.user
}
if (req.get("accept") == "application/json") { if (req.get("accept") == "application/json") {
log.audit({event: "nodes.list.get"},req); runtimeAPI.nodes.getNodeList(opts).then(function(list) {
res.json(redNodes.getNodeList()); res.json(list);
})
} else { } else {
var lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages()); opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages());
log.audit({event: "nodes.configs.get"},req); runtimeAPI.nodes.getNodeConfigs(opts).then(function(configs) {
res.send(redNodes.getNodeConfigs(lang)); res.send(configs);
})
} }
}, },
post: function(req,res) { post: function(req,res) {
if (!settings.available()) { var opts = {
log.audit({event: "nodes.install",error:"settings_unavailable"},req); user: req.user,
res.status(400).json({error:"settings_unavailable", message:"Settings unavailable"}); module: req.body.module,
return; version: req.body.version
} }
var node = req.body; runtimeAPI.nodes.addModule(opts).then(function(info) {
var promise;
var isUpgrade = false;
if (node.module) {
var module = redNodes.getModuleInfo(node.module);
if (module) {
if (!node.version || module.version === node.version) {
log.audit({event: "nodes.install",module:node.module, version:node.version, error:"module_already_loaded"},req);
res.status(400).json({error:"module_already_loaded", message:"Module already loaded"});
return;
}
if (!module.local) {
log.audit({event: "nodes.install",module:node.module, version:node.version, error:"module_not_local"},req);
res.status(400).json({error:"module_not_local", message:"Module not locally installed"});
return;
}
isUpgrade = true;
}
promise = redNodes.installModule(node.module,node.version);
} else {
log.audit({event: "nodes.install",module:node.module,error:"invalid_request"},req);
res.status(400).json({error:"invalid_request", message:"Invalid request"});
return;
}
promise.then(function(info) {
if (node.module) {
log.audit({event: "nodes.install",module:node.module,version:node.version},req);
res.json(info); res.json(info);
}
}).catch(function(err) { }).catch(function(err) {
if (err.code === 404) { apiUtils.rejectHandler(req,res,err);
log.audit({event: "nodes.install",module:node.module,version:node.version,error:"not_found"},req); })
res.status(404).end();
} else if (err.code) {
log.audit({event: "nodes.install",module:node.module,version:node.version,error:err.code},req);
res.status(400).json({error:err.code, message:err.message});
} else {
log.audit({event: "nodes.install",module:node.module,version:node.version,error:err.code||"unexpected_error",message:err.toString()},req);
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
}
});
}, },
delete: function(req,res) { delete: function(req,res) {
if (!settings.available()) { var opts = {
log.audit({event: "nodes.remove",error:"settings_unavailable"},req); user: req.user,
res.status(400).json({error:"settings_unavailable", message:"Settings unavailable"}); module: req.params[0]
return;
} }
var mod = req.params[0]; runtimeAPI.nodes.removeModule(opts).then(function(info) {
try {
var promise = null;
var module = redNodes.getModuleInfo(mod);
if (!module) {
log.audit({event: "nodes.remove",module:mod,error:"not_found"},req);
res.status(404).end();
return;
} else {
promise = redNodes.uninstallModule(mod);
}
promise.then(function(list) {
log.audit({event: "nodes.remove",module:mod},req);
res.status(204).end(); res.status(204).end();
}).catch(function(err) { }).catch(function(err) {
log.audit({event: "nodes.remove",module:mod,error:err.code||"unexpected_error",message:err.toString()},req); apiUtils.rejectHandler(req,res,err);
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()}); })
});
} catch(err) {
log.audit({event: "nodes.remove",module:mod,error:err.code||"unexpected_error",message:err.toString()},req);
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
}
}, },
getSet: function(req,res) { getSet: function(req,res) {
var id = req.params[0] + "/" + req.params[2]; var opts = {
var result = null; user: req.user,
id: req.params[0] + "/" + req.params[2]
}
if (req.get("accept") === "application/json") { if (req.get("accept") === "application/json") {
result = redNodes.getNodeInfo(id); runtimeAPI.nodes.getNodeInfo(opts).then(function(result) {
if (result) {
log.audit({event: "nodes.info.get",id:id},req);
delete result.loaded;
res.send(result); res.send(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
} else { } else {
log.audit({event: "nodes.info.get",id:id,error:"not_found"},req); opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages());
res.status(404).end(); runtimeAPI.nodes.getNodeConfig(opts).then(function(result) {
} return res.json(result);
} else { }).catch(function(err) {
var lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages()); apiUtils.rejectHandler(req,res,err);
result = redNodes.getNodeConfig(id,lang); })
if (result) {
log.audit({event: "nodes.config.get",id:id},req);
res.send(result);
} else {
log.audit({event: "nodes.config.get",id:id,error:"not_found"},req);
res.status(404).end();
}
} }
}, },
getModule: function(req,res) { getModule: function(req,res) {
var module = req.params[0]; var opts = {
var result = redNodes.getModuleInfo(module); user: req.user,
if (result) { module: req.params[0]
log.audit({event: "nodes.module.get",module:module},req);
res.json(result);
} else {
log.audit({event: "nodes.module.get",module:module,error:"not_found"},req);
res.status(404).end();
} }
runtimeAPI.nodes.getModuleInfo(opts).then(function(result) {
res.send(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
}, },
putSet: function(req,res) { putSet: function(req,res) {
if (!settings.available()) {
log.audit({event: "nodes.info.set",error:"settings_unavailable"},req);
res.status(400).json({error:"settings_unavailable", message:"Settings unavailable"});
return;
}
var body = req.body; var body = req.body;
if (!body.hasOwnProperty("enabled")) { if (!body.hasOwnProperty("enabled")) {
log.audit({event: "nodes.info.set",error:"invalid_request"},req); // log.audit({event: "nodes.module.set",error:"invalid_request"},req);
res.status(400).json({error:"invalid_request", message:"Invalid request"}); res.status(400).json({error:"invalid_request", message:"Invalid request"});
return; return;
} }
var id = req.params[0] + "/" + req.params[2]; var opts = {
try { user: req.user,
var node = redNodes.getNodeInfo(id); id: req.params[0] + "/" + req.params[2],
var info; enabled: body.enabled
if (!node) {
log.audit({event: "nodes.info.set",id:id,error:"not_found"},req);
res.status(404).end();
} else {
delete node.loaded;
putNode(node, body.enabled).then(function(result) {
log.audit({event: "nodes.info.set",id:id,enabled:body.enabled},req);
res.json(result);
});
}
} catch(err) {
log.audit({event: "nodes.info.set",id:id,enabled:body.enabled,error:err.code||"unexpected_error",message:err.toString()},req);
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
} }
runtimeAPI.nodes.setNodeSetState(opts).then(function(result) {
res.send(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
}, },
putModule: function(req,res) { putModule: function(req,res) {
if (!settings.available()) {
log.audit({event: "nodes.module.set",error:"settings_unavailable"},req);
res.status(400).json({error:"settings_unavailable", message:"Settings unavailable"});
return;
}
var body = req.body; var body = req.body;
if (!body.hasOwnProperty("enabled")) { if (!body.hasOwnProperty("enabled")) {
log.audit({event: "nodes.module.set",error:"invalid_request"},req); // log.audit({event: "nodes.module.set",error:"invalid_request"},req);
res.status(400).json({error:"invalid_request", message:"Invalid request"}); res.status(400).json({error:"invalid_request", message:"Invalid request"});
return; return;
} }
var mod = req.params[0]; var opts = {
try { user: req.user,
var module = redNodes.getModuleInfo(mod); module: req.params[0],
if (!module) { enabled: body.enabled
log.audit({event: "nodes.module.set",module:mod,error:"not_found"},req);
return res.status(404).end();
} }
runtimeAPI.nodes.setModuleState(opts).then(function(result) {
res.send(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
var nodes = module.nodes;
var promises = [];
for (var i = 0; i < nodes.length; ++i) {
promises.push(putNode(nodes[i],body.enabled));
}
when.settle(promises).then(function() {
res.json(redNodes.getModuleInfo(mod));
});
} catch(err) {
log.audit({event: "nodes.module.set",module:mod,enabled:body.enabled,error:err.code||"unexpected_error",message:err.toString()},req);
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
}
}, },
getIcons: function(req,res) { getIcons: function(req,res) {
log.audit({event: "nodes.icons.get"},req); var opts = {
res.json(redNodes.getNodeIcons()); user: req.user
}
runtimeAPI.nodes.getIconList(opts).then(function(list) {
res.json(list);
});
} }
}; };
function putNode(node, enabled) {
var info;
var promise;
if (!node.err && node.enabled === enabled) {
promise = when.resolve(node);
} else {
if (enabled) {
promise = redNodes.enableNode(node.id);
} else {
promise = redNodes.disableNode(node.id);
}
}
return promise;
}

View File

@ -14,39 +14,23 @@
* limitations under the License. * limitations under the License.
**/ **/
var log; var runtimeAPI;
var api; var apiUtils = require("../util");
module.exports = { module.exports = {
init: function(runtime) { init: function(_runtimeAPI) {
log = runtime.log; runtimeAPI = _runtimeAPI
api = runtime.nodes;
}, },
get: function (req, res) { get: function (req, res) {
// TODO: It should verify the given node id is of the type specified - var opts = {
// but that would add a dependency from this module to the user: req.user,
// registry module that knows about node types. type: req.params.type,
var nodeType = req.params.type; id: req.params.id
var nodeID = req.params.id;
log.audit({event: "credentials.get",type:nodeType,id:nodeID},req);
var credentials = api.getCredentials(nodeID);
if (!credentials) {
res.json({});
return;
} }
var definition = api.getCredentialDefinition(nodeType); runtimeAPI.flows.getNodeCredentials(opts).then(function(result) {
res.json(result);
var sendCredentials = {}; }).catch(function(err) {
for (var cred in definition) { apiUtils.rejectHandler(req,res,err);
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);
} }
} }

View File

@ -25,6 +25,7 @@ var auth = require("../auth");
var nodes = require("../admin/nodes"); // TODO: move /icons into here var nodes = require("../admin/nodes"); // TODO: move /icons into here
var needsPermission; var needsPermission;
var runtime; var runtime;
var runtimeAPI;
var log; var log;
var apiUtil = require("../util"); var apiUtil = require("../util");
@ -38,16 +39,17 @@ var ensureRuntimeStarted = function(req,res,next) {
} }
module.exports = { module.exports = {
init: function(server, _runtime) { init: function(server, settings, _runtime, _runtimeAPI) {
runtime = _runtime; runtime = _runtime;
runtimeAPI = _runtimeAPI;
log = runtime.log; log = runtime.log;
needsPermission = auth.needsPermission; needsPermission = auth.needsPermission;
var settings = runtime.settings;
if (!settings.disableEditor) { if (!settings.disableEditor) {
info.init(runtime); info.init(runtimeAPI);
comms.init(server,runtime); comms.init(server,runtime);
var ui = require("./ui"); var ui = require("./ui");
// ui is passed runtime so it get access runtime.nodes.getNodeIconPath
ui.init(runtime); ui.init(runtime);
var editorApp = express(); var editorApp = express();
if (settings.requireHttps === true) { if (settings.requireHttps === true) {
@ -67,7 +69,7 @@ module.exports = {
editorApp.get("/icons/:scope/:module/:icon",ui.icon); editorApp.get("/icons/:scope/:module/:icon",ui.icon);
var theme = require("./theme"); var theme = require("./theme");
theme.init(runtime); theme.init(settings, runtime.version());
editorApp.use("/theme",theme.app()); editorApp.use("/theme",theme.app());
editorApp.use("/",ui.editorResources); editorApp.use("/",ui.editorResources);
@ -91,7 +93,7 @@ module.exports = {
// Credentials // Credentials
var credentials = require("./credentials"); var credentials = require("./credentials");
credentials.init(runtime); credentials.init(runtimeAPI);
editorApp.get('/credentials/:type/:id', needsPermission("credentials.read"),credentials.get,apiUtil.errorHandler); editorApp.get('/credentials/:type/:id', needsPermission("credentials.read"),credentials.get,apiUtil.errorHandler);
// Settings // Settings
@ -100,11 +102,8 @@ module.exports = {
editorApp.get("/settings/user",needsPermission("settings.read"),info.userSettings,apiUtil.errorHandler); editorApp.get("/settings/user",needsPermission("settings.read"),info.userSettings,apiUtil.errorHandler);
// User Settings // User Settings
editorApp.post("/settings/user",needsPermission("settings.write"),info.updateUserSettings,apiUtil.errorHandler); editorApp.post("/settings/user",needsPermission("settings.write"),info.updateUserSettings,apiUtil.errorHandler);
// SSH keys // SSH keys
var sshkeys = require("./sshkeys"); editorApp.use("/settings/user/keys",info.sshkeys());
sshkeys.init(runtime);
editorApp.use("/settings/user/keys",sshkeys.app());
return editorApp; return editorApp;
} }

View File

@ -13,117 +13,48 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
**/ **/
var theme = require("../editor/theme"); var apiUtils = require("../util");
var util = require('util'); var runtimeAPI;
var runtime; var sshkeys = require("./sshkeys");
var settings; var theme = require("./theme");
var log;
module.exports = { module.exports = {
init: function(_runtime) { init: function(_runtimeAPI) {
runtime = _runtime; runtimeAPI = _runtimeAPI;
settings = runtime.settings; sshkeys.init(runtimeAPI);
log = runtime.log;
}, },
runtimeSettings: function(req,res) { runtimeSettings: function(req,res) {
var safeSettings = { var opts = {
httpNodeRoot: settings.httpNodeRoot||"/",
version: settings.version,
user: req.user user: req.user
} }
runtimeAPI.settings.getRuntimeSettings(opts).then(function(result) {
var themeSettings = theme.settings(); var themeSettings = theme.settings();
if (themeSettings) { if (themeSettings) {
safeSettings.editorTheme = themeSettings; result.editorTheme = themeSettings;
} }
res.json(result);
if (util.isArray(settings.paletteCategories)) { });
safeSettings.paletteCategories = settings.paletteCategories;
}
if (settings.flowFilePretty) {
safeSettings.flowFilePretty = settings.flowFilePretty;
}
if (!runtime.nodes.paletteEditorEnabled()) {
safeSettings.editorTheme = safeSettings.editorTheme || {};
safeSettings.editorTheme.palette = safeSettings.editorTheme.palette || {};
safeSettings.editorTheme.palette.editable = false;
}
if (runtime.storage.projects) {
var activeProject = runtime.storage.projects.getActiveProject();
if (activeProject) {
safeSettings.project = activeProject;
} else if (runtime.storage.projects.flowFileExists()) {
safeSettings.files = {
flow: runtime.storage.projects.getFlowFilename(),
credentials: runtime.storage.projects.getCredentialsFilename()
}
}
safeSettings.git = {
globalUser: runtime.storage.projects.getGlobalGitUser()
}
}
safeSettings.flowEncryptionType = runtime.nodes.getCredentialKeyType();
settings.exportNodeSettings(safeSettings);
res.json(safeSettings);
}, },
userSettings: function(req, res) { userSettings: function(req, res) {
var username; var opts = {
if (!req.user || req.user.anonymous) { user: req.user
username = '_';
} else {
username = req.user.username;
} }
res.json(settings.getUserSettings(username)||{}); runtimeAPI.settings.getUserSettings(opts).then(function(result) {
res.json(result);
});
}, },
updateUserSettings: function(req,res) { updateUserSettings: function(req,res) {
var username; var opts = {
if (!req.user || req.user.anonymous) { user: req.user,
username = '_'; settings: req.body
} else {
username = req.user.username;
} }
var currentSettings = settings.getUserSettings(username)||{}; runtimeAPI.settings.updateUserSettings(opts).then(function(result) {
currentSettings = extend(currentSettings, req.body);
try {
settings.setUserSettings(username, currentSettings).then(function() {
log.audit({event: "settings.update",username:username},req);
res.status(204).end(); res.status(204).end();
}).catch(function(err) { }).catch(function(err) {
log.audit({event: "settings.update",username:username,error:err.code||"unexpected_error",message:err.toString()},req); apiUtils.rejectHandler(req,res,err);
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
}); });
} catch(err) { },
log.warn(log._("settings.user-not-available",{message:log._("settings.not-available")})); sshkeys: function() {
log.audit({event: "settings.update",username:username,error:err.code||"unexpected_error",message:err.toString()},req); return sshkeys.app()
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
} }
} }
}
function extend(target, source) {
var keys = Object.keys(source);
var i = keys.length;
while(i--) {
var value = source[keys[i]]
var type = typeof value;
if (type === 'string' || type === 'number' || type === 'boolean' || Array.isArray(value)) {
target[keys[i]] = value;
} else if (value === null) {
if (target.hasOwnProperty(keys[i])) {
delete target[keys[i]];
}
} else {
// Object
if (target.hasOwnProperty(keys[i])) {
target[keys[i]] = extend(target[keys[i]],value);
} else {
target[keys[i]] = value;
}
}
}
return target;
}

View File

@ -15,8 +15,7 @@
**/ **/
var express = require("express"); var express = require("express");
var os = require("os"); var runtimeAPI;
var runtime;
var needsPermission = require("../auth").needsPermission; var needsPermission = require("../auth").needsPermission;
function getUsername(userObj) { function getUsername(userObj) {
@ -28,94 +27,66 @@ function getUsername(userObj) {
} }
module.exports = { module.exports = {
init: function(_runtime) { init: function(_runtimeAPI) {
runtime = _runtime; runtimeAPI = _runtimeAPI;
}, },
app: function() { app: function() {
var app = express(); var app = express();
// SSH keys
// List all SSH keys // List all SSH keys
app.get("/", needsPermission("settings.read"), function(req,res) { app.get("/", needsPermission("settings.read"), function(req,res) {
var username = getUsername(req.user); var opts = {
runtime.storage.projects.ssh.listSSHKeys(username) user: req.user
.then(function(list) { }
runtimeAPI.settings.getUserKeys(opts).then(function(list) {
res.json({ res.json({
keys: list keys: list
}); });
}) }).catch(function(err) {
.catch(function(err) { apiUtils.rejectHandler(req,res,err);
// console.log(err.stack);
if (err.code) {
res.status(400).json({error:err.code, message: err.message});
} else {
res.status(400).json({error:"unexpected_error", message:err.toString()});
}
}); });
}); });
// Get SSH key detail // Get SSH key detail
app.get("/:id", needsPermission("settings.read"), function(req,res) { app.get("/:id", needsPermission("settings.read"), function(req,res) {
var username = getUsername(req.user); var opts = {
// console.log('username:', username); user: req.user,
runtime.storage.projects.ssh.getSSHKey(username, req.params.id) id: req.params.id
.then(function(data) { }
if (data) { runtimeAPI.settings.getUserKey(opts).then(function(data) {
res.json({ res.json({
publickey: data publickey: data
}); });
} else { }).catch(function(err) {
res.status(404).end(); apiUtils.rejectHandler(req,res,err);
}
})
.catch(function(err) {
if (err.code) {
res.status(400).json({error:err.code, message: err.message});
} else {
res.status(400).json({error:"unexpected_error", message:err.toString()});
}
}); });
}); });
// Generate a SSH key // Generate a SSH key
app.post("/", needsPermission("settings.write"), function(req,res) { app.post("/", needsPermission("settings.write"), function(req,res) {
var username = getUsername(req.user); var opts = {
// console.log('req.body:', req.body); user: req.user,
if ( req.body && req.body.name && /^[a-zA-Z0-9\-_]+$/.test(req.body.name)) { id: req.params.id
runtime.storage.projects.ssh.generateSSHKey(username, req.body) }
.then(function(name) { runtimeAPI.settings.generateUserKey(opts).then(function(name) {
// console.log('generate key --- success name:', name);
res.json({ res.json({
name: name name: name
}); });
}) }).catch(function(err) {
.catch(function(err) { apiUtils.rejectHandler(req,res,err);
if (err.code) {
res.status(400).json({error:err.code, message: err.message});
} else {
res.status(400).json({error:"unexpected_error", message:err.toString()});
}
}); });
}
else {
res.status(400).json({error:"unexpected_error", message:"You need to have body or body.name"});
}
}); });
// Delete a SSH key // Delete a SSH key
app.delete("/:id", needsPermission("settings.write"), function(req,res) { app.delete("/:id", needsPermission("settings.write"), function(req,res) {
var username = getUsername(req.user); var opts = {
runtime.storage.projects.ssh.deleteSSHKey(username, req.params.id) user: req.user,
.then(function() { id: req.params.id
res.status(204).end();
})
.catch(function(err) {
if (err.code) {
res.status(400).json({error:err.code, message: err.message});
} else {
res.status(400).json({error:"unexpected_error", message:err.toString()});
} }
runtimeAPI.settings.generateUserKey(opts).then(function(name) {
res.status(204).end();
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
}); });
}); });

View File

@ -40,7 +40,6 @@ var defaultContext = {
var theme = null; var theme = null;
var themeContext = clone(defaultContext); var themeContext = clone(defaultContext);
var themeSettings = null; var themeSettings = null;
var runtime = null;
var themeApp; var themeApp;
@ -78,11 +77,10 @@ function serveFilesFromTheme(themeValue, themeApp, directory) {
} }
module.exports = { module.exports = {
init: function(runtime) { init: function(settings,version) {
var settings = runtime.settings;
themeContext = clone(defaultContext); themeContext = clone(defaultContext);
if (runtime.version) { if (version) {
themeContext.version = runtime.version(); themeContext.version = version;
} }
themeSettings = null; themeSettings = null;
theme = settings.editorTheme || {}; theme = settings.editorTheme || {};

View File

@ -26,13 +26,10 @@ var apiUtil = require("./util");
var adminApp; var adminApp;
var server; var server;
var runtime;
var editor; var editor;
function init(_server,_runtime) { function init(_server,settings,runtime,runtimeAPI) {
server = _server; server = _server;
runtime = _runtime;
var settings = runtime.settings;
if (settings.httpAdminRoot !== false) { if (settings.httpAdminRoot !== false) {
apiUtil.init(runtime); apiUtil.init(runtime);
adminApp = express(); adminApp = express();
@ -61,7 +58,7 @@ function init(_server,_runtime) {
// Editor // Editor
if (!settings.disableEditor) { if (!settings.disableEditor) {
editor = require("./editor"); editor = require("./editor");
var editorApp = editor.init(server, runtime); var editorApp = editor.init(server, settings, runtime, runtimeAPI);
adminApp.use(editorApp); adminApp.use(editorApp);
} }
@ -70,7 +67,7 @@ function init(_server,_runtime) {
adminApp.use(corsHandler); adminApp.use(corsHandler);
} }
var adminApiApp = require("./admin").init(runtime); var adminApiApp = require("./admin").init(runtimeAPI);
adminApp.use(adminApiApp); adminApp.use(adminApiApp);
} else { } else {
adminApp = null; adminApp = null;

View File

@ -41,5 +41,15 @@ module.exports = {
lang = acceptedLanguages[0]; lang = acceptedLanguages[0];
} }
return lang; return lang;
},
rejectHandler: function(req,res,err) {
res.status(err.status||500);
if (err.code || err.message) {
res.json({
code: err.code||"unexpected_error",
message: err.message
})
}
res.end();
} }
} }

View File

@ -18,6 +18,8 @@ var fs = require("fs");
var path = require('path'); var path = require('path');
var runtime = require("./runtime"); var runtime = require("./runtime");
var runtimeAPI = require("./runtime-api");
var api = require("./api"); var api = require("./api");
process.env.NODE_RED_HOME = process.env.NODE_RED_HOME || path.resolve(__dirname+"/.."); process.env.NODE_RED_HOME = process.env.NODE_RED_HOME || path.resolve(__dirname+"/..");
@ -67,7 +69,10 @@ module.exports = {
if (userSettings.httpAdminRoot !== false) { if (userSettings.httpAdminRoot !== false) {
runtime.init(userSettings,api); runtime.init(userSettings,api);
api.init(httpServer,runtime);
runtimeAPI.init(runtime);
api.init(httpServer,userSettings,runtime,runtimeAPI);
apiEnabled = true; apiEnabled = true;
server = runtime.adminApi.server; server = runtime.adminApi.server;
runtime.server = runtime.adminApi.server; runtime.server = runtime.adminApi.server;

19
red/runtime-api/auth.js Normal file
View File

@ -0,0 +1,19 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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.
**/
/**
* @module red/auth
*/

19
red/runtime-api/comms.js Normal file
View File

@ -0,0 +1,19 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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.
**/
/**
* @module red/comms
*/

248
red/runtime-api/flows.js Normal file
View File

@ -0,0 +1,248 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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.
**/
/**
* @namespace RED.flows
*/
/**
* @typedef Flows
* @alias Dave
* @type {object}
* @property {string} rev - the flow revision identifier
* @property {Array} flows - the flow configuration, an array of node configuration objects
*/
/**
* @typedef Flow
* @type {object}
* @property {string} id - the flow identifier
* @property {string} label - a label for the flow
* @property {Array} nodes - an array of node configuration objects
*/
var runtime;
var api = module.exports = {
init: function(_runtime) {
runtime = _runtime;
},
/**
* Gets the current flow configuration
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @return {Promise<Flows>} - the active flow configuration
* @memberof RED.flows
*/
getFlows: function(opts) {
return new Promise(function(resolve,reject) {
runtime.log.audit({event: "flows.get"}/*,req*/);
var version = opts.version||"v1";
return resolve(runtime.nodes.getFlows());
});
},
/**
* Sets the current flow configuration
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @return {Promise<Flows>} - the active flow configuration
* @memberof RED.flows
*/
setFlows: function(opts) {
var err;
return new Promise(function(resolve,reject) {
var flows = opts.flows;
var deploymentType = opts.deploymentType||"full";
runtime.log.audit({event: "flows.set",type:deploymentType}/*,req*/);
var apiPromise;
if (deploymentType === 'reload') {
apiPromise = runtime.nodes.loadFlows();
} else {
if (flows.hasOwnProperty('rev')) {
var currentVersion = runtime.nodes.getFlows().rev;
if (currentVersion !== flows.rev) {
err = new Error();
err.code = "version_mismatch";
err.status = 409;
//TODO: log warning
return reject(err);
}
}
apiPromise = runtime.nodes.setFlows(flows.flows,deploymentType);
}
apiPromise.then(function(flowId) {
return resolve({rev:flowId});
}).catch(function(err) {
log.warn(log._("api.flows.error-"+(deploymentType === 'reload'?'reload':'save'),{message:err.message}));
log.warn(err.stack);
return reject(err);
});
});
},
/**
* Adds a flow configuration
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @param {Object} opts.flow - the flow to add
* @return {Promise<String>} - the id of the added flow
* @memberof RED.flows
*/
addFlow: function(opts) {
return new Promise(function(resolve,reject) {
var flow = opts.flow;
runtime.nodes.addFlow(flow).then(function(id) {
runtime.log.audit({event: "flow.add",id:id});
return resolve(id);
}).catch(function(err) {
runtime.log.audit({event: "flow.add",error:err.code||"unexpected_error",message:err.toString()});
err.status = 400;
return reject(err);
})
})
},
/**
* Gets an individual flow configuration
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @param {Object} opts.id - the id of the flow to retrieve
* @return {Promise<Flow>} - the active flow configuration
* @memberof RED.flows
*/
getFlow: function(opts) {
return new Promise(function (resolve,reject) {
var flow = runtime.nodes.getFlow(opts.id);
if (flow) {
runtime.log.audit({event: "flow.get",id:opts.id});
return resolve(flow);
} else {
runtime.log.audit({event: "flow.get",id:opts.id,error:"not_found"});
var err = new Error();
err.status = 404;
return reject(err);
}
})
},
/**
* Updates an existing flow configuration
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @param {Object} opts.id - the id of the flow to update
* @param {Object} opts.flow - the flow configuration
* @return {Promise<String>} - the id of the updated flow
* @memberof RED.flows
*/
updateFlow: function(opts) {
return new Promise(function (resolve,reject) {
var flow = opts.flow;
var id = opts.id;
try {
runtime.nodes.updateFlow(id,flow).then(function() {
runtime.log.audit({event: "flow.update",id:id});
return resolve(id);
}).catch(function(err) {
runtime.log.audit({event: "flow.update",error:err.code||"unexpected_error",message:err.toString()});
err.status = 400;
return reject(err);
})
} catch(err) {
if (err.code === 404) {
runtime.log.audit({event: "flow.update",id:id,error:"not_found"});
// TODO: this swap around of .code and .status isn't ideal
err.status = 404;
err.code = "not_found";
return reject(err);
} else {
runtime.log.audit({event: "flow.update",error:err.code||"unexpected_error",message:err.toString()});
err.status = 400;
return reject(err);
}
}
});
},
/**
* Deletes a flow
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @param {Object} opts.id - the id of the flow to delete
* @return {Promise} - resolves if successful
* @memberof RED.flows
*/
deleteFlow: function(opts) {
return new Promise(function (resolve,reject) {
var id = opts.id;
try {
runtime.nodes.removeFlow(id).then(function() {
log.audit({event: "flow.remove",id:id});
return resolve();
})
} catch(err) {
if (err.code === 404) {
log.audit({event: "flow.remove",id:id,error:"not_found"});
// TODO: this swap around of .code and .status isn't ideal
err.status = 404;
err.code = "not_found";
return reject(err);
} else {
log.audit({event: "flow.remove",id:id,error:err.code||"unexpected_error",message:err.toString()});
err.status = 400;
return reject(err);
}
}
});
},
/**
* Gets the safe credentials for a node
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @param {String} opts.type - the node type to return the credential information for
* @param {String} opts.id - the node id
* @return {Promise<Object>} - the safe credentials
* @memberof RED.flows
*/
getNodeCredentials: function(opts) {
return new Promise(function(resolve,reject) {
log.audit({event: "credentials.get",type:opts.type,id:opts.id});
var credentials = runtime.nodes.getCredentials(opts.id);
if (!credentials) {
return resolve({});
}
var definition = runtime.nodes.getCredentialDefinition(opts.type);
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] || '';
}
}
resolve(sendCredentials);
})
}
}

64
red/runtime-api/index.js Normal file
View File

@ -0,0 +1,64 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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.
**/
/**
* A user accessing the API
* @typedef User
* @type {object}
*/
/**
* @namespace RED
*/
var api = module.exports = {
init: function(runtime) {
api.flows.init(runtime);
api.nodes.init(runtime);
api.settings.init(runtime);
},
/**
* Auth module
*/
auth: require("./auth"),
/**
* Comms module
*/
comms: require("./comms"),
/**
* Flows module
*/
flows: require("./flows"),
/**
* Library module
*/
library: require("./library"),
/**
* Nodes module
*/
nodes: require("./nodes"),
/**
* Settings module
*/
settings: require("./settings")
}

View File

@ -0,0 +1,35 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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.
**/
/**
* @module red/library
*/
module.exports = {
/**
* Does something
*/
setEnty: function() {},
/**
* Does something
*/
getEntry: function() {},
/**
* Does something
*/
getEntries: function() {}
}

377
red/runtime-api/nodes.js Normal file
View File

@ -0,0 +1,377 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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.
**/
"use strict"
/**
* @namespace RED.nodes
*/
var runtime;
function putNode(node, enabled) {
var promise;
if (!node.err && node.enabled === enabled) {
promise = Promise.resolve(node);
} else {
if (enabled) {
promise = runtime.nodes.enableNode(node.id);
} else {
promise = runtime.nodes.disableNode(node.id);
}
}
return promise;
}
var api = module.exports = {
init: function(_runtime) {
runtime = _runtime;
},
/**
* Gets the info of an individual node set
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @param {String} opts.id - the id of the node set to return
* @return {Promise<NodeInfo>} - the node information
* @memberof RED.nodes
*/
getNodeInfo: function(opts) {
return new Promise(function(resolve,reject) {
var id = opts.id;
var result = redNodes.getNodeInfo(id);
if (result) {
runtime.log.audit({event: "nodes.info.get",id:id});
delete result.loaded;
return resolve(result);
} else {
runtime.log.audit({event: "nodes.info.get",id:id,error:"not_found"});
var err = new Error();
err.code = "not_found";
err.status = 404;
return reject(err);
}
})
},
/**
* Gets the list of node modules installed in the runtime
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @return {Promise<NodeList>} - the list of node modules
* @memberof RED.nodes
*/
getNodeList: function(opts) {
return new Promise(function(resolve,reject) {
runtime.log.audit({event: "nodes.list.get"});
return resolve(runtime.nodes.getNodeList());
})
},
/**
* Gets an individual node's html content
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @param {String} opts.id - the id of the node set to return
* @param {String} opts.lang - the locale language to return
* @return {Promise<String>} - the node html content
* @memberof RED.nodes
*/
getNodeConfig: function(opts) {
return new Promise(function(resolve,reject) {
var id = opts.id;
var lang = opts.lang;
var result = runtime.nodes.getNodeConfig(id,lang);
if (result) {
runtime.log.audit({event: "nodes.config.get",id:id});
return resolve(result);
} else {
runtime.log.audit({event: "nodes.config.get",id:id,error:"not_found"});
var err = new Error();
err.code = "not_found";
err.status = 404;
return reject(err);
}
});
},
/**
* Gets all node html content
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @param {String} opts.lang - the locale language to return
* @return {Promise<String>} - the node html content
* @memberof RED.nodes
*/
getNodeConfigs: function(opts) {
return new Promise(function(resolve,reject) {
runtime.log.audit({event: "nodes.configs.get"});
return resolve(runtime.nodes.getNodeConfigs(opts.lang));
});
},
/**
* Gets the info of a node module
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @param {String} opts.module - the id of the module to return
* @return {Promise<ModuleInfo>} - the node module info
* @memberof RED.nodes
*/
getModuleInfo: function(opts) {
return new Promise(function(resolve,reject) {
var module = opts.module;
var result = redNodes.getModuleInfo(module);
if (result) {
runtime.log.audit({event: "nodes.module.get",id:id});
return resolve(result);
} else {
runtime.log.audit({event: "nodes.module.get",id:id,error:"not_found"});
var err = new Error();
err.code = "not_found";
err.status = 404;
return reject(err);
}
})
},
/**
* Install a new module into the runtime
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @param {String} opts.module - the id of the module to install
* @param {String} opts.version - (optional) the version of the module to install
* @return {Promise<ModuleInfo>} - the node module info
* @memberof RED.nodes
*/
addModule: function(opts) {
return new Promise(function(resolve,reject) {
if (!runtime.settings.available()) {
runtime.log.audit({event: "nodes.install",error:"settings_unavailable"});
let err = new Error("Settings unavailable");
err.code = "settings_unavailable";
err.status = 400;
return reject(err);
}
if (opts.module) {
var existingModule = runtime.nodes.getModuleInfo(opts.module);
if (existingModule) {
if (!opts.version || existingModule.version === opts.version) {
runtime.log.audit({event: "nodes.install",module:opts.module, version:opts.version, error:"module_already_loaded"});
let err = new Error("Module already loaded");
err.code = "module_already_loaded";
err.status = 400;
return reject(err);
}
if (!module.local) {
runtime.log.audit({event: "nodes.install",module:opts.module, version:opts.version, error:"module_not_local"});
let err = new Error("Module not locally installed");
err.code = "module_not_local";
err.status = 400;
return reject(err);
}
}
runtime.nodes.installModule(opts.module,opts.version).then(function(info) {
runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version});
return resolve(info);
}).catch(function(err) {
if (err.code === 404) {
runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,error:"not_found"});
// TODO: code/status
err.status = 404;
} else if (err.code) {
err.status = 400;
runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,error:err.code});
} else {
err.status = 400;
runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,error:err.code||"unexpected_error",message:err.toString()});
}
return reject(err);
})
} else {
runtime.log.audit({event: "nodes.install",module:opts.module,error:"invalid_request"});
let err = new Error("Invalid request");
err.code = "invalid_request";
err.status = 400;
return reject(err);
}
});
},
/**
* Removes a module from the runtime
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @param {String} opts.module - the id of the module to remove
* @return {Promise} - resolves when complete
* @memberof RED.nodes
*/
removeModule: function(opts) {
return new Promise(function(resolve,reject) {
if (!runtime.settings.available()) {
runtime.log.audit({event: "nodes.install",error:"settings_unavailable"});
let err = new Error("Settings unavailable");
err.code = "settings_unavailable";
err.status = 400;
return reject(err);
}
var module = runtime.nodes.getModuleInfo(opts.module);
if (!module) {
runtime.log.audit({event: "nodes.remove",module:opts.module,error:"not_found"});
var err = new Error();
err.code = "not_found";
err.status = 404;
return reject(err);
}
try {
runtime.nodes.uninstallModule(opts.module).then(function() {
runtime.log.audit({event: "nodes.remove",module:opts.module});
resolve();
}).catch(function(err) {
err.status = 400;
runtime.log.audit({event: "nodes.remove",module:opts.module,error:err.code||"unexpected_error",message:err.toString()});
return reject(err);
})
} catch(err) {
runtime.log.audit({event: "nodes.remove",module:opts.module,error:err.code||"unexpected_error",message:err.toString()});
err.status = 400;
return reject(err);
}
});
},
/**
* Enables or disables a module in the runtime
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @param {String} opts.module - the id of the module to enable or disable
* @param {String} opts.enabled - whether the module should be enabled or disabled
* @return {Promise<ModuleInfo>} - the module info object
* @memberof RED.nodes
*/
setModuleState: function(opts) {
return new Promise(function(resolve,reject) {
if (!runtime.settings.available()) {
runtime.log.audit({event: "nodes.module.set",error:"settings_unavailable"});
let err = new Error("Settings unavailable");
err.code = "settings_unavailable";
err.status = 400;
return reject(err);
}
try {
var mod = opts.module;
var module = runtime.nodes.getModuleInfo(mod);
if (!module) {
runtime.log.audit({event: "nodes.module.set",module:mod,error:"not_found"});
var err = new Error();
err.code = "not_found";
err.status = 404;
return reject(err);
}
var nodes = module.nodes;
var promises = [];
for (var i = 0; i < nodes.length; ++i) {
promises.push(putNode(nodes[i],opts.enabled));
}
Promise.all(promises).then(function() {
return resolve(runtime.nodes.getModuleInfo(mod));
}).catch(function(err) {
err.status = 400;
return reject(err);
});
} catch(err) {
runtime.log.audit({event: "nodes.module.set",module:mod,enabled:opts.enabled,error:err.code||"unexpected_error",message:err.toString()});
err.status = 400;
return reject(err);
}
});
},
/**
* Enables or disables a n individual node-set in the runtime
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @param {String} opts.id - the id of the node-set to enable or disable
* @param {String} opts.enabled - whether the module should be enabled or disabled
* @return {Promise<ModuleInfo>} - the module info object
* @memberof RED.nodes
*/
setNodeSetState: function(opts) {
return new Promise(function(resolve,reject) {
if (!runtime.settings.available()) {
runtime.log.audit({event: "nodes.info.set",error:"settings_unavailable"});
let err = new Error("Settings unavailable");
err.code = "settings_unavailable";
err.status = 400;
return reject(err);
}
var id = opts.id;
var enabled = opts.enabled;
try {
var node = runtime.nodes.getNodeInfo(id);
if (!node) {
runtime.log.audit({event: "nodes.info.set",id:id,error:"not_found"});
var err = new Error();
err.code = "not_found";
err.status = 404;
return reject(err);
} else {
delete node.loaded;
putNode(node,enabled).then(function(result) {
runtime.log.audit({event: "nodes.info.set",id:id,enabled:enabled});
return resolve(result);
}).catch(function(err) {
runtime.log.audit({event: "nodes.info.set",id:id,enabled:enabled,error:err.code||"unexpected_error",message:err.toString()});
err.status = 400;
return reject(err);
});
}
} catch(err) {
runtime.log.audit({event: "nodes.info.set",id:id,enabled:enabled,error:err.code||"unexpected_error",message:err.toString()});
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
}
});
},
/**
* TODO: getModuleCatalogs
*/
getModuleCatalogs: function() {},
/**
* TODO: getModuleCatalog
*/
getModuleCatalog: function() {},
/**
* Gets the list of all icons available in the modules installed within the runtime
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @return {Promise<IconList>} - the list of all icons
* @memberof RED.nodes
*/
getIconList: function(opts) {
return new Promise(function(resolve,reject) {
runtime.log.audit({event: "nodes.icons.get"});
return resolve(runtime.nodes.getNodeIcons());
});
},
/**
* TODO: getIcon
*/
getIcon: function() {}
}

257
red/runtime-api/settings.js Normal file
View File

@ -0,0 +1,257 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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.
**/
/**
* @namespace RED.settings
*/
var util = require("util");
var runtime;
function extend(target, source) {
var keys = Object.keys(source);
var i = keys.length;
while(i--) {
var value = source[keys[i]]
var type = typeof value;
if (type === 'string' || type === 'number' || type === 'boolean' || Array.isArray(value)) {
target[keys[i]] = value;
} else if (value === null) {
if (target.hasOwnProperty(keys[i])) {
delete target[keys[i]];
}
} else {
// Object
if (target.hasOwnProperty(keys[i])) {
target[keys[i]] = extend(target[keys[i]],value);
} else {
target[keys[i]] = value;
}
}
}
return target;
}
function getUsername(userObj) {
var username = '__default';
if ( userObj && userObj.name ) {
username = userObj.name;
}
return username;
}
var api = module.exports = {
init: function(_runtime) {
runtime = _runtime;
},
/**
* Gets the runtime settings object
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @return {Promise<Object>} - the runtime settings
* @memberof RED.settings
*/
getRuntimeSettings: function(opts) {
return new Promise(function(resolve,reject) {
try {
var safeSettings = {
httpNodeRoot: runtime.settings.httpNodeRoot||"/",
version: runtime.settings.version,
user: opts.user
}
if (util.isArray(runtime.settings.paletteCategories)) {
safeSettings.paletteCategories = runtime.settings.paletteCategories;
}
if (runtime.settings.flowFilePretty) {
safeSettings.flowFilePretty = runtime.settings.flowFilePretty;
}
if (!runtime.nodes.paletteEditorEnabled()) {
safeSettings.editorTheme = safeSettings.editorTheme || {};
safeSettings.editorTheme.palette = safeSettings.editorTheme.palette || {};
safeSettings.editorTheme.palette.editable = false;
}
if (runtime.storage.projects) {
var activeProject = runtime.storage.projects.getActiveProject();
if (activeProject) {
safeSettings.project = activeProject;
} else if (runtime.storage.projects.flowFileExists()) {
safeSettings.files = {
flow: runtime.storage.projects.getFlowFilename(),
credentials: runtime.storage.projects.getCredentialsFilename()
}
}
safeSettings.git = {
globalUser: runtime.storage.projects.getGlobalGitUser()
}
}
safeSettings.flowEncryptionType = runtime.nodes.getCredentialKeyType();
runtime.settings.exportNodeSettings(safeSettings);
resolve(safeSettings);
}catch(err) {
console.log(err);
}
});
},
/**
* Gets an individual user's settings object
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @return {Promise<Object>} - the user settings
* @memberof RED.settings
*/
getUserSettings: function(opts) {
var username;
if (!opts.user || opts.user.anonymous) {
username = '_';
} else {
username = opts.user.username;
}
return Promise.resolve(runtime.settings.getUserSettings(username)||{});
},
/**
* Updates an individual user's settings object.
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @param {Object} opts.settings - the updates to the user settings
* @return {Promise<Object>} - the user settings
* @memberof RED.settings
*/
updateUserSettings: function(opts) {
var username;
if (!opts.user || opts.user.anonymous) {
username = '_';
} else {
username = opts.user.username;
}
return new Promise(function(resolve,reject) {
var currentSettings = runtime.settings.getUserSettings(username)||{};
currentSettings = extend(currentSettings, opts.settings);
try {
runtime.settings.setUserSettings(username, currentSettings).then(function() {
runtime.log.audit({event: "settings.update",username:username});
return resolve();
}).catch(function(err) {
runtime.log.audit({event: "settings.update",username:username,error:err.code||"unexpected_error",message:err.toString()});
err.status = 400;
return reject(err);
});
} catch(err) {
log.warn(log._("settings.user-not-available",{message:log._("settings.not-available")}));
log.audit({event: "settings.update",username:username,error:err.code||"unexpected_error",message:err.toString()});
err.status = 400;
return reject(err);
}
});
},
/**
* Gets a list of a user's ssh keys
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @return {Promise<Object>} - the user's ssh keys
* @memberof RED.settings
*/
getUserKeys: function(opts) {
return new Promise(function(resolve,reject) {
var username = getUsername(opts.user);
runtime.storage.projects.ssh.listSSHKeys(username).then(function(list) {
return resolve(list);
}).catch(function(err) {
err.status = 400;
return reject(err);
});
});
},
/**
* Gets a user's ssh public key
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @param {User} opts.id - the id of the key to return
* @return {Promise<String>} - the user's ssh public key
* @memberof RED.settings
*/
getUserKey: function(opts) {
return new Promise(function(resolve,reject) {
var username = getUsername(opts.user);
// console.log('username:', username);
runtime.storage.projects.ssh.getSSHKey(username, opts.id).then(function(data) {
if (data) {
return resolve(data);
} else {
var err = new Error("Key not found");
err.code = "not_found";
err.status = 404;
return reject(err);
}
}).catch(function(err) {
err.status = 400;
return reject(err);
});
});
},
/**
* Generates a new ssh key pair
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @param {User} opts.name - the id of the key to return
* @param {User} opts.password - (optional) the password for the key pair
* @param {User} opts.comment - (option) a comment to associate with the key pair
* @param {User} opts.size - (optional) the size of the key. Default: 2048
* @return {Promise<String>} - the id of the generated key
* @memberof RED.settings
*/
generateUserKey: function(opts) {
return new Promise(function(resolve,reject) {
var username = getUsername(opts.user);
runtime.storage.projects.ssh.generateSSHKey(username, opts).then(function(name) {
return resolve(name);
}).catch(function(err) {
err.status = 400;
return reject(err);
});
});
},
/**
* Deletes a user's ssh key pair
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @param {User} opts.id - the id of the key to delete
* @return {Promise} - resolves when deleted
* @memberof RED.settings
*/
removeUserKey: function(opts) {
return new Promise(function(resolve,reject) {
var username = getUsername(req.user);
runtime.storage.projects.ssh.deleteSSHKey(username, opts.id).then(function() {
return resolve();
}).catch(function(err) {
err.status = 400;
return reject(err);
});
});
}
}

View File

@ -107,6 +107,11 @@ function getSSHKey(username, name) {
function generateSSHKey(username, options) { function generateSSHKey(username, options) {
options = options || {}; options = options || {};
var name = options.name || ""; var name = options.name || "";
if (!/^[a-zA-Z0-9\-_]+$/.test(options.name)) {
var err = new Error("Invalid SSH Key name");
e.code = "invalid_key_name";
return Promise.reject(err);
}
return checkExistSSHKeyFiles(username, name) return checkExistSSHKeyFiles(username, name)
.then(function(result) { .then(function(result) {
if ( result ) { if ( result ) {