mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Compare commits
34 Commits
0.19.4
...
runtime-ap
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06abe63fb1 | ||
|
|
9d507b09ca | ||
|
|
9c4a712dc7 | ||
|
|
0835fdd0d1 | ||
|
|
522360dcb7 | ||
|
|
979713c4db | ||
|
|
e41d5c249f | ||
|
|
f82a779817 | ||
|
|
df8a8ea204 | ||
|
|
8957d33e49 | ||
|
|
28fe1e4c8f | ||
|
|
0c7f4e2168 | ||
|
|
b22956bd99 | ||
|
|
42516206d9 | ||
|
|
fc4edde6e6 | ||
|
|
54cc04fd96 | ||
|
|
80062b6a62 | ||
|
|
99af79fcf3 | ||
|
|
11d87205d7 | ||
|
|
5866d414ce | ||
|
|
9a972b0b8a | ||
|
|
e6aeeea8c1 | ||
|
|
5d064aa1d7 | ||
|
|
34832d5942 | ||
|
|
e3b1179a21 | ||
|
|
f94a36613c | ||
|
|
efc3cc24f4 | ||
|
|
b47f8aaf70 | ||
|
|
94ca4607bc | ||
|
|
2dab1d3e6e | ||
|
|
825b0fb22f | ||
|
|
1cdb039ea2 | ||
|
|
7409cb3abb | ||
|
|
e8e8f70c27 |
24
Gruntfile.js
24
Gruntfile.js
@@ -414,6 +414,24 @@ module.exports = function(grunt) {
|
||||
cwd: '<%= paths.dist %>/',
|
||||
src: ['node-red-<%= pkg.version %>/**']
|
||||
}
|
||||
},
|
||||
jsdoc : {
|
||||
runtimeAPI: {
|
||||
src: 'red/runtime-api/*.js',
|
||||
options: {
|
||||
destination: 'docs',
|
||||
configure: './jsdoc.json'
|
||||
}
|
||||
}
|
||||
},
|
||||
jsdoc2md: {
|
||||
runtimeAPI: {
|
||||
options: {
|
||||
separators: true
|
||||
},
|
||||
src: 'red/runtime-api/*.js',
|
||||
dest: 'docs/runtime-api.md'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -432,6 +450,8 @@ module.exports = function(grunt) {
|
||||
grunt.loadNpmTasks('grunt-jsonlint');
|
||||
grunt.loadNpmTasks('grunt-mocha-istanbul');
|
||||
grunt.loadNpmTasks('grunt-webdriver');
|
||||
grunt.loadNpmTasks('grunt-jsdoc');
|
||||
grunt.loadNpmTasks('grunt-jsdoc-to-markdown');
|
||||
|
||||
grunt.registerMultiTask('attachCopyright', function() {
|
||||
var files = this.data.src;
|
||||
@@ -514,4 +534,8 @@ module.exports = function(grunt) {
|
||||
grunt.registerTask('coverage',
|
||||
'Run Istanbul code test coverage task',
|
||||
['build','mocha_istanbul:all']);
|
||||
|
||||
grunt.registerTask('docs',
|
||||
'Generates API documentation',
|
||||
['jsdoc','jsdoc2md']);
|
||||
};
|
||||
|
||||
@@ -28,14 +28,24 @@ RED.comms = (function() {
|
||||
|
||||
function connectWS() {
|
||||
active = true;
|
||||
var path = location.hostname;
|
||||
var port = location.port;
|
||||
if (port.length !== 0) {
|
||||
path = path+":"+port;
|
||||
var wspath;
|
||||
|
||||
if (RED.settings.apiRootUrl) {
|
||||
var m = /^(https?):\/\/(.*)$/.exec(RED.settings.apiRootUrl);
|
||||
if (m) {
|
||||
console.log(m);
|
||||
wspath = "ws"+(m[1]==="https"?"s":"")+"://"+m[2]+"comms";
|
||||
}
|
||||
} else {
|
||||
var path = location.hostname;
|
||||
var port = location.port;
|
||||
if (port.length !== 0) {
|
||||
path = path+":"+port;
|
||||
}
|
||||
path = path+document.location.pathname;
|
||||
path = path+(path.slice(-1) == "/"?"":"/")+"comms";
|
||||
wspath = "ws"+(document.location.protocol=="https:"?"s":"")+"://"+path;
|
||||
}
|
||||
path = path+document.location.pathname;
|
||||
path = path+(path.slice(-1) == "/"?"":"/")+"comms";
|
||||
path = "ws"+(document.location.protocol=="https:"?"s":"")+"://"+path;
|
||||
|
||||
var auth_tokens = RED.settings.get("auth-tokens");
|
||||
pendingAuth = (auth_tokens!=null);
|
||||
@@ -48,7 +58,7 @@ RED.comms = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
ws = new WebSocket(path);
|
||||
ws = new WebSocket(wspath);
|
||||
ws.onopen = function() {
|
||||
reconnectAttempts = 0;
|
||||
if (errornotification) {
|
||||
|
||||
@@ -16,10 +16,13 @@
|
||||
|
||||
RED.i18n = (function() {
|
||||
|
||||
var apiRootUrl;
|
||||
|
||||
return {
|
||||
init: function(done) {
|
||||
init: function(options, done) {
|
||||
apiRootUrl = options.apiRootUrl||"";
|
||||
i18n.init({
|
||||
resGetPath: 'locales/__ns__?lng=__lng__',
|
||||
resGetPath: apiRootUrl+'locales/__ns__?lng=__lng__',
|
||||
dynamicLoad: false,
|
||||
load:'current',
|
||||
ns: {
|
||||
@@ -36,7 +39,7 @@ RED.i18n = (function() {
|
||||
}
|
||||
|
||||
},
|
||||
loadCatalog: function(namespace,done) {
|
||||
loadNodeCatalog: function(namespace,done) {
|
||||
var languageList = i18n.functions.toLanguages(i18n.detectLanguage());
|
||||
var toLoad = languageList.length;
|
||||
languageList.forEach(function(lang) {
|
||||
@@ -45,7 +48,7 @@ RED.i18n = (function() {
|
||||
"Accept":"application/json"
|
||||
},
|
||||
cache: false,
|
||||
url: 'locales/'+namespace+'?lng='+lang,
|
||||
url: apiRootUrl+'nodes/'+namespace+'/messages?lng='+lang,
|
||||
success: function(data) {
|
||||
i18n.addResourceBundle(lang,namespace,data);
|
||||
toLoad--;
|
||||
@@ -68,7 +71,7 @@ RED.i18n = (function() {
|
||||
"Accept":"application/json"
|
||||
},
|
||||
cache: false,
|
||||
url: 'locales/nodes?lng='+lang,
|
||||
url: apiRootUrl+'nodes/messages?lng='+lang,
|
||||
success: function(data) {
|
||||
var namespaces = Object.keys(data);
|
||||
namespaces.forEach(function(ns) {
|
||||
|
||||
@@ -13,489 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
(function() {
|
||||
|
||||
function appendNodeConfig(nodeConfig) {
|
||||
var m = /<!-- --- \[red-module:(\S+)\] --- -->/.exec(nodeConfig.trim());
|
||||
var moduleId;
|
||||
if (m) {
|
||||
moduleId = m[1];
|
||||
} else {
|
||||
moduleId = "unknown";
|
||||
}
|
||||
try {
|
||||
$("body").append(nodeConfig);
|
||||
} catch(err) {
|
||||
RED.notify(RED._("notification.errors.failedToAppendNode",{module:moduleId, error:err.toString()}),{
|
||||
type: "error",
|
||||
timeout: 10000
|
||||
});
|
||||
console.log("["+moduleId+"] "+err.toString());
|
||||
}
|
||||
$(function() {
|
||||
if ((window.location.hostname !== "localhost") && (window.location.hostname !== "127.0.0.1")) {
|
||||
document.title = document.title+" : "+window.location.hostname;
|
||||
}
|
||||
|
||||
function loadNodeList() {
|
||||
$.ajax({
|
||||
headers: {
|
||||
"Accept":"application/json"
|
||||
},
|
||||
cache: false,
|
||||
url: 'nodes',
|
||||
success: function(data) {
|
||||
RED.nodes.setNodeList(data);
|
||||
RED.i18n.loadNodeCatalogs(function() {
|
||||
loadIconList(loadNodes);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadIconList(done) {
|
||||
$.ajax({
|
||||
headers: {
|
||||
"Accept":"application/json"
|
||||
},
|
||||
cache: false,
|
||||
url: 'icons',
|
||||
success: function(data) {
|
||||
RED.nodes.setIconSets(data);
|
||||
if (done) {
|
||||
done();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadNodes() {
|
||||
$.ajax({
|
||||
headers: {
|
||||
"Accept":"text/html"
|
||||
},
|
||||
cache: false,
|
||||
url: 'nodes',
|
||||
success: function(data) {
|
||||
var configs = data.trim().split(/(?=<!-- --- \[red-module:\S+\] --- -->)/);
|
||||
configs.forEach(function(data) {
|
||||
appendNodeConfig(data);
|
||||
});
|
||||
|
||||
$("body").i18n();
|
||||
$("#palette > .palette-spinner").hide();
|
||||
$(".palette-scroll").removeClass("hide");
|
||||
$("#palette-search").removeClass("hide");
|
||||
loadFlows(function() {
|
||||
if (RED.settings.theme("projects.enabled",false)) {
|
||||
RED.projects.refresh(function(activeProject) {
|
||||
RED.sidebar.info.refresh()
|
||||
if (!activeProject) {
|
||||
// Projects enabled but no active project
|
||||
RED.menu.setDisabled('menu-item-projects-open',true);
|
||||
RED.menu.setDisabled('menu-item-projects-settings',true);
|
||||
if (activeProject === false) {
|
||||
// User previously decline the migration to projects.
|
||||
} else { // null/undefined
|
||||
RED.projects.showStartup();
|
||||
}
|
||||
}
|
||||
completeLoad();
|
||||
});
|
||||
} else {
|
||||
// Projects disabled by the user
|
||||
RED.sidebar.info.refresh()
|
||||
completeLoad();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadFlows(done) {
|
||||
$.ajax({
|
||||
headers: {
|
||||
"Accept":"application/json",
|
||||
},
|
||||
cache: false,
|
||||
url: 'flows',
|
||||
success: function(nodes) {
|
||||
if (nodes) {
|
||||
var currentHash = window.location.hash;
|
||||
RED.nodes.version(nodes.rev);
|
||||
RED.nodes.import(nodes.flows);
|
||||
RED.nodes.dirty(false);
|
||||
RED.view.redraw(true);
|
||||
if (/^#flow\/.+$/.test(currentHash)) {
|
||||
RED.workspaces.show(currentHash.substring(6));
|
||||
}
|
||||
}
|
||||
done();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function completeLoad() {
|
||||
var persistentNotifications = {};
|
||||
RED.comms.subscribe("notification/#",function(topic,msg) {
|
||||
var parts = topic.split("/");
|
||||
var notificationId = parts[1];
|
||||
if (notificationId === "runtime-deploy") {
|
||||
// handled in ui/deploy.js
|
||||
return;
|
||||
}
|
||||
if (notificationId === "node") {
|
||||
// handled below
|
||||
return;
|
||||
}
|
||||
if (notificationId === "project-update") {
|
||||
RED.nodes.clear();
|
||||
RED.history.clear();
|
||||
RED.view.redraw(true);
|
||||
RED.projects.refresh(function() {
|
||||
loadFlows(function() {
|
||||
var project = RED.projects.getActiveProject();
|
||||
var message = {
|
||||
"change-branch": RED._("notification.project.change-branch", {project: project.git.branches.local}),
|
||||
"merge-abort": RED._("notification.project.merge-abort"),
|
||||
"loaded": RED._("notification.project.loaded", {project: msg.project}),
|
||||
"updated": RED._("notification.project.updated", {project: msg.project}),
|
||||
"pull": RED._("notification.project.pull", {project: msg.project}),
|
||||
"revert": RED._("notification.project.revert", {project: msg.project}),
|
||||
"merge-complete": RED._("notification.project.merge-complete")
|
||||
}[msg.action];
|
||||
RED.notify("<p>"+message+"</p>");
|
||||
RED.sidebar.info.refresh()
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.text) {
|
||||
msg.default = msg.text;
|
||||
var text = RED._(msg.text,msg);
|
||||
var options = {
|
||||
type: msg.type,
|
||||
fixed: msg.timeout === undefined,
|
||||
timeout: msg.timeout,
|
||||
id: notificationId
|
||||
}
|
||||
if (notificationId === "runtime-state") {
|
||||
if (msg.error === "missing-types") {
|
||||
text+="<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
|
||||
if (!!RED.projects.getActiveProject()) {
|
||||
options.buttons = [
|
||||
{
|
||||
text: RED._("notification.label.manage-project-dep"),
|
||||
click: function() {
|
||||
persistentNotifications[notificationId].hideNotification();
|
||||
RED.projects.settings.show('deps');
|
||||
}
|
||||
}
|
||||
]
|
||||
// } else if (RED.settings.theme('palette.editable') !== false) {
|
||||
} else {
|
||||
options.buttons = [
|
||||
{
|
||||
text: RED._("common.label.close"),
|
||||
click: function() {
|
||||
persistentNotifications[notificationId].hideNotification();
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
} else if (msg.error === "credentials_load_failed") {
|
||||
if (RED.settings.theme("projects.enabled",false)) {
|
||||
// projects enabled
|
||||
if (RED.user.hasPermission("projects.write")) {
|
||||
options.buttons = [
|
||||
{
|
||||
text: RED._("notification.label.setup-cred"),
|
||||
click: function() {
|
||||
persistentNotifications[notificationId].hideNotification();
|
||||
RED.projects.showCredentialsPrompt();
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
} else {
|
||||
options.buttons = [
|
||||
{
|
||||
text: RED._("common.label.close"),
|
||||
click: function() {
|
||||
persistentNotifications[notificationId].hideNotification();
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
} else if (msg.error === "missing_flow_file") {
|
||||
if (RED.user.hasPermission("projects.write")) {
|
||||
options.buttons = [
|
||||
{
|
||||
text: RED._("notification.label.setup-project"),
|
||||
click: function() {
|
||||
persistentNotifications[notificationId].hideNotification();
|
||||
RED.projects.showFilesPrompt();
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
} else if (msg.error === "missing_package_file") {
|
||||
if (RED.user.hasPermission("projects.write")) {
|
||||
options.buttons = [
|
||||
{
|
||||
text: RED._("notification.label.create-default-package"),
|
||||
click: function() {
|
||||
persistentNotifications[notificationId].hideNotification();
|
||||
RED.projects.createDefaultPackageFile();
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
} else if (msg.error === "project_empty") {
|
||||
if (RED.user.hasPermission("projects.write")) {
|
||||
options.buttons = [
|
||||
{
|
||||
text: RED._("notification.label.no-thanks"),
|
||||
click: function() {
|
||||
persistentNotifications[notificationId].hideNotification();
|
||||
}
|
||||
},
|
||||
{
|
||||
text: RED._("notification.label.create-default-project"),
|
||||
click: function() {
|
||||
persistentNotifications[notificationId].hideNotification();
|
||||
RED.projects.createDefaultFileSet();
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
} else if (msg.error === "git_merge_conflict") {
|
||||
RED.nodes.clear();
|
||||
RED.sidebar.versionControl.refresh(true);
|
||||
if (RED.user.hasPermission("projects.write")) {
|
||||
options.buttons = [
|
||||
{
|
||||
text: RED._("notification.label.show-merge-conflicts"),
|
||||
click: function() {
|
||||
persistentNotifications[notificationId].hideNotification();
|
||||
RED.sidebar.versionControl.showLocalChanges();
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!persistentNotifications.hasOwnProperty(notificationId)) {
|
||||
persistentNotifications[notificationId] = RED.notify(text,options);
|
||||
} else {
|
||||
persistentNotifications[notificationId].update(text,options);
|
||||
}
|
||||
} else if (persistentNotifications.hasOwnProperty(notificationId)) {
|
||||
persistentNotifications[notificationId].close();
|
||||
delete persistentNotifications[notificationId];
|
||||
}
|
||||
});
|
||||
RED.comms.subscribe("status/#",function(topic,msg) {
|
||||
var parts = topic.split("/");
|
||||
var node = RED.nodes.node(parts[1]);
|
||||
if (node) {
|
||||
if (msg.hasOwnProperty("text")) {
|
||||
if (msg.text[0] !== ".") {
|
||||
msg.text = node._(msg.text.toString(),{defaultValue:msg.text.toString()});
|
||||
}
|
||||
}
|
||||
node.status = msg;
|
||||
node.dirty = true;
|
||||
RED.view.redraw();
|
||||
}
|
||||
});
|
||||
RED.comms.subscribe("notification/node/#",function(topic,msg) {
|
||||
var i,m;
|
||||
var typeList;
|
||||
var info;
|
||||
if (topic == "notification/node/added") {
|
||||
var addedTypes = [];
|
||||
msg.forEach(function(m) {
|
||||
var id = m.id;
|
||||
RED.nodes.addNodeSet(m);
|
||||
addedTypes = addedTypes.concat(m.types);
|
||||
RED.i18n.loadCatalog(id, function() {
|
||||
$.get('nodes/'+id, function(data) {
|
||||
appendNodeConfig(data);
|
||||
});
|
||||
});
|
||||
});
|
||||
if (addedTypes.length) {
|
||||
typeList = "<ul><li>"+addedTypes.join("</li><li>")+"</li></ul>";
|
||||
RED.notify(RED._("palette.event.nodeAdded", {count:addedTypes.length})+typeList,"success");
|
||||
}
|
||||
loadIconList();
|
||||
} else if (topic == "notification/node/removed") {
|
||||
for (i=0;i<msg.length;i++) {
|
||||
m = msg[i];
|
||||
info = RED.nodes.removeNodeSet(m.id);
|
||||
if (info.added) {
|
||||
typeList = "<ul><li>"+m.types.join("</li><li>")+"</li></ul>";
|
||||
RED.notify(RED._("palette.event.nodeRemoved", {count:m.types.length})+typeList,"success");
|
||||
}
|
||||
}
|
||||
loadIconList();
|
||||
} else if (topic == "notification/node/enabled") {
|
||||
if (msg.types) {
|
||||
info = RED.nodes.getNodeSet(msg.id);
|
||||
if (info.added) {
|
||||
RED.nodes.enableNodeSet(msg.id);
|
||||
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
|
||||
RED.notify(RED._("palette.event.nodeEnabled", {count:msg.types.length})+typeList,"success");
|
||||
} else {
|
||||
$.get('nodes/'+msg.id, function(data) {
|
||||
appendNodeConfig(data);
|
||||
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
|
||||
RED.notify(RED._("palette.event.nodeAdded", {count:msg.types.length})+typeList,"success");
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (topic == "notification/node/disabled") {
|
||||
if (msg.types) {
|
||||
RED.nodes.disableNodeSet(msg.id);
|
||||
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
|
||||
RED.notify(RED._("palette.event.nodeDisabled", {count:msg.types.length})+typeList,"success");
|
||||
}
|
||||
} else if (topic == "node/upgraded") {
|
||||
RED.notify(RED._("palette.event.nodeUpgraded", {module:msg.module,version:msg.version}),"success");
|
||||
RED.nodes.registry.setModulePendingUpdated(msg.module,msg.version);
|
||||
}
|
||||
// Refresh flow library to ensure any examples are updated
|
||||
RED.library.loadFlowLibrary();
|
||||
});
|
||||
}
|
||||
|
||||
function showAbout() {
|
||||
$.get('red/about', function(data) {
|
||||
var aboutHeader = '<div style="text-align:center;">'+
|
||||
'<img width="50px" src="red/images/node-red-icon.svg" />'+
|
||||
'</div>';
|
||||
|
||||
RED.sidebar.info.set(aboutHeader+marked(data));
|
||||
RED.sidebar.info.show();
|
||||
});
|
||||
}
|
||||
|
||||
function loadEditor() {
|
||||
var menuOptions = [];
|
||||
if (RED.settings.theme("projects.enabled",false)) {
|
||||
menuOptions.push({id:"menu-item-projects-menu",label:RED._("menu.label.projects"),options:[
|
||||
{id:"menu-item-projects-new",label:RED._("menu.label.projects-new"),disabled:false,onselect:"core:new-project"},
|
||||
{id:"menu-item-projects-open",label:RED._("menu.label.projects-open"),disabled:false,onselect:"core:open-project"},
|
||||
{id:"menu-item-projects-settings",label:RED._("menu.label.projects-settings"),disabled:false,onselect:"core:show-project-settings"}
|
||||
]});
|
||||
}
|
||||
|
||||
|
||||
menuOptions.push({id:"menu-item-view-menu",label:RED._("menu.label.view.view"),options:[
|
||||
// {id:"menu-item-view-show-grid",setting:"view-show-grid",label:RED._("menu.label.view.showGrid"),toggle:true,onselect:"core:toggle-show-grid"},
|
||||
// {id:"menu-item-view-snap-grid",setting:"view-snap-grid",label:RED._("menu.label.view.snapGrid"),toggle:true,onselect:"core:toggle-snap-grid"},
|
||||
// {id:"menu-item-status",setting:"node-show-status",label:RED._("menu.label.displayStatus"),toggle:true,onselect:"core:toggle-status", selected: true},
|
||||
//null,
|
||||
// {id:"menu-item-bidi",label:RED._("menu.label.view.textDir"),options:[
|
||||
// {id:"menu-item-bidi-default",toggle:"text-direction",label:RED._("menu.label.view.defaultDir"),selected: true, onselect:function(s) { if(s){RED.text.bidi.setTextDirection("")}}},
|
||||
// {id:"menu-item-bidi-ltr",toggle:"text-direction",label:RED._("menu.label.view.ltr"), onselect:function(s) { if(s){RED.text.bidi.setTextDirection("ltr")}}},
|
||||
// {id:"menu-item-bidi-rtl",toggle:"text-direction",label:RED._("menu.label.view.rtl"), onselect:function(s) { if(s){RED.text.bidi.setTextDirection("rtl")}}},
|
||||
// {id:"menu-item-bidi-auto",toggle:"text-direction",label:RED._("menu.label.view.auto"), onselect:function(s) { if(s){RED.text.bidi.setTextDirection("auto")}}}
|
||||
// ]},
|
||||
// null,
|
||||
{id:"menu-item-sidebar",label:RED._("menu.label.sidebar.show"),toggle:true,onselect:"core:toggle-sidebar", selected: true},
|
||||
null
|
||||
]});
|
||||
menuOptions.push(null);
|
||||
menuOptions.push({id:"menu-item-import",label:RED._("menu.label.import"),options:[
|
||||
{id:"menu-item-import-clipboard",label:RED._("menu.label.clipboard"),onselect:"core:show-import-dialog"},
|
||||
{id:"menu-item-import-library",label:RED._("menu.label.library"),options:[]}
|
||||
]});
|
||||
menuOptions.push({id:"menu-item-export",label:RED._("menu.label.export"),options:[
|
||||
{id:"menu-item-export-clipboard",label:RED._("menu.label.clipboard"),onselect:"core:show-export-dialog"},
|
||||
{id:"menu-item-export-library",label:RED._("menu.label.library"),disabled:true,onselect:"core:library-export"}
|
||||
]});
|
||||
menuOptions.push(null);
|
||||
menuOptions.push({id:"menu-item-search",label:RED._("menu.label.search"),onselect:"core:search"});
|
||||
menuOptions.push(null);
|
||||
menuOptions.push({id:"menu-item-config-nodes",label:RED._("menu.label.displayConfig"),onselect:"core:show-config-tab"});
|
||||
menuOptions.push({id:"menu-item-workspace",label:RED._("menu.label.flows"),options:[
|
||||
{id:"menu-item-workspace-add",label:RED._("menu.label.add"),onselect:"core:add-flow"},
|
||||
{id:"menu-item-workspace-edit",label:RED._("menu.label.rename"),onselect:"core:edit-flow"},
|
||||
{id:"menu-item-workspace-delete",label:RED._("menu.label.delete"),onselect:"core:remove-flow"}
|
||||
]});
|
||||
menuOptions.push({id:"menu-item-subflow",label:RED._("menu.label.subflows"), options: [
|
||||
{id:"menu-item-subflow-create",label:RED._("menu.label.createSubflow"),onselect:"core:create-subflow"},
|
||||
{id:"menu-item-subflow-convert",label:RED._("menu.label.selectionToSubflow"),disabled:true,onselect:"core:convert-to-subflow"},
|
||||
]});
|
||||
menuOptions.push(null);
|
||||
if (RED.settings.theme('palette.editable') !== false) {
|
||||
menuOptions.push({id:"menu-item-edit-palette",label:RED._("menu.label.editPalette"),onselect:"core:manage-palette"});
|
||||
menuOptions.push(null);
|
||||
}
|
||||
|
||||
menuOptions.push({id:"menu-item-user-settings",label:RED._("menu.label.settings"),onselect:"core:show-user-settings"});
|
||||
menuOptions.push(null);
|
||||
|
||||
menuOptions.push({id:"menu-item-keyboard-shortcuts",label:RED._("menu.label.keyboardShortcuts"),onselect:"core:show-help"});
|
||||
menuOptions.push({id:"menu-item-help",
|
||||
label: RED.settings.theme("menu.menu-item-help.label",RED._("menu.label.help")),
|
||||
href: RED.settings.theme("menu.menu-item-help.url","http://nodered.org/docs")
|
||||
});
|
||||
menuOptions.push({id:"menu-item-node-red-version", label:"v"+RED.settings.version, onselect: "core:show-about" });
|
||||
|
||||
|
||||
RED.view.init();
|
||||
RED.userSettings.init();
|
||||
RED.user.init();
|
||||
RED.library.init();
|
||||
RED.keyboard.init();
|
||||
RED.palette.init();
|
||||
if (RED.settings.theme('palette.editable') !== false) {
|
||||
RED.palette.editor.init();
|
||||
} else {
|
||||
console.log("Palette editor disabled");
|
||||
}
|
||||
|
||||
RED.sidebar.init();
|
||||
|
||||
if (RED.settings.theme("projects.enabled",false)) {
|
||||
RED.projects.init();
|
||||
} else {
|
||||
console.log("Projects disabled");
|
||||
}
|
||||
|
||||
RED.subflow.init();
|
||||
RED.workspaces.init();
|
||||
RED.clipboard.init();
|
||||
RED.search.init();
|
||||
RED.editor.init();
|
||||
RED.diff.init();
|
||||
|
||||
RED.menu.init({id:"btn-sidemenu",options: menuOptions});
|
||||
|
||||
RED.deploy.init(RED.settings.theme("deployButton",null));
|
||||
RED.notifications.init();
|
||||
|
||||
RED.actions.add("core:show-about", showAbout);
|
||||
RED.nodes.init();
|
||||
RED.comms.connect();
|
||||
|
||||
$("#main-container").show();
|
||||
$(".header-toolbar").show();
|
||||
|
||||
loadNodeList();
|
||||
}
|
||||
|
||||
$(function() {
|
||||
|
||||
if ((window.location.hostname !== "localhost") && (window.location.hostname !== "127.0.0.1")) {
|
||||
document.title = document.title+" : "+window.location.hostname;
|
||||
}
|
||||
|
||||
ace.require("ace/ext/language_tools");
|
||||
|
||||
RED.i18n.init(function() {
|
||||
RED.settings.init(loadEditor);
|
||||
})
|
||||
RED.init({
|
||||
apiRootUrl: ""
|
||||
});
|
||||
})();
|
||||
});
|
||||
|
||||
519
editor/js/red.js
519
editor/js/red.js
@@ -13,4 +13,521 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
var RED = {};
|
||||
var RED = (function() {
|
||||
|
||||
function appendNodeConfig(nodeConfig,done) {
|
||||
done = done || function(){};
|
||||
var m = /<!-- --- \[red-module:(\S+)\] --- -->/.exec(nodeConfig.trim());
|
||||
var moduleId;
|
||||
if (m) {
|
||||
moduleId = m[1];
|
||||
} else {
|
||||
moduleId = "unknown";
|
||||
}
|
||||
try {
|
||||
var hasDeferred = false;
|
||||
|
||||
var nodeConfigEls = $("<div>"+nodeConfig+"</div>");
|
||||
nodeConfigEls.find("script").each(function(i,el) {
|
||||
var srcUrl = $(el).attr('src');
|
||||
if (srcUrl && !/^\s*(https?:|\/|\.)/.test(srcUrl)) {
|
||||
$(el).remove();
|
||||
var newScript = document.createElement("script");
|
||||
newScript.onload = function() { $("body").append(nodeConfigEls); done() }
|
||||
$('body').append(newScript);
|
||||
newScript.src = RED.settings.apiRootUrl+srcUrl;
|
||||
hasDeferred = true;
|
||||
}
|
||||
})
|
||||
if (!hasDeferred) {
|
||||
$("body").append(nodeConfigEls);
|
||||
done();
|
||||
}
|
||||
} catch(err) {
|
||||
RED.notify(RED._("notification.errors.failedToAppendNode",{module:moduleId, error:err.toString()}),{
|
||||
type: "error",
|
||||
timeout: 10000
|
||||
});
|
||||
console.log("["+moduleId+"] "+err.toString());
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
function loadNodeList() {
|
||||
$.ajax({
|
||||
headers: {
|
||||
"Accept":"application/json"
|
||||
},
|
||||
cache: false,
|
||||
url: 'nodes',
|
||||
success: function(data) {
|
||||
RED.nodes.setNodeList(data);
|
||||
RED.i18n.loadNodeCatalogs(function() {
|
||||
loadIconList(loadNodes);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadIconList(done) {
|
||||
$.ajax({
|
||||
headers: {
|
||||
"Accept":"application/json"
|
||||
},
|
||||
cache: false,
|
||||
url: 'icons',
|
||||
success: function(data) {
|
||||
RED.nodes.setIconSets(data);
|
||||
if (done) {
|
||||
done();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadNodes() {
|
||||
$.ajax({
|
||||
headers: {
|
||||
"Accept":"text/html"
|
||||
},
|
||||
cache: false,
|
||||
url: 'nodes',
|
||||
success: function(data) {
|
||||
var configs = data.trim().split(/(?=<!-- --- \[red-module:\S+\] --- -->)/);
|
||||
var stepConfig = function() {
|
||||
if (configs.length === 0) {
|
||||
$("body").i18n();
|
||||
$("#palette > .palette-spinner").hide();
|
||||
$(".palette-scroll").removeClass("hide");
|
||||
$("#palette-search").removeClass("hide");
|
||||
loadFlows(function() {
|
||||
if (RED.settings.theme("projects.enabled",false)) {
|
||||
RED.projects.refresh(function(activeProject) {
|
||||
RED.sidebar.info.refresh()
|
||||
if (!activeProject) {
|
||||
// Projects enabled but no active project
|
||||
RED.menu.setDisabled('menu-item-projects-open',true);
|
||||
RED.menu.setDisabled('menu-item-projects-settings',true);
|
||||
if (activeProject === false) {
|
||||
// User previously decline the migration to projects.
|
||||
} else { // null/undefined
|
||||
RED.projects.showStartup();
|
||||
}
|
||||
}
|
||||
completeLoad();
|
||||
});
|
||||
} else {
|
||||
// Projects disabled by the user
|
||||
RED.sidebar.info.refresh()
|
||||
completeLoad();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
var config = configs.shift();
|
||||
appendNodeConfig(config,stepConfig);
|
||||
}
|
||||
}
|
||||
stepConfig();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadFlows(done) {
|
||||
$.ajax({
|
||||
headers: {
|
||||
"Accept":"application/json",
|
||||
},
|
||||
cache: false,
|
||||
url: 'flows',
|
||||
success: function(nodes) {
|
||||
if (nodes) {
|
||||
var currentHash = window.location.hash;
|
||||
RED.nodes.version(nodes.rev);
|
||||
RED.nodes.import(nodes.flows);
|
||||
RED.nodes.dirty(false);
|
||||
RED.view.redraw(true);
|
||||
if (/^#flow\/.+$/.test(currentHash)) {
|
||||
RED.workspaces.show(currentHash.substring(6));
|
||||
}
|
||||
}
|
||||
done();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function completeLoad() {
|
||||
var persistentNotifications = {};
|
||||
RED.comms.subscribe("notification/#",function(topic,msg) {
|
||||
var parts = topic.split("/");
|
||||
var notificationId = parts[1];
|
||||
if (notificationId === "runtime-deploy") {
|
||||
// handled in ui/deploy.js
|
||||
return;
|
||||
}
|
||||
if (notificationId === "node") {
|
||||
// handled below
|
||||
return;
|
||||
}
|
||||
if (notificationId === "project-update") {
|
||||
RED.nodes.clear();
|
||||
RED.history.clear();
|
||||
RED.view.redraw(true);
|
||||
RED.projects.refresh(function() {
|
||||
loadFlows(function() {
|
||||
var project = RED.projects.getActiveProject();
|
||||
var message = {
|
||||
"change-branch": RED._("notification.project.change-branch", {project: project.git.branches.local}),
|
||||
"merge-abort": RED._("notification.project.merge-abort"),
|
||||
"loaded": RED._("notification.project.loaded", {project: msg.project}),
|
||||
"updated": RED._("notification.project.updated", {project: msg.project}),
|
||||
"pull": RED._("notification.project.pull", {project: msg.project}),
|
||||
"revert": RED._("notification.project.revert", {project: msg.project}),
|
||||
"merge-complete": RED._("notification.project.merge-complete")
|
||||
}[msg.action];
|
||||
RED.notify("<p>"+message+"</p>");
|
||||
RED.sidebar.info.refresh()
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.text) {
|
||||
msg.default = msg.text;
|
||||
var text = RED._(msg.text,msg);
|
||||
var options = {
|
||||
type: msg.type,
|
||||
fixed: msg.timeout === undefined,
|
||||
timeout: msg.timeout,
|
||||
id: notificationId
|
||||
}
|
||||
if (notificationId === "runtime-state") {
|
||||
if (msg.error === "missing-types") {
|
||||
text+="<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
|
||||
if (!!RED.projects.getActiveProject()) {
|
||||
options.buttons = [
|
||||
{
|
||||
text: RED._("notification.label.manage-project-dep"),
|
||||
click: function() {
|
||||
persistentNotifications[notificationId].hideNotification();
|
||||
RED.projects.settings.show('deps');
|
||||
}
|
||||
}
|
||||
]
|
||||
// } else if (RED.settings.theme('palette.editable') !== false) {
|
||||
} else {
|
||||
options.buttons = [
|
||||
{
|
||||
text: RED._("common.label.close"),
|
||||
click: function() {
|
||||
persistentNotifications[notificationId].hideNotification();
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
} else if (msg.error === "credentials_load_failed") {
|
||||
if (RED.settings.theme("projects.enabled",false)) {
|
||||
// projects enabled
|
||||
if (RED.user.hasPermission("projects.write")) {
|
||||
options.buttons = [
|
||||
{
|
||||
text: "Setup credentials",
|
||||
click: function() {
|
||||
persistentNotifications[notificationId].hideNotification();
|
||||
RED.projects.showCredentialsPrompt();
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
} else {
|
||||
options.buttons = [
|
||||
{
|
||||
text: "Close",
|
||||
click: function() {
|
||||
persistentNotifications[notificationId].hideNotification();
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
} else if (msg.error === "missing_flow_file") {
|
||||
if (RED.user.hasPermission("projects.write")) {
|
||||
options.buttons = [
|
||||
{
|
||||
text: "Setup project files",
|
||||
click: function() {
|
||||
persistentNotifications[notificationId].hideNotification();
|
||||
RED.projects.showFilesPrompt();
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
} else if (msg.error === "missing_package_file") {
|
||||
if (RED.user.hasPermission("projects.write")) {
|
||||
options.buttons = [
|
||||
{
|
||||
text: "Create default package file",
|
||||
click: function() {
|
||||
persistentNotifications[notificationId].hideNotification();
|
||||
RED.projects.createDefaultPackageFile();
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
} else if (msg.error === "project_empty") {
|
||||
if (RED.user.hasPermission("projects.write")) {
|
||||
options.buttons = [
|
||||
{
|
||||
text: "No thanks",
|
||||
click: function() {
|
||||
persistentNotifications[notificationId].hideNotification();
|
||||
}
|
||||
},
|
||||
{
|
||||
text: "Create default project files",
|
||||
click: function() {
|
||||
persistentNotifications[notificationId].hideNotification();
|
||||
RED.projects.createDefaultFileSet();
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
} else if (msg.error === "git_merge_conflict") {
|
||||
RED.nodes.clear();
|
||||
RED.sidebar.versionControl.refresh(true);
|
||||
if (RED.user.hasPermission("projects.write")) {
|
||||
options.buttons = [
|
||||
{
|
||||
text: "Show merge conflicts",
|
||||
click: function() {
|
||||
persistentNotifications[notificationId].hideNotification();
|
||||
RED.sidebar.versionControl.showLocalChanges();
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!persistentNotifications.hasOwnProperty(notificationId)) {
|
||||
persistentNotifications[notificationId] = RED.notify(text,options);
|
||||
} else {
|
||||
persistentNotifications[notificationId].update(text,options);
|
||||
}
|
||||
} else if (persistentNotifications.hasOwnProperty(notificationId)) {
|
||||
persistentNotifications[notificationId].close();
|
||||
delete persistentNotifications[notificationId];
|
||||
}
|
||||
});
|
||||
RED.comms.subscribe("status/#",function(topic,msg) {
|
||||
var parts = topic.split("/");
|
||||
var node = RED.nodes.node(parts[1]);
|
||||
if (node) {
|
||||
if (msg.hasOwnProperty("text")) {
|
||||
if (msg.text[0] !== ".") {
|
||||
msg.text = node._(msg.text.toString(),{defaultValue:msg.text.toString()});
|
||||
}
|
||||
}
|
||||
node.status = msg;
|
||||
node.dirty = true;
|
||||
RED.view.redraw();
|
||||
}
|
||||
});
|
||||
RED.comms.subscribe("notification/node/#",function(topic,msg) {
|
||||
var i,m;
|
||||
var typeList;
|
||||
var info;
|
||||
if (topic == "notification/node/added") {
|
||||
var addedTypes = [];
|
||||
msg.forEach(function(m) {
|
||||
var id = m.id;
|
||||
RED.nodes.addNodeSet(m);
|
||||
addedTypes = addedTypes.concat(m.types);
|
||||
RED.i18n.loadNodeCatalog(id, function() {
|
||||
$.get('nodes/'+id, function(data) {
|
||||
appendNodeConfig(data);
|
||||
});
|
||||
});
|
||||
});
|
||||
if (addedTypes.length) {
|
||||
typeList = "<ul><li>"+addedTypes.join("</li><li>")+"</li></ul>";
|
||||
RED.notify(RED._("palette.event.nodeAdded", {count:addedTypes.length})+typeList,"success");
|
||||
}
|
||||
loadIconList();
|
||||
} else if (topic == "notification/node/removed") {
|
||||
for (i=0;i<msg.length;i++) {
|
||||
m = msg[i];
|
||||
info = RED.nodes.removeNodeSet(m.id);
|
||||
if (info.added) {
|
||||
typeList = "<ul><li>"+m.types.join("</li><li>")+"</li></ul>";
|
||||
RED.notify(RED._("palette.event.nodeRemoved", {count:m.types.length})+typeList,"success");
|
||||
}
|
||||
}
|
||||
loadIconList();
|
||||
} else if (topic == "notification/node/enabled") {
|
||||
if (msg.types) {
|
||||
info = RED.nodes.getNodeSet(msg.id);
|
||||
if (info.added) {
|
||||
RED.nodes.enableNodeSet(msg.id);
|
||||
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
|
||||
RED.notify(RED._("palette.event.nodeEnabled", {count:msg.types.length})+typeList,"success");
|
||||
} else {
|
||||
$.get('nodes/'+msg.id, function(data) {
|
||||
appendNodeConfig(data);
|
||||
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
|
||||
RED.notify(RED._("palette.event.nodeAdded", {count:msg.types.length})+typeList,"success");
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (topic == "notification/node/disabled") {
|
||||
if (msg.types) {
|
||||
RED.nodes.disableNodeSet(msg.id);
|
||||
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
|
||||
RED.notify(RED._("palette.event.nodeDisabled", {count:msg.types.length})+typeList,"success");
|
||||
}
|
||||
} else if (topic == "node/upgraded") {
|
||||
RED.notify(RED._("palette.event.nodeUpgraded", {module:msg.module,version:msg.version}),"success");
|
||||
RED.nodes.registry.setModulePendingUpdated(msg.module,msg.version);
|
||||
}
|
||||
// Refresh flow library to ensure any examples are updated
|
||||
RED.library.loadFlowLibrary();
|
||||
});
|
||||
}
|
||||
|
||||
function showAbout() {
|
||||
$.get('red/about', function(data) {
|
||||
var aboutHeader = '<div style="text-align:center;">'+
|
||||
'<img width="50px" src="red/images/node-red-icon.svg" />'+
|
||||
'</div>';
|
||||
|
||||
RED.sidebar.info.set(aboutHeader+marked(data));
|
||||
RED.sidebar.info.show();
|
||||
});
|
||||
}
|
||||
|
||||
function loadEditor() {
|
||||
var menuOptions = [];
|
||||
if (RED.settings.theme("projects.enabled",false)) {
|
||||
menuOptions.push({id:"menu-item-projects-menu",label:"Projects",options:[
|
||||
{id:"menu-item-projects-new",label:"New",disabled:false,onselect:"core:new-project"},
|
||||
{id:"menu-item-projects-open",label:"Open",disabled:false,onselect:"core:open-project"},
|
||||
{id:"menu-item-projects-settings",label:"Project Settings",disabled:false,onselect:"core:show-project-settings"}
|
||||
]});
|
||||
}
|
||||
|
||||
|
||||
menuOptions.push({id:"menu-item-view-menu",label:RED._("menu.label.view.view"),options:[
|
||||
// {id:"menu-item-view-show-grid",setting:"view-show-grid",label:RED._("menu.label.view.showGrid"),toggle:true,onselect:"core:toggle-show-grid"},
|
||||
// {id:"menu-item-view-snap-grid",setting:"view-snap-grid",label:RED._("menu.label.view.snapGrid"),toggle:true,onselect:"core:toggle-snap-grid"},
|
||||
// {id:"menu-item-status",setting:"node-show-status",label:RED._("menu.label.displayStatus"),toggle:true,onselect:"core:toggle-status", selected: true},
|
||||
//null,
|
||||
// {id:"menu-item-bidi",label:RED._("menu.label.view.textDir"),options:[
|
||||
// {id:"menu-item-bidi-default",toggle:"text-direction",label:RED._("menu.label.view.defaultDir"),selected: true, onselect:function(s) { if(s){RED.text.bidi.setTextDirection("")}}},
|
||||
// {id:"menu-item-bidi-ltr",toggle:"text-direction",label:RED._("menu.label.view.ltr"), onselect:function(s) { if(s){RED.text.bidi.setTextDirection("ltr")}}},
|
||||
// {id:"menu-item-bidi-rtl",toggle:"text-direction",label:RED._("menu.label.view.rtl"), onselect:function(s) { if(s){RED.text.bidi.setTextDirection("rtl")}}},
|
||||
// {id:"menu-item-bidi-auto",toggle:"text-direction",label:RED._("menu.label.view.auto"), onselect:function(s) { if(s){RED.text.bidi.setTextDirection("auto")}}}
|
||||
// ]},
|
||||
// null,
|
||||
{id:"menu-item-sidebar",label:RED._("menu.label.sidebar.show"),toggle:true,onselect:"core:toggle-sidebar", selected: true},
|
||||
null
|
||||
]});
|
||||
menuOptions.push(null);
|
||||
menuOptions.push({id:"menu-item-import",label:RED._("menu.label.import"),options:[
|
||||
{id:"menu-item-import-clipboard",label:RED._("menu.label.clipboard"),onselect:"core:show-import-dialog"},
|
||||
{id:"menu-item-import-library",label:RED._("menu.label.library"),options:[]}
|
||||
]});
|
||||
menuOptions.push({id:"menu-item-export",label:RED._("menu.label.export"),options:[
|
||||
{id:"menu-item-export-clipboard",label:RED._("menu.label.clipboard"),onselect:"core:show-export-dialog"},
|
||||
{id:"menu-item-export-library",label:RED._("menu.label.library"),disabled:true,onselect:"core:library-export"}
|
||||
]});
|
||||
menuOptions.push(null);
|
||||
menuOptions.push({id:"menu-item-search",label:RED._("menu.label.search"),onselect:"core:search"});
|
||||
menuOptions.push(null);
|
||||
menuOptions.push({id:"menu-item-config-nodes",label:RED._("menu.label.displayConfig"),onselect:"core:show-config-tab"});
|
||||
menuOptions.push({id:"menu-item-workspace",label:RED._("menu.label.flows"),options:[
|
||||
{id:"menu-item-workspace-add",label:RED._("menu.label.add"),onselect:"core:add-flow"},
|
||||
{id:"menu-item-workspace-edit",label:RED._("menu.label.rename"),onselect:"core:edit-flow"},
|
||||
{id:"menu-item-workspace-delete",label:RED._("menu.label.delete"),onselect:"core:remove-flow"}
|
||||
]});
|
||||
menuOptions.push({id:"menu-item-subflow",label:RED._("menu.label.subflows"), options: [
|
||||
{id:"menu-item-subflow-create",label:RED._("menu.label.createSubflow"),onselect:"core:create-subflow"},
|
||||
{id:"menu-item-subflow-convert",label:RED._("menu.label.selectionToSubflow"),disabled:true,onselect:"core:convert-to-subflow"},
|
||||
]});
|
||||
menuOptions.push(null);
|
||||
if (RED.settings.theme('palette.editable') !== false) {
|
||||
menuOptions.push({id:"menu-item-edit-palette",label:RED._("menu.label.editPalette"),onselect:"core:manage-palette"});
|
||||
menuOptions.push(null);
|
||||
}
|
||||
|
||||
menuOptions.push({id:"menu-item-user-settings",label:RED._("menu.label.settings"),onselect:"core:show-user-settings"});
|
||||
menuOptions.push(null);
|
||||
|
||||
menuOptions.push({id:"menu-item-keyboard-shortcuts",label:RED._("menu.label.keyboardShortcuts"),onselect:"core:show-help"});
|
||||
menuOptions.push({id:"menu-item-help",
|
||||
label: RED.settings.theme("menu.menu-item-help.label",RED._("menu.label.help")),
|
||||
href: RED.settings.theme("menu.menu-item-help.url","http://nodered.org/docs")
|
||||
});
|
||||
menuOptions.push({id:"menu-item-node-red-version", label:"v"+RED.settings.version, onselect: "core:show-about" });
|
||||
|
||||
|
||||
RED.view.init();
|
||||
RED.userSettings.init();
|
||||
RED.user.init();
|
||||
RED.library.init();
|
||||
RED.keyboard.init();
|
||||
RED.palette.init();
|
||||
if (RED.settings.theme('palette.editable') !== false) {
|
||||
RED.palette.editor.init();
|
||||
} else {
|
||||
console.log("Palette editor disabled");
|
||||
}
|
||||
|
||||
RED.sidebar.init();
|
||||
|
||||
if (RED.settings.theme("projects.enabled",false)) {
|
||||
RED.projects.init();
|
||||
} else {
|
||||
console.log("Projects disabled");
|
||||
}
|
||||
|
||||
RED.subflow.init();
|
||||
RED.workspaces.init();
|
||||
RED.clipboard.init();
|
||||
RED.search.init();
|
||||
RED.editor.init();
|
||||
RED.diff.init();
|
||||
|
||||
RED.menu.init({id:"btn-sidemenu",options: menuOptions});
|
||||
|
||||
RED.deploy.init(RED.settings.theme("deployButton",null));
|
||||
RED.notifications.init();
|
||||
|
||||
RED.actions.add("core:show-about", showAbout);
|
||||
RED.nodes.init();
|
||||
RED.comms.connect();
|
||||
|
||||
$("#main-container").show();
|
||||
$(".header-toolbar").show();
|
||||
|
||||
loadNodeList();
|
||||
}
|
||||
|
||||
var initialised = false;
|
||||
|
||||
function init(options) {
|
||||
if (initialised) {
|
||||
throw new Error("RED already initialised");
|
||||
}
|
||||
initialised = true;
|
||||
ace.require("ace/ext/language_tools");
|
||||
options = options || {};
|
||||
options.apiRootUrl = options.apiRootUrl || "";
|
||||
if (options.apiRootUrl && !/\/$/.test(options.apiRootUrl)) {
|
||||
options.apiRootUrl = options.apiRootUrl+"/";
|
||||
}
|
||||
RED.i18n.init(options, function() {
|
||||
RED.settings.init(options, loadEditor);
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
init: init
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -89,18 +89,22 @@ RED.settings = (function () {
|
||||
userSettings = data;
|
||||
}
|
||||
|
||||
var init = function (done) {
|
||||
var init = function (options, done) {
|
||||
var accessTokenMatch = /[?&]access_token=(.*?)(?:$|&)/.exec(window.location.search);
|
||||
if (accessTokenMatch) {
|
||||
var accessToken = accessTokenMatch[1];
|
||||
RED.settings.set("auth-tokens",{access_token: accessToken});
|
||||
window.location.search = "";
|
||||
}
|
||||
RED.settings.apiRootUrl = options.apiRootUrl;
|
||||
|
||||
$.ajaxSetup({
|
||||
beforeSend: function(jqXHR,settings) {
|
||||
// Only attach auth header for requests to relative paths
|
||||
if (!/^\s*(https?:|\/|\.)/.test(settings.url)) {
|
||||
if (options.apiRootUrl) {
|
||||
settings.url = options.apiRootUrl+settings.url;
|
||||
}
|
||||
var auth_tokens = RED.settings.get("auth-tokens");
|
||||
if (auth_tokens) {
|
||||
jqXHR.setRequestHeader("Authorization","Bearer "+auth_tokens.access_token);
|
||||
|
||||
@@ -18,6 +18,12 @@ RED.library = (function() {
|
||||
var exportToLibraryDialog;
|
||||
var elementPrefix = "node-input-";
|
||||
|
||||
|
||||
var _librarySaveConfirm = '<div id="node-dialog-library-save-confirm" class="hide"><form class="form-horizontal"><div style="text-align: center; padding-top: 30px;" id="node-dialog-library-save-content"></div></form></div>';
|
||||
var _librarySave = '<div id="node-dialog-library-save" class="hide"><form class="form-horizontal"><div class="form-row"><label for="node-dialog-library-save-folder" data-i18n="[append]library.folder"><i class="fa fa-folder-open"></i> </label><input type="text" id="node-dialog-library-save-folder" data-i18n="[placeholder]library.folderPlaceholder"></div><div class="form-row"><label for="node-dialog-library-save-filename" data-i18n="[append]library.filename"><i class="fa fa-file"></i> </label><input type="text" id="node-dialog-library-save-filename" data-i18n="[placeholder]library.filenamePlaceholder"></div></form></div>';
|
||||
var _libraryLookup = '<div id="node-dialog-library-lookup" class="hide"><form class="form-horizontal"><div class="form-row"><ul id="node-dialog-library-breadcrumbs" class="breadcrumb"><li class="active"><a href="#" data-i18n="[append]library.breadcrumb"></a></li></ul></div><div class="form-row"><div style="vertical-align: top; display: inline-block; height: 100%; width: 30%; padding-right: 20px;"><div id="node-select-library" style="border: 1px solid #999; width: 100%; height: 100%; overflow:scroll;"><ul></ul></div></div><div style="vertical-align: top; display: inline-block;width: 65%; height: 100%;"><div style="height: 100%; width: 95%;" class="node-text-editor" id="node-select-library-text" ></div></div></div></form></div>';
|
||||
|
||||
|
||||
function loadFlowLibrary() {
|
||||
$.getJSON("library/flows",function(data) {
|
||||
//console.log(data);
|
||||
@@ -410,6 +416,11 @@ RED.library = (function() {
|
||||
return {
|
||||
init: function() {
|
||||
|
||||
|
||||
$(_librarySave).appendTo(document.body);
|
||||
$(_librarySaveConfirm).appendTo(document.body);
|
||||
$(_libraryLookup).appendTo(document.body);
|
||||
|
||||
RED.actions.add("core:library-export",exportFlow);
|
||||
|
||||
RED.events.on("view:selection-changed",function(selection) {
|
||||
|
||||
@@ -459,7 +459,6 @@ RED.palette = (function() {
|
||||
}
|
||||
});
|
||||
RED.events.on('registry:node-set-disabled', function(nodeSet) {
|
||||
console.log(nodeSet);
|
||||
for (var j=0;j<nodeSet.types.length;j++) {
|
||||
hideNodeType(nodeSet.types[j]);
|
||||
var def = RED.nodes.getType(nodeSet.types[j]);
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
RED.subflow = (function() {
|
||||
|
||||
|
||||
var _subflowEditTemplate = '<script type="text/x-red" data-template-name="subflow"><div class="form-row"><label for="node-input-name" data-i18n="[append]editor:common.label.name"><i class="fa fa-tag"></i> </label><input type="text" id="node-input-name"></div></script>';
|
||||
var _subflowTemplateEditTemplate = '<script type="text/x-red" data-template-name="subflow-template"><div class="form-row"><i class="fa fa-tag"></i><label for="subflow-input-name" data-i18n="common.label.name"></label><input type="text" id="subflow-input-name"></div><div class="form-row" style="margin-bottom: 0px;"><label for="subflow-input-info" data-i18n="editor:subflow.info"></label><a href="https://help.github.com/articles/markdown-basics/" style="font-size: 0.8em; float: right;" data-i18n="[html]subflow.format"></a></div><div class="form-row node-text-editor-row"><div style="height: 250px;" class="node-text-editor" id="subflow-input-info-editor"></div></div><div class="form-row form-tips" id="subflow-dialog-user-count"></div></script>';
|
||||
|
||||
|
||||
function getSubflow() {
|
||||
return RED.nodes.subflow(RED.workspaces.active());
|
||||
}
|
||||
@@ -386,6 +390,10 @@ RED.subflow = (function() {
|
||||
|
||||
RED.actions.add("core:create-subflow",createSubflow);
|
||||
RED.actions.add("core:convert-to-subflow",convertToSubflow);
|
||||
|
||||
$(_subflowEditTemplate).appendTo(document.body);
|
||||
$(_subflowTemplateEditTemplate).appendTo(document.body);
|
||||
|
||||
}
|
||||
|
||||
function createSubflow() {
|
||||
|
||||
@@ -756,25 +756,25 @@ RED.utils = (function() {
|
||||
|
||||
function getNodeIcon(def,node) {
|
||||
if (def.category === 'config') {
|
||||
return "icons/node-red/cog.png"
|
||||
return RED.settings.apiRootUrl+"icons/node-red/cog.png"
|
||||
} else if (node && node.type === 'tab') {
|
||||
return "icons/node-red/subflow.png"
|
||||
return RED.settings.apiRootUrl+"icons/node-red/subflow.png"
|
||||
} else if (node && node.type === 'unknown') {
|
||||
return "icons/node-red/alert.png"
|
||||
return RED.settings.apiRootUrl+"icons/node-red/alert.png"
|
||||
} else if (node && node.icon) {
|
||||
var iconPath = separateIconPath(node.icon);
|
||||
if (isIconExists(iconPath)) {
|
||||
return "icons/" + node.icon;
|
||||
return RED.settings.apiRootUrl+"icons/" + node.icon;
|
||||
}
|
||||
}
|
||||
|
||||
var iconPath = getDefaultNodeIcon(def, node);
|
||||
if (def.category === 'subflows') {
|
||||
if (!isIconExists(iconPath)) {
|
||||
return "icons/node-red/subflow.png";
|
||||
return RED.settings.apiRootUrl+"icons/node-red/subflow.png";
|
||||
}
|
||||
}
|
||||
return "icons/"+iconPath.module+"/"+iconPath.file;
|
||||
return RED.settings.apiRootUrl+"icons/"+iconPath.module+"/"+iconPath.file;
|
||||
}
|
||||
|
||||
function getNodeLabel(node,defaultLabel) {
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</head>
|
||||
<body spellcheck="false">
|
||||
<div id="header">
|
||||
<span class="logo">{{#header.url}}<a href="{{.}}">{{/header.url}}{{#header.image}}<img src="{{.}}" title="{{version}}">{{/header.image}} <span>{{ header.title }}</span>{{#header.url}}</a>{{/header.url}}</span>
|
||||
<span class="logo">{{#header.url}}<a href="{{.}}">{{/header.url}}{{#header.image}}<img src="{{.}}">{{/header.image}} <span>{{ header.title }}</span>{{#header.url}}</a>{{/header.url}}</span>
|
||||
<ul class="header-toolbar hide">
|
||||
<li><a id="btn-sidemenu" class="button" data-toggle="dropdown" href="#"><i class="fa fa-bars"></i></a></li>
|
||||
</ul>
|
||||
@@ -83,68 +83,6 @@
|
||||
<div id="notifications"></div>
|
||||
<div id="dropTarget"><div data-i18n="[append]workspace.dropFlowHere"><br/><i class="fa fa-download"></i></div></div>
|
||||
|
||||
<div id="node-dialog-library-save-confirm" class="hide">
|
||||
<form class="form-horizontal">
|
||||
<div style="text-align: center; padding-top: 30px;" id="node-dialog-library-save-content">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="node-dialog-library-save" class="hide">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-row">
|
||||
<label for="node-dialog-library-save-folder" data-i18n="[append]library.folder"><i class="fa fa-folder-open"></i> </label>
|
||||
<input type="text" id="node-dialog-library-save-folder" data-i18n="[placeholder]library.folderPlaceholder">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-dialog-library-save-filename" data-i18n="[append]library.filename"><i class="fa fa-file"></i> </label>
|
||||
<input type="text" id="node-dialog-library-save-filename" data-i18n="[placeholder]library.filenamePlaceholder">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="node-dialog-library-lookup" class="hide">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-row">
|
||||
<ul id="node-dialog-library-breadcrumbs" class="breadcrumb">
|
||||
<li class="active"><a href="#" data-i18n="[append]library.breadcrumb"></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div style="vertical-align: top; display: inline-block; height: 100%; width: 30%; padding-right: 20px;">
|
||||
<div id="node-select-library" style="border: 1px solid #999; width: 100%; height: 100%; overflow:scroll;"><ul></ul></div>
|
||||
</div>
|
||||
<div style="vertical-align: top; display: inline-block;width: 65%; height: 100%;">
|
||||
<div style="height: 100%; width: 95%;" class="node-text-editor" id="node-select-library-text" ></div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<script type="text/x-red" data-template-name="subflow">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name" data-i18n="[append]editor:common.label.name"><i class="fa fa-tag"></i> </label>
|
||||
<input type="text" id="node-input-name">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-template-name="subflow-template">
|
||||
<div class="form-row">
|
||||
<i class="fa fa-tag"></i>
|
||||
<label for="subflow-input-name" data-i18n="common.label.name"></label><input type="text" id="subflow-input-name">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<i class="fa fa-folder-o"></i>
|
||||
<label for="subflow-input-category" data-i18n="editor:subflow.category"></label><select style="width: 250px;" id="subflow-input-category"></select><input style="display:none; margin-left: 10px; width:calc(100% - 250px)" type="text" id="subflow-input-custom-category">
|
||||
</div>
|
||||
<div class="form-row" style="margin-bottom: 0px;">
|
||||
<label for="subflow-input-info" data-i18n="editor:subflow.info"></label>
|
||||
<a href="https://help.github.com/articles/markdown-basics/" style="font-size: 0.8em; float: right;" data-i18n="[html]subflow.format"></a>
|
||||
</div>
|
||||
<div class="form-row node-text-editor-row">
|
||||
<div style="height: 250px;" class="node-text-editor" id="subflow-input-info-editor"></div>
|
||||
</div>
|
||||
<div class="form-row form-tips" id="subflow-dialog-user-count"></div>
|
||||
</script>
|
||||
<script src="vendor/vendor.js"></script>
|
||||
<script src="vendor/jsonata/jsonata.min.js"></script>
|
||||
<script src="vendor/ace/ace.js"></script>
|
||||
|
||||
22
jsdoc.json
Normal file
22
jsdoc.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"opts": {
|
||||
"template": "./node_modules/ink-docstrap/template",
|
||||
"destination": "./docs",
|
||||
"recurse": true
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
$(function() {
|
||||
RED.i18n.init(function() {
|
||||
RED.i18n.init({},function() {
|
||||
var options = {
|
||||
messageMouseEnter: function(sourceId) {
|
||||
window.opener.postMessage({event:"mouseEnter",id:sourceId},'*');
|
||||
|
||||
235
package.json
235
package.json
@@ -1,119 +1,124 @@
|
||||
{
|
||||
"name": "node-red",
|
||||
"version": "0.18.7",
|
||||
"description": "A visual tool for wiring the Internet of Things",
|
||||
"homepage": "http://nodered.org",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/node-red/node-red.git"
|
||||
},
|
||||
"main": "red/red.js",
|
||||
"scripts": {
|
||||
"start": "node red.js",
|
||||
"test": "grunt",
|
||||
"build": "grunt build"
|
||||
},
|
||||
"bin": {
|
||||
"node-red": "./red.js",
|
||||
"node-red-pi": "bin/node-red-pi"
|
||||
},
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Nick O'Leary"
|
||||
"name": "node-red",
|
||||
"version": "0.18.7",
|
||||
"description": "A visual tool for wiring the Internet of Things",
|
||||
"homepage": "http://nodered.org",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/node-red/node-red.git"
|
||||
},
|
||||
{
|
||||
"name": "Dave Conway-Jones"
|
||||
"main": "red/red.js",
|
||||
"scripts": {
|
||||
"start": "node red.js",
|
||||
"test": "grunt",
|
||||
"build": "grunt build",
|
||||
"docs": "grunt docs"
|
||||
},
|
||||
"bin": {
|
||||
"node-red": "./red.js",
|
||||
"node-red-pi": "bin/node-red-pi"
|
||||
},
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Nick O'Leary"
|
||||
},
|
||||
{
|
||||
"name": "Dave Conway-Jones"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"editor",
|
||||
"messaging",
|
||||
"iot",
|
||||
"flow"
|
||||
],
|
||||
"dependencies": {
|
||||
"ajv": "6.5.2",
|
||||
"basic-auth": "2.0.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"body-parser": "1.18.3",
|
||||
"cheerio": "0.22.0",
|
||||
"clone": "2.1.1",
|
||||
"cookie": "0.3.1",
|
||||
"cookie-parser": "1.4.3",
|
||||
"cors": "2.8.4",
|
||||
"cron": "1.3.0",
|
||||
"denque": "1.3.0",
|
||||
"express": "4.16.3",
|
||||
"express-session": "1.15.6",
|
||||
"fs-extra": "5.0.0",
|
||||
"fs.notify": "0.0.4",
|
||||
"hash-sum": "1.0.2",
|
||||
"i18next": "^11.4.0",
|
||||
"is-utf8": "0.2.1",
|
||||
"js-yaml": "3.12.0",
|
||||
"json-stringify-safe": "5.0.1",
|
||||
"jsonata": "1.5.4",
|
||||
"media-typer": "0.3.0",
|
||||
"memorystore": "1.6.0",
|
||||
"mime": "1.4.1",
|
||||
"mqtt": "2.18.3",
|
||||
"multer": "1.3.1",
|
||||
"mustache": "2.3.0",
|
||||
"node-red-node-email": "0.1.*",
|
||||
"node-red-node-feedparser": "^0.1.12",
|
||||
"node-red-node-rbe": "0.2.*",
|
||||
"node-red-node-twitter": "*",
|
||||
"nopt": "4.0.1",
|
||||
"oauth2orize": "1.11.0",
|
||||
"on-headers": "1.0.1",
|
||||
"passport": "0.4.0",
|
||||
"passport-http-bearer": "1.0.1",
|
||||
"passport-oauth2-client-password": "0.1.2",
|
||||
"raw-body": "2.3.3",
|
||||
"request": "2.87.0",
|
||||
"semver": "5.5.0",
|
||||
"sentiment": "2.1.0",
|
||||
"uglify-js": "3.4.5",
|
||||
"when": "3.7.8",
|
||||
"ws": "1.1.5",
|
||||
"xml2js": "0.4.19"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"bcrypt": "~2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chromedriver": "^2.40.0",
|
||||
"grunt": "~1.0.3",
|
||||
"grunt-chmod": "~1.1.1",
|
||||
"grunt-cli": "~1.2.0",
|
||||
"grunt-concurrent": "~2.3.1",
|
||||
"grunt-contrib-clean": "~1.1.0",
|
||||
"grunt-contrib-compress": "~1.4.0",
|
||||
"grunt-contrib-concat": "~1.0.1",
|
||||
"grunt-contrib-copy": "~1.0.0",
|
||||
"grunt-contrib-jshint": "~1.1.0",
|
||||
"grunt-contrib-uglify": "~3.3.0",
|
||||
"grunt-contrib-watch": "~1.1.0",
|
||||
"grunt-jsdoc": "^2.2.1",
|
||||
"grunt-jsdoc-to-markdown": "^4.0.0",
|
||||
"grunt-jsonlint": "~1.1.0",
|
||||
"grunt-mocha-istanbul": "5.0.2",
|
||||
"grunt-nodemon": "~0.4.2",
|
||||
"grunt-sass": "~2.0.0",
|
||||
"grunt-simple-mocha": "~0.4.1",
|
||||
"grunt-webdriver": "^2.0.3",
|
||||
"http-proxy": "^1.16.2",
|
||||
"ink-docstrap": "^1.3.2",
|
||||
"istanbul": "0.4.5",
|
||||
"mocha": "^5.2.0",
|
||||
"should": "^8.4.0",
|
||||
"sinon": "1.17.7",
|
||||
"stoppable": "^1.0.6",
|
||||
"supertest": "3.1.0",
|
||||
"wdio-chromedriver-service": "^0.1.3",
|
||||
"wdio-mocha-framework": "^0.6.2",
|
||||
"wdio-spec-reporter": "^0.1.5",
|
||||
"webdriverio": "^4.13.1",
|
||||
"node-red-node-test-helper": "^0.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"editor",
|
||||
"messaging",
|
||||
"iot",
|
||||
"flow"
|
||||
],
|
||||
"dependencies": {
|
||||
"ajv": "6.5.2",
|
||||
"basic-auth": "2.0.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"body-parser": "1.18.3",
|
||||
"cheerio": "0.22.0",
|
||||
"clone": "2.1.1",
|
||||
"cookie": "0.3.1",
|
||||
"cookie-parser": "1.4.3",
|
||||
"cors": "2.8.4",
|
||||
"cron": "1.3.0",
|
||||
"denque": "1.3.0",
|
||||
"express": "4.16.3",
|
||||
"express-session": "1.15.6",
|
||||
"fs-extra": "5.0.0",
|
||||
"fs.notify": "0.0.4",
|
||||
"hash-sum": "1.0.2",
|
||||
"i18next": "^11.4.0",
|
||||
"is-utf8": "0.2.1",
|
||||
"js-yaml": "3.12.0",
|
||||
"json-stringify-safe": "5.0.1",
|
||||
"jsonata": "1.5.4",
|
||||
"media-typer": "0.3.0",
|
||||
"memorystore": "1.6.0",
|
||||
"mqtt": "2.18.3",
|
||||
"multer": "1.3.1",
|
||||
"mustache": "2.3.0",
|
||||
"node-red-node-email": "0.1.*",
|
||||
"node-red-node-feedparser": "^0.1.12",
|
||||
"node-red-node-rbe": "0.2.*",
|
||||
"node-red-node-twitter": "*",
|
||||
"nopt": "4.0.1",
|
||||
"oauth2orize": "1.11.0",
|
||||
"on-headers": "1.0.1",
|
||||
"passport": "0.4.0",
|
||||
"passport-http-bearer": "1.0.1",
|
||||
"passport-oauth2-client-password": "0.1.2",
|
||||
"raw-body": "2.3.3",
|
||||
"request": "2.87.0",
|
||||
"semver": "5.5.0",
|
||||
"sentiment": "2.1.0",
|
||||
"uglify-js": "3.4.5",
|
||||
"when": "3.7.8",
|
||||
"ws": "1.1.5",
|
||||
"xml2js": "0.4.19"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"bcrypt": "~2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chromedriver": "^2.40.0",
|
||||
"grunt": "~1.0.3",
|
||||
"grunt-chmod": "~1.1.1",
|
||||
"grunt-cli": "~1.2.0",
|
||||
"grunt-concurrent": "~2.3.1",
|
||||
"grunt-contrib-clean": "~1.1.0",
|
||||
"grunt-contrib-compress": "~1.4.0",
|
||||
"grunt-contrib-concat": "~1.0.1",
|
||||
"grunt-contrib-copy": "~1.0.0",
|
||||
"grunt-contrib-jshint": "~1.1.0",
|
||||
"grunt-contrib-uglify": "~3.3.0",
|
||||
"grunt-contrib-watch": "~1.1.0",
|
||||
"grunt-jsonlint": "~1.1.0",
|
||||
"grunt-mocha-istanbul": "5.0.2",
|
||||
"grunt-nodemon": "~0.4.2",
|
||||
"grunt-sass": "~2.0.0",
|
||||
"grunt-simple-mocha": "~0.4.1",
|
||||
"grunt-webdriver": "^2.0.3",
|
||||
"http-proxy": "^1.16.2",
|
||||
"istanbul": "0.4.5",
|
||||
"mocha": "^5.2.0",
|
||||
"should": "^8.4.0",
|
||||
"sinon": "1.17.7",
|
||||
"stoppable": "^1.0.6",
|
||||
"supertest": "3.1.0",
|
||||
"wdio-chromedriver-service": "^0.1.3",
|
||||
"wdio-mocha-framework": "^0.6.2",
|
||||
"wdio-spec-reporter": "^0.1.5",
|
||||
"webdriverio": "^4.13.1",
|
||||
"node-red-node-test-helper": "^0.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,120 +14,28 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var log;
|
||||
var redNodes;
|
||||
var util;
|
||||
var settings;
|
||||
var apiUtils = require("../util");
|
||||
|
||||
var runtimeAPI;
|
||||
|
||||
function exportContextStore(scope,ctx, store, result, callback) {
|
||||
ctx.keys(store,function(err, keys) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
result[store] = {};
|
||||
var c = keys.length;
|
||||
if (c === 0) {
|
||||
callback(null);
|
||||
} else {
|
||||
keys.forEach(function(key) {
|
||||
ctx.get(key,store,function(err, v) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
if (scope !== 'global' ||
|
||||
store === redNodes.listContextStores().default ||
|
||||
!settings.hasOwnProperty("functionGlobalContext") ||
|
||||
!settings.functionGlobalContext.hasOwnProperty(key) ||
|
||||
settings.functionGlobalContext[key] !== v) {
|
||||
result[store][key] = util.encodeObject({msg:v});
|
||||
}
|
||||
c--;
|
||||
if (c === 0) {
|
||||
callback(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function(runtime) {
|
||||
redNodes = runtime.nodes;
|
||||
log = runtime.log;
|
||||
util = runtime.util;
|
||||
settings = runtime.settings;
|
||||
init: function(_runtimeAPI) {
|
||||
runtimeAPI = _runtimeAPI;
|
||||
},
|
||||
|
||||
get: function(req,res) {
|
||||
var scope = req.params.scope;
|
||||
var id = req.params.id;
|
||||
var key = req.params[0];
|
||||
var availableStores = redNodes.listContextStores();
|
||||
//{ default: 'default', stores: [ 'default', 'file' ] }
|
||||
var store = req.query['store'];
|
||||
if (store && availableStores.stores.indexOf(store) === -1) {
|
||||
return res.status(404).end();
|
||||
}
|
||||
var ctx;
|
||||
if (scope === 'global') {
|
||||
ctx = redNodes.getContext('global');
|
||||
} else if (scope === 'flow') {
|
||||
ctx = redNodes.getContext(id);
|
||||
} else if (scope === 'node') {
|
||||
var node = redNodes.getNode(id);
|
||||
if (node) {
|
||||
ctx = node.context();
|
||||
}
|
||||
}
|
||||
if (ctx) {
|
||||
if (key) {
|
||||
store = store || availableStores.default;
|
||||
ctx.get(key,store,function(err, v) {
|
||||
var encoded = util.encodeObject({msg:v});
|
||||
if (store !== availableStores.default) {
|
||||
encoded.store = store;
|
||||
}
|
||||
res.json(encoded);
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
var stores;
|
||||
if (!store) {
|
||||
stores = availableStores.stores;
|
||||
} else {
|
||||
stores = [store];
|
||||
}
|
||||
|
||||
var result = {};
|
||||
var c = stores.length;
|
||||
var errorReported = false;
|
||||
stores.forEach(function(store) {
|
||||
exportContextStore(scope,ctx,store,result,function(err) {
|
||||
if (err) {
|
||||
// TODO: proper error reporting
|
||||
if (!errorReported) {
|
||||
errorReported = true;
|
||||
res.end(400);
|
||||
}
|
||||
return;
|
||||
}
|
||||
c--;
|
||||
if (c === 0) {
|
||||
if (!errorReported) {
|
||||
if (stores.length > 1 && scope === 'global') {
|
||||
}
|
||||
res.json(result);
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
|
||||
}
|
||||
} else {
|
||||
res.json({});
|
||||
var opts = {
|
||||
user: req.user,
|
||||
scope: req.params.scope,
|
||||
id: req.params.id,
|
||||
key: req.params[0],
|
||||
store: req.query['store']
|
||||
}
|
||||
runtimeAPI.context.getValue(opts).then(function(result) {
|
||||
res.json(result);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,72 +14,56 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var log;
|
||||
var redNodes;
|
||||
var runtimeAPI;
|
||||
var apiUtils = require("../util");
|
||||
|
||||
module.exports = {
|
||||
init: function(runtime) {
|
||||
redNodes = runtime.nodes;
|
||||
log = runtime.log;
|
||||
init: function(_runtimeAPI) {
|
||||
runtimeAPI = _runtimeAPI;
|
||||
},
|
||||
get: function(req,res) {
|
||||
var id = req.params.id;
|
||||
var flow = redNodes.getFlow(id);
|
||||
if (flow) {
|
||||
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();
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id
|
||||
}
|
||||
runtimeAPI.flows.getFlow(opts).then(function(result) {
|
||||
return res.json(result);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
},
|
||||
post: function(req,res) {
|
||||
var flow = req.body;
|
||||
redNodes.addFlow(flow).then(function(id) {
|
||||
log.audit({event: "flow.add",id:id},req);
|
||||
res.json({id:id});
|
||||
var opts = {
|
||||
user: req.user,
|
||||
flow: req.body
|
||||
}
|
||||
runtimeAPI.flows.addFlow(opts).then(function(id) {
|
||||
return res.json({id:id});
|
||||
}).catch(function(err) {
|
||||
log.audit({event: "flow.add",error:err.code||"unexpected_error",message:err.toString()},req);
|
||||
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
|
||||
},
|
||||
put: function(req,res) {
|
||||
var id = req.params.id;
|
||||
var flow = req.body;
|
||||
try {
|
||||
redNodes.updateFlow(id,flow).then(function() {
|
||||
log.audit({event: "flow.update",id:id},req);
|
||||
res.json({id:id});
|
||||
}).catch(function(err) {
|
||||
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()});
|
||||
})
|
||||
} 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()});
|
||||
}
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id,
|
||||
flow: req.body
|
||||
}
|
||||
runtimeAPI.flows.updateFlow(opts).then(function(id) {
|
||||
return res.json({id:id});
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
},
|
||||
delete: function(req,res) {
|
||||
var id = req.params.id;
|
||||
try {
|
||||
redNodes.removeFlow(id).then(function() {
|
||||
log.audit({event: "flow.remove",id:id},req);
|
||||
res.status(204).end();
|
||||
})
|
||||
} 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()});
|
||||
}
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id
|
||||
}
|
||||
runtimeAPI.flows.deleteFlow(opts).then(function() {
|
||||
res.status(204).end();
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,72 +14,57 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var log;
|
||||
var redNodes;
|
||||
var runtimeAPI;
|
||||
var apiUtils = require("../util");
|
||||
|
||||
module.exports = {
|
||||
init: function(runtime) {
|
||||
redNodes = runtime.nodes;
|
||||
log = runtime.log;
|
||||
init: function(_runtimeAPI) {
|
||||
runtimeAPI = _runtimeAPI;
|
||||
},
|
||||
get: function(req,res) {
|
||||
var version = req.get("Node-RED-API-Version")||"v1";
|
||||
if (version === "v1") {
|
||||
log.audit({event: "flows.get",version:"v1"},req);
|
||||
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"});
|
||||
if (!/^v[12]$/.test(version)) {
|
||||
return 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) {
|
||||
var version = req.get("Node-RED-API-Version")||"v1";
|
||||
if (!/^v[12]$/.test(version)) {
|
||||
log.audit({event: "flows.set",version:version,error:"invalid_api_version"},req);
|
||||
res.status(400).json({code:"invalid_api_version", message:"Invalid API Version requested"});
|
||||
return;
|
||||
return res.status(400).json({code:"invalid_api_version", message:"Invalid API Version requested"});
|
||||
}
|
||||
var flows = req.body;
|
||||
var deploymentType = req.get("Node-RED-Deployment-Type")||"full";
|
||||
log.audit({event: "flows.set",type:deploymentType,version:version},req);
|
||||
if (deploymentType === 'reload') {
|
||||
redNodes.loadFlows().then(function(flowId) {
|
||||
if (version === "v1") {
|
||||
res.status(204).end();
|
||||
} else {
|
||||
res.json({rev:flowId});
|
||||
}
|
||||
}).catch(function(err) {
|
||||
log.warn(log._("api.flows.error-reload",{message:err.message}));
|
||||
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"});
|
||||
}
|
||||
}
|
||||
var opts = {
|
||||
user: req.user,
|
||||
deploymentType: req.get("Node-RED-Deployment-Type")||"full"
|
||||
}
|
||||
|
||||
if (opts.deploymentType !== 'reload') {
|
||||
if (version === "v1") {
|
||||
opts.flows = {flows: req.body}
|
||||
} else {
|
||||
opts.flows = req.body;
|
||||
}
|
||||
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});
|
||||
});
|
||||
}
|
||||
|
||||
runtimeAPI.flows.setFlows(opts).then(function(result) {
|
||||
if (version === "v1") {
|
||||
res.status(204).end();
|
||||
} else {
|
||||
res.json(result);
|
||||
}
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,11 +25,11 @@ var auth = require("../auth");
|
||||
var apiUtil = require("../util");
|
||||
|
||||
module.exports = {
|
||||
init: function(runtime) {
|
||||
flows.init(runtime);
|
||||
flow.init(runtime);
|
||||
nodes.init(runtime);
|
||||
context.init(runtime);
|
||||
init: function(runtimeAPI) {
|
||||
flows.init(runtimeAPI);
|
||||
flow.init(runtimeAPI);
|
||||
nodes.init(runtimeAPI);
|
||||
context.init(runtimeAPI);
|
||||
|
||||
var needsPermission = auth.needsPermission;
|
||||
|
||||
@@ -48,6 +48,8 @@ module.exports = {
|
||||
// Nodes
|
||||
adminApp.get("/nodes",needsPermission("nodes.read"),nodes.getAll,apiUtil.errorHandler);
|
||||
adminApp.post("/nodes",needsPermission("nodes.write"),nodes.post,apiUtil.errorHandler);
|
||||
adminApp.get(/\/nodes\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalogs,apiUtil.errorHandler);
|
||||
adminApp.get(/\/nodes\/((@[^\/]+\/)?[^\/]+\/[^\/]+)\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalog,apiUtil.errorHandler);
|
||||
adminApp.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.read"),nodes.getModule,apiUtil.errorHandler);
|
||||
adminApp.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.write"),nodes.putModule,apiUtil.errorHandler);
|
||||
adminApp.delete(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.write"),nodes.delete,apiUtil.errorHandler);
|
||||
|
||||
@@ -14,230 +14,160 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var when = require("when");
|
||||
var apiUtils = require("../util");
|
||||
var redNodes;
|
||||
var log;
|
||||
var settings;
|
||||
|
||||
var runtimeAPI;
|
||||
|
||||
module.exports = {
|
||||
init: function(runtime) {
|
||||
redNodes = runtime.nodes;
|
||||
log = runtime.log;
|
||||
settings = runtime.settings;
|
||||
init: function(_runtimeAPI) {
|
||||
runtimeAPI = _runtimeAPI;
|
||||
},
|
||||
getAll: function(req,res) {
|
||||
var opts = {
|
||||
user: req.user
|
||||
}
|
||||
if (req.get("accept") == "application/json") {
|
||||
log.audit({event: "nodes.list.get"},req);
|
||||
res.json(redNodes.getNodeList());
|
||||
runtimeAPI.nodes.getNodeList(opts).then(function(list) {
|
||||
res.json(list);
|
||||
})
|
||||
} else {
|
||||
var lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages());
|
||||
log.audit({event: "nodes.configs.get"},req);
|
||||
res.send(redNodes.getNodeConfigs(lang));
|
||||
opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages());
|
||||
runtimeAPI.nodes.getNodeConfigs(opts).then(function(configs) {
|
||||
res.send(configs);
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
post: function(req,res) {
|
||||
if (!settings.available()) {
|
||||
log.audit({event: "nodes.install",error:"settings_unavailable"},req);
|
||||
res.status(400).json({error:"settings_unavailable", message:"Settings unavailable"});
|
||||
return;
|
||||
var opts = {
|
||||
user: req.user,
|
||||
module: req.body.module,
|
||||
version: req.body.version
|
||||
}
|
||||
var node = req.body;
|
||||
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);
|
||||
}
|
||||
runtimeAPI.nodes.addModule(opts).then(function(info) {
|
||||
res.json(info);
|
||||
}).catch(function(err) {
|
||||
if (err.code === 404) {
|
||||
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()});
|
||||
}
|
||||
});
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
},
|
||||
|
||||
delete: function(req,res) {
|
||||
if (!settings.available()) {
|
||||
log.audit({event: "nodes.remove",error:"settings_unavailable"},req);
|
||||
res.status(400).json({error:"settings_unavailable", message:"Settings unavailable"});
|
||||
return;
|
||||
}
|
||||
var mod = req.params[0];
|
||||
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();
|
||||
}).catch(function(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()});
|
||||
});
|
||||
} 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()});
|
||||
var opts = {
|
||||
user: req.user,
|
||||
module: req.params[0]
|
||||
}
|
||||
runtimeAPI.nodes.removeModule(opts).then(function() {
|
||||
res.status(204).end();
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
},
|
||||
|
||||
getSet: function(req,res) {
|
||||
var id = req.params[0] + "/" + req.params[2];
|
||||
var result = null;
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params[0] + "/" + req.params[2]
|
||||
}
|
||||
if (req.get("accept") === "application/json") {
|
||||
result = redNodes.getNodeInfo(id);
|
||||
if (result) {
|
||||
log.audit({event: "nodes.info.get",id:id},req);
|
||||
delete result.loaded;
|
||||
runtimeAPI.nodes.getNodeInfo(opts).then(function(result) {
|
||||
res.send(result);
|
||||
} else {
|
||||
log.audit({event: "nodes.info.get",id:id,error:"not_found"},req);
|
||||
res.status(404).end();
|
||||
}
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
} else {
|
||||
var lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages());
|
||||
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();
|
||||
}
|
||||
opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages());
|
||||
runtimeAPI.nodes.getNodeConfig(opts).then(function(result) {
|
||||
return res.send(result);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
getModule: function(req,res) {
|
||||
var module = req.params[0];
|
||||
var result = redNodes.getModuleInfo(module);
|
||||
if (result) {
|
||||
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();
|
||||
var opts = {
|
||||
user: req.user,
|
||||
module: req.params[0]
|
||||
}
|
||||
runtimeAPI.nodes.getModuleInfo(opts).then(function(result) {
|
||||
res.send(result);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
},
|
||||
|
||||
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;
|
||||
if (!body.hasOwnProperty("enabled")) {
|
||||
log.audit({event: "nodes.info.set",error:"invalid_request"},req);
|
||||
res.status(400).json({error:"invalid_request", message:"Invalid request"});
|
||||
// log.audit({event: "nodes.module.set",error:"invalid_request"},req);
|
||||
res.status(400).json({code:"invalid_request", message:"Invalid request"});
|
||||
return;
|
||||
}
|
||||
var id = req.params[0] + "/" + req.params[2];
|
||||
try {
|
||||
var node = redNodes.getNodeInfo(id);
|
||||
var info;
|
||||
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()});
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params[0] + "/" + req.params[2],
|
||||
enabled: body.enabled
|
||||
}
|
||||
runtimeAPI.nodes.setNodeSetState(opts).then(function(result) {
|
||||
res.send(result);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
},
|
||||
|
||||
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;
|
||||
if (!body.hasOwnProperty("enabled")) {
|
||||
log.audit({event: "nodes.module.set",error:"invalid_request"},req);
|
||||
res.status(400).json({error:"invalid_request", message:"Invalid request"});
|
||||
// log.audit({event: "nodes.module.set",error:"invalid_request"},req);
|
||||
res.status(400).json({code:"invalid_request", message:"Invalid request"});
|
||||
return;
|
||||
}
|
||||
var mod = req.params[0];
|
||||
try {
|
||||
var module = redNodes.getModuleInfo(mod);
|
||||
if (!module) {
|
||||
log.audit({event: "nodes.module.set",module:mod,error:"not_found"},req);
|
||||
return res.status(404).end();
|
||||
}
|
||||
|
||||
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()});
|
||||
var opts = {
|
||||
user: req.user,
|
||||
module: req.params[0],
|
||||
enabled: body.enabled
|
||||
}
|
||||
runtimeAPI.nodes.setModuleState(opts).then(function(result) {
|
||||
res.send(result);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
getModuleCatalog: function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
module: req.params[0],
|
||||
lang: req.query.lng
|
||||
}
|
||||
runtimeAPI.nodes.getModuleCatalog(opts).then(function(result) {
|
||||
res.json(result);
|
||||
}).catch(function(err) {
|
||||
console.log(err.stack);
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
},
|
||||
|
||||
getModuleCatalogs: function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
lang: req.query.lng
|
||||
}
|
||||
runtimeAPI.nodes.getModuleCatalogs(opts).then(function(result) {
|
||||
res.json(result);
|
||||
}).catch(function(err) {
|
||||
console.log(err.stack);
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
},
|
||||
|
||||
getIcons: function(req,res) {
|
||||
log.audit({event: "nodes.icons.get"},req);
|
||||
res.json(redNodes.getNodeIcons());
|
||||
var opts = {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ var permissions = require("./permissions");
|
||||
var theme = require("../editor/theme");
|
||||
|
||||
var settings = null;
|
||||
var log = null
|
||||
var log = require("../../util").log; // TODO: separate module
|
||||
|
||||
|
||||
passport.use(strategies.bearerStrategy.BearerStrategy);
|
||||
@@ -36,13 +36,11 @@ var server = oauth2orize.createServer();
|
||||
|
||||
server.exchange(oauth2orize.exchange.password(strategies.passwordTokenExchange));
|
||||
|
||||
function init(runtime) {
|
||||
settings = runtime.settings;
|
||||
log = runtime.log;
|
||||
function init(_settings,storage) {
|
||||
settings = _settings;
|
||||
if (settings.adminAuth) {
|
||||
Users.init(settings.adminAuth);
|
||||
Tokens.init(settings.adminAuth,runtime.storage);
|
||||
strategies.init(runtime);
|
||||
Tokens.init(settings.adminAuth,storage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ var Users = require("./users");
|
||||
var Clients = require("./clients");
|
||||
var permissions = require("./permissions");
|
||||
|
||||
var log;
|
||||
var log = require("../../util").log; // TODO: separate module
|
||||
|
||||
var bearerStrategy = function (accessToken, done) {
|
||||
// is this a valid token?
|
||||
@@ -124,9 +124,6 @@ AnonymousStrategy.prototype.authenticate = function(req) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function(runtime) {
|
||||
log = runtime.log;
|
||||
},
|
||||
bearerStrategy: bearerStrategy,
|
||||
clientPasswordStrategy: clientPasswordStrategy,
|
||||
passwordTokenExchange: passwordTokenExchange,
|
||||
|
||||
@@ -15,45 +15,168 @@
|
||||
**/
|
||||
|
||||
var ws = require("ws");
|
||||
var log;
|
||||
|
||||
var log = require("../../util").log; // TODO: separate module
|
||||
var Tokens;
|
||||
var Users;
|
||||
var Permissions;
|
||||
|
||||
var server;
|
||||
var settings;
|
||||
var runtimeAPI;
|
||||
|
||||
var wsServer;
|
||||
var pendingConnections = [];
|
||||
var activeConnections = [];
|
||||
|
||||
var anonymousUser;
|
||||
|
||||
var retained = {};
|
||||
|
||||
var heartbeatTimer;
|
||||
var lastSentTime;
|
||||
|
||||
function handleStatus(event) {
|
||||
publish("status/"+event.id,event.status,true);
|
||||
}
|
||||
function handleRuntimeEvent(event) {
|
||||
log.trace("runtime event: "+JSON.stringify(event));
|
||||
publish("notification/"+event.id,event.payload||{},event.retain);
|
||||
}
|
||||
function init(_server,runtime) {
|
||||
function init(_server,_settings,_runtimeAPI) {
|
||||
server = _server;
|
||||
settings = runtime.settings;
|
||||
log = runtime.log;
|
||||
settings = _settings;
|
||||
runtimeAPI = _runtimeAPI;
|
||||
Tokens = require("../auth/tokens");
|
||||
Users = require("../auth/users");
|
||||
Permissions = require("../auth/permissions");
|
||||
|
||||
runtime.events.removeListener("node-status",handleStatus);
|
||||
runtime.events.on("node-status",handleStatus);
|
||||
}
|
||||
|
||||
runtime.events.removeListener("runtime-event",handleRuntimeEvent);
|
||||
runtime.events.on("runtime-event",handleRuntimeEvent);
|
||||
function generateSession(length) {
|
||||
var c = "ABCDEFGHIJKLMNOPQRSTUZWXYZabcdefghijklmnopqrstuvwxyz1234567890";
|
||||
var token = [];
|
||||
for (var i=0;i<length;i++) {
|
||||
token.push(c[Math.floor(Math.random()*c.length)]);
|
||||
}
|
||||
return token.join("");
|
||||
}
|
||||
|
||||
function CommsConnection(ws) {
|
||||
this.session = generateSession(32);
|
||||
this.ws = ws;
|
||||
this.stack = [];
|
||||
this.user = null;
|
||||
this.lastSentTime = 0;
|
||||
var self = this;
|
||||
|
||||
log.audit({event: "comms.open"});
|
||||
log.trace("comms.open "+self.session);
|
||||
var pendingAuth = (settings.adminAuth != null);
|
||||
|
||||
if (!pendingAuth) {
|
||||
addActiveConnection(self);
|
||||
}
|
||||
ws.on('close',function() {
|
||||
log.audit({event: "comms.close",user:self.user, session: self.session});
|
||||
log.trace("comms.close "+self.session);
|
||||
removeActiveConnection(self);
|
||||
});
|
||||
ws.on('message', function(data,flags) {
|
||||
var msg = null;
|
||||
try {
|
||||
msg = JSON.parse(data);
|
||||
} catch(err) {
|
||||
log.trace("comms received malformed message : "+err.toString());
|
||||
return;
|
||||
}
|
||||
if (!pendingAuth) {
|
||||
if (msg.subscribe) {
|
||||
self.subscribe(msg.subscribe);
|
||||
// handleRemoteSubscription(ws,msg.subscribe);
|
||||
}
|
||||
} else {
|
||||
var completeConnection = function(userScope,sendAck) {
|
||||
try {
|
||||
if (!userScope || !Permissions.hasPermission(userScope,"status.read")) {
|
||||
ws.send(JSON.stringify({auth:"fail"}));
|
||||
ws.close();
|
||||
} else {
|
||||
pendingAuth = false;
|
||||
addActiveConnection(self);
|
||||
if (sendAck) {
|
||||
ws.send(JSON.stringify({auth:"ok"}));
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
console.log(err.stack);
|
||||
// Just in case the socket closes before we attempt
|
||||
// to send anything.
|
||||
}
|
||||
}
|
||||
if (msg.auth) {
|
||||
Tokens.get(msg.auth).then(function(client) {
|
||||
if (client) {
|
||||
Users.get(client.user).then(function(user) {
|
||||
if (user) {
|
||||
self.user = user;
|
||||
log.audit({event: "comms.auth",user:self.user});
|
||||
completeConnection(client.scope,true);
|
||||
} else {
|
||||
log.audit({event: "comms.auth.fail"});
|
||||
completeConnection(null,false);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
log.audit({event: "comms.auth.fail"});
|
||||
completeConnection(null,false);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (anonymousUser) {
|
||||
log.audit({event: "comms.auth",user:anonymousUser});
|
||||
self.user = anonymousUser;
|
||||
completeConnection(anonymousUser.permissions,false);
|
||||
//TODO: duplicated code - pull non-auth message handling out
|
||||
if (msg.subscribe) {
|
||||
self.subscribe(msg.subscribe);
|
||||
}
|
||||
} else {
|
||||
log.audit({event: "comms.auth.fail"});
|
||||
completeConnection(null,false);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
ws.on('error', function(err) {
|
||||
log.warn(log._("comms.error",{message:err.toString()}));
|
||||
});
|
||||
}
|
||||
|
||||
CommsConnection.prototype.send = function(topic,data) {
|
||||
var self = this;
|
||||
if (topic && data) {
|
||||
this.stack.push({topic:topic,data:data});
|
||||
}
|
||||
if (!this._xmitTimer) {
|
||||
this._xmitTimer = setTimeout(function() {
|
||||
try {
|
||||
self.ws.send(JSON.stringify(self.stack));
|
||||
self.lastSentTime = Date.now();
|
||||
} catch(err) {
|
||||
removeActiveConnection(self);
|
||||
log.warn(log._("comms.error-send",{message:err.toString()}));
|
||||
}
|
||||
delete self._xmitTimer;
|
||||
self.stack = [];
|
||||
},50);
|
||||
}
|
||||
}
|
||||
|
||||
CommsConnection.prototype.subscribe = function(topic) {
|
||||
runtimeAPI.comms.subscribe({
|
||||
user: this.user,
|
||||
client: this,
|
||||
topic: topic
|
||||
})
|
||||
}
|
||||
|
||||
function start() {
|
||||
var Tokens = require("../auth/tokens");
|
||||
var Users = require("../auth/users");
|
||||
var Permissions = require("../auth/permissions");
|
||||
if (!settings.disableEditor) {
|
||||
Users.default().then(function(anonymousUser) {
|
||||
Users.default().then(function(_anonymousUser) {
|
||||
anonymousUser = _anonymousUser;
|
||||
var webSocketKeepAliveTime = settings.webSocketKeepAliveTime || 15000;
|
||||
var path = settings.httpAdminRoot || "/";
|
||||
path = (path.slice(0,1) != "/" ? "/":"") + path + (path.slice(-1) == "/" ? "":"/") + "comms";
|
||||
@@ -68,90 +191,10 @@ function start() {
|
||||
});
|
||||
|
||||
wsServer.on('connection',function(ws) {
|
||||
log.audit({event: "comms.open"});
|
||||
var pendingAuth = (settings.adminAuth != null);
|
||||
ws._nr_stack = [];
|
||||
ws._nr_ok2tx = true;
|
||||
|
||||
if (!pendingAuth) {
|
||||
activeConnections.push(ws);
|
||||
} else {
|
||||
pendingConnections.push(ws);
|
||||
}
|
||||
ws.on('close',function() {
|
||||
log.audit({event: "comms.close",user:ws.user});
|
||||
removeActiveConnection(ws);
|
||||
removePendingConnection(ws);
|
||||
});
|
||||
ws.on('message', function(data,flags) {
|
||||
var msg = null;
|
||||
try {
|
||||
msg = JSON.parse(data);
|
||||
} catch(err) {
|
||||
log.trace("comms received malformed message : "+err.toString());
|
||||
return;
|
||||
}
|
||||
if (!pendingAuth) {
|
||||
if (msg.subscribe) {
|
||||
handleRemoteSubscription(ws,msg.subscribe);
|
||||
}
|
||||
} else {
|
||||
var completeConnection = function(userScope,sendAck) {
|
||||
try {
|
||||
if (!userScope || !Permissions.hasPermission(userScope,"status.read")) {
|
||||
ws.send(JSON.stringify({auth:"fail"}));
|
||||
ws.close();
|
||||
} else {
|
||||
pendingAuth = false;
|
||||
removePendingConnection(ws);
|
||||
activeConnections.push(ws);
|
||||
if (sendAck) {
|
||||
ws.send(JSON.stringify({auth:"ok"}));
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
// Just in case the socket closes before we attempt
|
||||
// to send anything.
|
||||
}
|
||||
}
|
||||
if (msg.auth) {
|
||||
Tokens.get(msg.auth).then(function(client) {
|
||||
if (client) {
|
||||
Users.get(client.user).then(function(user) {
|
||||
if (user) {
|
||||
ws.user = user;
|
||||
log.audit({event: "comms.auth",user:ws.user});
|
||||
completeConnection(client.scope,true);
|
||||
} else {
|
||||
log.audit({event: "comms.auth.fail"});
|
||||
completeConnection(null,false);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
log.audit({event: "comms.auth.fail"});
|
||||
completeConnection(null,false);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (anonymousUser) {
|
||||
log.audit({event: "comms.auth",user:anonymousUser});
|
||||
completeConnection(anonymousUser.permissions,false);
|
||||
//TODO: duplicated code - pull non-auth message handling out
|
||||
if (msg.subscribe) {
|
||||
handleRemoteSubscription(ws,msg.subscribe);
|
||||
}
|
||||
} else {
|
||||
log.audit({event: "comms.auth.fail"});
|
||||
completeConnection(null,false);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
ws.on('error', function(err) {
|
||||
log.warn(log._("comms.error",{message:err.toString()}));
|
||||
});
|
||||
var commsConnection = new CommsConnection(ws);
|
||||
});
|
||||
|
||||
|
||||
wsServer.on('error', function(err) {
|
||||
log.warn(log._("comms.error-server",{message:err.toString()}));
|
||||
});
|
||||
@@ -161,7 +204,7 @@ function start() {
|
||||
heartbeatTimer = setInterval(function() {
|
||||
var now = Date.now();
|
||||
if (now-lastSentTime > webSocketKeepAliveTime) {
|
||||
publish("hb",lastSentTime);
|
||||
activeConnections.forEach(connection => connection.send("hb",lastSentTime));
|
||||
}
|
||||
}, webSocketKeepAliveTime);
|
||||
});
|
||||
@@ -179,63 +222,15 @@ function stop() {
|
||||
}
|
||||
}
|
||||
|
||||
function publish(topic,data,retain) {
|
||||
if (server) {
|
||||
if (retain) {
|
||||
retained[topic] = data;
|
||||
} else {
|
||||
delete retained[topic];
|
||||
}
|
||||
lastSentTime = Date.now();
|
||||
activeConnections.forEach(function(conn) {
|
||||
publishTo(conn,topic,data);
|
||||
});
|
||||
}
|
||||
function addActiveConnection(connection) {
|
||||
activeConnections.push(connection);
|
||||
runtimeAPI.comms.addConnection({client: connection});
|
||||
}
|
||||
|
||||
function publishTo(ws,topic,data) {
|
||||
if (topic && data) {
|
||||
ws._nr_stack.push({topic:topic,data:data});
|
||||
}
|
||||
if (ws._nr_ok2tx && (ws._nr_stack.length > 0)) {
|
||||
ws._nr_ok2tx = false;
|
||||
try {
|
||||
ws.send(JSON.stringify(ws._nr_stack));
|
||||
} catch(err) {
|
||||
removeActiveConnection(ws);
|
||||
removePendingConnection(ws);
|
||||
log.warn(log._("comms.error-send",{message:err.toString()}));
|
||||
}
|
||||
ws._nr_stack = [];
|
||||
setTimeout(function() {
|
||||
ws._nr_ok2tx = true;
|
||||
publishTo(ws);
|
||||
}, 50); // TODO: OK so a 50mS update rate should prob not be hard-coded
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function handleRemoteSubscription(ws,topic) {
|
||||
var re = new RegExp("^"+topic.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$");
|
||||
for (var t in retained) {
|
||||
if (re.test(t)) {
|
||||
publishTo(ws,t,retained[t]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeActiveConnection(ws) {
|
||||
function removeActiveConnection(connection) {
|
||||
for (var i=0;i<activeConnections.length;i++) {
|
||||
if (activeConnections[i] === ws) {
|
||||
if (activeConnections[i] === connection) {
|
||||
activeConnections.splice(i,1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
function removePendingConnection(ws) {
|
||||
for (var i=0;i<pendingConnections.length;i++) {
|
||||
if (pendingConnections[i] === ws) {
|
||||
pendingConnections.splice(i,1);
|
||||
runtimeAPI.comms.removeConnection({client:connection})
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -244,6 +239,5 @@ function removePendingConnection(ws) {
|
||||
module.exports = {
|
||||
init:init,
|
||||
start:start,
|
||||
stop:stop,
|
||||
publish:publish
|
||||
stop:stop
|
||||
}
|
||||
|
||||
@@ -14,39 +14,23 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var log;
|
||||
var api;
|
||||
var runtimeAPI;
|
||||
var apiUtils = require("../util");
|
||||
|
||||
module.exports = {
|
||||
init: function(runtime) {
|
||||
log = runtime.log;
|
||||
api = runtime.nodes;
|
||||
init: function(_runtimeAPI) {
|
||||
runtimeAPI = _runtimeAPI
|
||||
},
|
||||
get: function (req, res) {
|
||||
// TODO: It should verify the given node id is of the type specified -
|
||||
// but that would add a dependency from this module to the
|
||||
// registry module that knows about node types.
|
||||
var nodeType = req.params.type;
|
||||
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 opts = {
|
||||
user: req.user,
|
||||
type: req.params.type,
|
||||
id: req.params.id
|
||||
}
|
||||
var definition = api.getCredentialDefinition(nodeType);
|
||||
|
||||
var sendCredentials = {};
|
||||
for (var cred in definition) {
|
||||
if (definition.hasOwnProperty(cred)) {
|
||||
if (definition[cred].type == "password") {
|
||||
var key = 'has_' + cred;
|
||||
sendCredentials[key] = credentials[cred] != null && credentials[cred] !== '';
|
||||
continue;
|
||||
}
|
||||
sendCredentials[cred] = credentials[cred] || '';
|
||||
}
|
||||
}
|
||||
res.json(sendCredentials);
|
||||
runtimeAPI.flows.getNodeCredentials(opts).then(function(result) {
|
||||
res.json(result);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,31 +24,35 @@ var info = require("./settings");
|
||||
var auth = require("../auth");
|
||||
var nodes = require("../admin/nodes"); // TODO: move /icons into here
|
||||
var needsPermission;
|
||||
var runtime;
|
||||
var log;
|
||||
var runtimeAPI;
|
||||
var log = require("../../util").log; // TODO: separate module
|
||||
var i18n = require("../../util").i18n; // TODO: separate module
|
||||
|
||||
var apiUtil = require("../util");
|
||||
|
||||
var ensureRuntimeStarted = function(req,res,next) {
|
||||
if (!runtime.isStarted()) {
|
||||
log.error("Node-RED runtime not started");
|
||||
res.status(503).send("Not started");
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
runtimeAPI.isStarted().then( started => {
|
||||
if (!started) {
|
||||
log.error("Node-RED runtime not started");
|
||||
res.status(503).send("Not started");
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function(server, _runtime) {
|
||||
runtime = _runtime;
|
||||
log = runtime.log;
|
||||
init: function(server, settings, _runtimeAPI) {
|
||||
runtimeAPI = _runtimeAPI;
|
||||
needsPermission = auth.needsPermission;
|
||||
var settings = runtime.settings;
|
||||
if (!settings.disableEditor) {
|
||||
info.init(runtime);
|
||||
comms.init(server,runtime);
|
||||
info.init(runtimeAPI);
|
||||
comms.init(server,settings,runtimeAPI);
|
||||
|
||||
var ui = require("./ui");
|
||||
ui.init(runtime);
|
||||
|
||||
ui.init(runtimeAPI);
|
||||
|
||||
var editorApp = express();
|
||||
if (settings.requireHttps === true) {
|
||||
editorApp.enable('trust proxy');
|
||||
@@ -67,31 +71,31 @@ module.exports = {
|
||||
editorApp.get("/icons/:scope/:module/:icon",ui.icon);
|
||||
|
||||
var theme = require("./theme");
|
||||
theme.init(runtime);
|
||||
theme.init(settings);
|
||||
editorApp.use("/theme",theme.app());
|
||||
editorApp.use("/",ui.editorResources);
|
||||
|
||||
//Projects
|
||||
var projects = require("./projects");
|
||||
projects.init(runtime);
|
||||
projects.init(runtimeAPI);
|
||||
editorApp.use("/projects",projects.app());
|
||||
|
||||
// Locales
|
||||
var locales = require("./locales");
|
||||
locales.init(runtime);
|
||||
editorApp.get('/locales/nodes',locales.getAllNodes,apiUtil.errorHandler);
|
||||
locales.init(runtimeAPI);
|
||||
editorApp.get(/locales\/(.+)\/?$/,locales.get,apiUtil.errorHandler);
|
||||
|
||||
// Library
|
||||
var library = require("./library");
|
||||
library.init(editorApp,runtime);
|
||||
editorApp.post(new RegExp("/library/flows\/(.*)"),needsPermission("library.write"),library.post,apiUtil.errorHandler);
|
||||
library.init(runtimeAPI);
|
||||
editorApp.get("/library/flows",needsPermission("library.read"),library.getAll,apiUtil.errorHandler);
|
||||
editorApp.get(new RegExp("/library/flows\/(.*)"),needsPermission("library.read"),library.get,apiUtil.errorHandler);
|
||||
editorApp.get(/library\/([^\/]+)(?:$|\/(.*))/,needsPermission("library.read"),library.getEntry);
|
||||
editorApp.post(/library\/([^\/]+)\/(.*)/,needsPermission("library.write"),library.saveEntry);
|
||||
|
||||
|
||||
// Credentials
|
||||
var credentials = require("./credentials");
|
||||
credentials.init(runtime);
|
||||
credentials.init(runtimeAPI);
|
||||
editorApp.get('/credentials/:type/:id', needsPermission("credentials.read"),credentials.get,apiUtil.errorHandler);
|
||||
|
||||
// Settings
|
||||
@@ -100,18 +104,15 @@ module.exports = {
|
||||
editorApp.get("/settings/user",needsPermission("settings.read"),info.userSettings,apiUtil.errorHandler);
|
||||
// User Settings
|
||||
editorApp.post("/settings/user",needsPermission("settings.write"),info.updateUserSettings,apiUtil.errorHandler);
|
||||
|
||||
// SSH keys
|
||||
var sshkeys = require("./sshkeys");
|
||||
sshkeys.init(runtime);
|
||||
editorApp.use("/settings/user/keys",sshkeys.app());
|
||||
editorApp.use("/settings/user/keys",needsPermission("settings.write"),info.sshkeys());
|
||||
|
||||
return editorApp;
|
||||
}
|
||||
},
|
||||
start: function() {
|
||||
var catalogPath = path.resolve(path.join(__dirname,"locales"));
|
||||
return runtime.i18n.registerMessageCatalogs([
|
||||
return i18n.registerMessageCatalogs([
|
||||
{namespace: "editor", dir: catalogPath, file:"editor.json"},
|
||||
{namespace: "jsonata", dir: catalogPath, file:"jsonata.json"},
|
||||
{namespace: "infotips", dir: catalogPath, file:"infotips.json"}
|
||||
@@ -119,7 +120,5 @@ module.exports = {
|
||||
comms.start();
|
||||
});
|
||||
},
|
||||
stop: comms.stop,
|
||||
publish: comms.publish,
|
||||
registerLibrary: library.register
|
||||
stop: comms.stop
|
||||
}
|
||||
|
||||
@@ -13,149 +13,71 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var apiUtils = require("../util");
|
||||
var fs = require('fs');
|
||||
var fspath = require('path');
|
||||
var when = require('when');
|
||||
|
||||
var redApp = null;
|
||||
var storage;
|
||||
var log;
|
||||
var redNodes;
|
||||
var needsPermission = require("../auth").needsPermission;
|
||||
|
||||
function createLibrary(type) {
|
||||
if (redApp) {
|
||||
redApp.get(new RegExp("/library/"+type+"($|\/(.*))"),needsPermission("library.read"),function(req,res) {
|
||||
var path = req.params[1]||"";
|
||||
storage.getLibraryEntry(type,path).then(function(result) {
|
||||
log.audit({event: "library.get",type:type},req);
|
||||
if (typeof result === "string") {
|
||||
res.writeHead(200, {'Content-Type': 'text/plain'});
|
||||
res.write(result);
|
||||
res.end();
|
||||
} else {
|
||||
res.json(result);
|
||||
}
|
||||
}).catch(function(err) {
|
||||
if (err) {
|
||||
log.warn(log._("api.library.error-load-entry",{path:path,message:err.toString()}));
|
||||
if (err.code === 'forbidden') {
|
||||
log.audit({event: "library.get",type:type,error:"forbidden"},req);
|
||||
res.status(403).end();
|
||||
return;
|
||||
}
|
||||
}
|
||||
log.audit({event: "library.get",type:type,error:"not_found"},req);
|
||||
res.status(404).end();
|
||||
});
|
||||
});
|
||||
|
||||
redApp.post(new RegExp("/library/"+type+"\/(.*)"),needsPermission("library.write"),function(req,res) {
|
||||
var path = req.params[0];
|
||||
var meta = req.body;
|
||||
var text = meta.text;
|
||||
delete meta.text;
|
||||
|
||||
storage.saveLibraryEntry(type,path,meta,text).then(function() {
|
||||
log.audit({event: "library.set",type:type},req);
|
||||
res.status(204).end();
|
||||
}).catch(function(err) {
|
||||
log.warn(log._("api.library.error-save-entry",{path:path,message:err.toString()}));
|
||||
if (err.code === 'forbidden') {
|
||||
log.audit({event: "library.set",type:type,error:"forbidden"},req);
|
||||
res.status(403).end();
|
||||
return;
|
||||
}
|
||||
log.audit({event: "library.set",type:type,error:"unexpected_error",message:err.toString()},req);
|
||||
res.status(500).json({error:"unexpected_error", message:err.toString()});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
var runtimeAPI;
|
||||
|
||||
module.exports = {
|
||||
init: function(app,runtime) {
|
||||
redApp = app;
|
||||
log = runtime.log;
|
||||
storage = runtime.storage;
|
||||
redNodes = runtime.nodes;
|
||||
init: function(_runtimeAPI) {
|
||||
runtimeAPI = _runtimeAPI;
|
||||
},
|
||||
register: createLibrary,
|
||||
|
||||
getAll: function(req,res) {
|
||||
storage.getAllFlows().then(function(flows) {
|
||||
log.audit({event: "library.get.all",type:"flow"},req);
|
||||
var examples = redNodes.getNodeExampleFlows();
|
||||
if (examples) {
|
||||
flows.d = flows.d||{};
|
||||
flows.d._examples_ = redNodes.getNodeExampleFlows();
|
||||
}
|
||||
res.json(flows);
|
||||
var opts = {
|
||||
user: req.user,
|
||||
type: 'flows'
|
||||
}
|
||||
runtimeAPI.library.getEntries(opts).then(function(result) {
|
||||
res.json(result);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
});
|
||||
},
|
||||
get: function(req,res) {
|
||||
if (req.params[0].indexOf("_examples_/") === 0) {
|
||||
var m = /^_examples_\/(@.*?\/[^\/]+|[^\/]+)\/(.*)$/.exec(req.params[0]);
|
||||
if (m) {
|
||||
var module = m[1];
|
||||
var path = m[2];
|
||||
var fullPath = redNodes.getNodeExampleFlowPath(module,path);
|
||||
if (fullPath) {
|
||||
try {
|
||||
fs.statSync(fullPath);
|
||||
log.audit({event: "library.get",type:"flow",path:req.params[0]},req);
|
||||
return res.sendFile(fullPath,{
|
||||
headers:{
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
} catch(err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
// IF we get here, we didn't find the file
|
||||
log.audit({event: "library.get",type:"flow",path:req.params[0],error:"not_found"},req);
|
||||
return res.status(404).end();
|
||||
} else {
|
||||
storage.getFlow(req.params[0]).then(function(data) {
|
||||
// data is already a JSON string
|
||||
log.audit({event: "library.get",type:"flow",path:req.params[0]},req);
|
||||
res.set('Content-Type', 'application/json');
|
||||
res.send(data);
|
||||
}).catch(function(err) {
|
||||
if (err) {
|
||||
log.warn(log._("api.library.error-load-flow",{path:req.params[0],message:err.toString()}));
|
||||
if (err.code === 'forbidden') {
|
||||
log.audit({event: "library.get",type:"flow",path:req.params[0],error:"forbidden"},req);
|
||||
res.status(403).end();
|
||||
return;
|
||||
}
|
||||
}
|
||||
log.audit({event: "library.get",type:"flow",path:req.params[0],error:"not_found"},req);
|
||||
res.status(404).end();
|
||||
});
|
||||
getEntry: function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
type: req.params[0],
|
||||
path: req.params[1]||""
|
||||
}
|
||||
runtimeAPI.library.getEntry(opts).then(function(result) {
|
||||
if (typeof result === "string") {
|
||||
if (opts.type === 'flows') {
|
||||
res.writeHead(200, {'Content-Type': 'application/json'});
|
||||
} else {
|
||||
res.writeHead(200, {'Content-Type': 'text/plain'});
|
||||
}
|
||||
res.write(result);
|
||||
res.end();
|
||||
} else {
|
||||
res.json(result);
|
||||
}
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
});
|
||||
},
|
||||
post: function(req,res) {
|
||||
// if (req.params[0].indexOf("_examples_/") === 0) {
|
||||
// log.warn(log._("api.library.error-save-flow",{path:req.params[0],message:"forbidden"}));
|
||||
// log.audit({event: "library.set",type:"flow",path:req.params[0],error:"forbidden"},req);
|
||||
// return res.status(403).send({error:"unexpected_error", message:"forbidden"});
|
||||
// }
|
||||
var flow = JSON.stringify(req.body);
|
||||
storage.saveFlow(req.params[0],flow).then(function() {
|
||||
log.audit({event: "library.set",type:"flow",path:req.params[0]},req);
|
||||
saveEntry: function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
type: req.params[0],
|
||||
path: req.params[1]||""
|
||||
}
|
||||
// TODO: horrible inconsistencies between flows and all other types
|
||||
if (opts.type === "flows") {
|
||||
opts.meta = {};
|
||||
opts.body = JSON.stringify(req.body);
|
||||
} else {
|
||||
opts.meta = req.body;
|
||||
opts.body = opts.meta.text;
|
||||
delete opts.meta.text;
|
||||
}
|
||||
runtimeAPI.library.saveEntry(opts).then(function(result) {
|
||||
res.status(204).end();
|
||||
}).catch(function(err) {
|
||||
log.warn(log._("api.library.error-save-flow",{path:req.params[0],message:err.toString()}));
|
||||
if (err.code === 'forbidden') {
|
||||
log.audit({event: "library.set",type:"flow",path:req.params[0],error:"forbidden"},req);
|
||||
res.status(403).end();
|
||||
return;
|
||||
}
|
||||
log.audit({event: "library.set",type:"flow",path:req.params[0],error:"unexpected_error",message:err.toString()},req);
|
||||
res.status(500).send({error:"unexpected_error", message:err.toString()});
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,13 +16,14 @@
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
//var apiUtil = require('../util');
|
||||
var i18n;
|
||||
var redNodes;
|
||||
|
||||
var i18n = require("../../util").i18n; // TODO: separate module
|
||||
|
||||
var runtimeAPI;
|
||||
|
||||
module.exports = {
|
||||
init: function(runtime) {
|
||||
i18n = runtime.i18n;
|
||||
redNodes = runtime.nodes;
|
||||
init: function(_runtimeAPI) {
|
||||
runtimeAPI = _runtimeAPI;
|
||||
},
|
||||
get: function(req,res) {
|
||||
var namespace = req.params[0];
|
||||
@@ -36,17 +37,5 @@ module.exports = {
|
||||
res.json(catalog||{});
|
||||
});
|
||||
i18n.i.changeLanguage(prevLang);
|
||||
|
||||
},
|
||||
getAllNodes: function(req,res) {
|
||||
var lngs = req.query.lng;
|
||||
var nodeList = redNodes.getNodeList();
|
||||
var result = {};
|
||||
nodeList.forEach(function(n) {
|
||||
if (n.module !== "node-red") {
|
||||
result[n.id] = i18n.i.getResourceBundle(lngs, n.id)||{};
|
||||
}
|
||||
});
|
||||
res.json(result);
|
||||
}
|
||||
}
|
||||
|
||||
507
red/api/editor/projects.js
Normal file
507
red/api/editor/projects.js
Normal file
@@ -0,0 +1,507 @@
|
||||
/**
|
||||
* 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.
|
||||
**/
|
||||
|
||||
var express = require("express");
|
||||
var apiUtils = require("../util");
|
||||
|
||||
var runtimeAPI;
|
||||
var needsPermission = require("../auth").needsPermission;
|
||||
|
||||
module.exports = {
|
||||
init: function(_runtimeAPI) {
|
||||
runtimeAPI = _runtimeAPI;
|
||||
},
|
||||
app: function() {
|
||||
var app = express();
|
||||
|
||||
app.use(function(req,res,next) {
|
||||
runtimeAPI.projects.available().then(function(available) {
|
||||
if (!available) {
|
||||
res.status(404).end();
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Projects
|
||||
|
||||
// List all projects
|
||||
app.get("/", needsPermission("projects.read"), function(req,res) {
|
||||
var opts = {
|
||||
user: req.user
|
||||
}
|
||||
runtimeAPI.projects.listProjects(opts).then(function(result) {
|
||||
res.json(result);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
});
|
||||
});
|
||||
|
||||
// Create project
|
||||
app.post("/", needsPermission("projects.write"), function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
project: req.body
|
||||
}
|
||||
runtimeAPI.projects.createProject(opts).then(function(result) {
|
||||
res.json(result);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
});
|
||||
});
|
||||
|
||||
// Update a project
|
||||
app.put("/:id", needsPermission("projects.write"), function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id,
|
||||
project: req.body
|
||||
}
|
||||
|
||||
if (req.body.active) {
|
||||
runtimeAPI.projects.setActiveProject(opts).then(function() {
|
||||
res.redirect(303,req.baseUrl + '/');
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
} else if (req.body.initialise) {
|
||||
runtimeAPI.projects.initialiseProject(opts).then(function() {
|
||||
res.redirect(303,req.baseUrl + '/'+ req.params.id);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
} else if (req.body.hasOwnProperty('credentialSecret') ||
|
||||
req.body.hasOwnProperty('description') ||
|
||||
req.body.hasOwnProperty('dependencies')||
|
||||
req.body.hasOwnProperty('summary') ||
|
||||
req.body.hasOwnProperty('files') ||
|
||||
req.body.hasOwnProperty('git')) {
|
||||
runtimeAPI.projects.updateProject(opts).then(function() {
|
||||
res.redirect(303,req.baseUrl + '/'+ req.params.id);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
} else {
|
||||
res.status(400).json({error:"unexpected_error", message:"invalid_request"});
|
||||
}
|
||||
});
|
||||
|
||||
// Get project metadata
|
||||
app.get("/:id", needsPermission("projects.read"), function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id
|
||||
}
|
||||
runtimeAPI.projects.getProject(opts).then(function(data) {
|
||||
if (data) {
|
||||
res.json(data);
|
||||
} else {
|
||||
res.status(404).end();
|
||||
}
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
});
|
||||
|
||||
// Delete project
|
||||
app.delete("/:id", needsPermission("projects.write"), function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id
|
||||
}
|
||||
runtimeAPI.projects.deleteProject(opts).then(function() {
|
||||
res.status(204).end();
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
// Get project status - files, commit counts, branch info
|
||||
app.get("/:id/status", needsPermission("projects.read"), function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id,
|
||||
remote: req.query.remote
|
||||
}
|
||||
runtimeAPI.projects.getStatus(opts).then(function(data){
|
||||
if (data) {
|
||||
res.json(data);
|
||||
} else {
|
||||
res.status(404).end();
|
||||
}
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
// Project file listing
|
||||
app.get("/:id/files", needsPermission("projects.read"), function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id
|
||||
}
|
||||
runtimeAPI.projects.getFiles(opts).then(function(data) {
|
||||
res.json(data);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
|
||||
// Get file content in a given tree (index/stage)
|
||||
app.get("/:id/files/:treeish/*", needsPermission("projects.read"), function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id,
|
||||
path: req.params[0],
|
||||
tree: req.params.treeish
|
||||
}
|
||||
runtimeAPI.projects.getFile(opts).then(function(data) {
|
||||
res.json({content:data});
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
});
|
||||
|
||||
// Revert a file
|
||||
app.delete("/:id/files/_/*", needsPermission("projects.write"), function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id,
|
||||
path: req.params[0]
|
||||
}
|
||||
|
||||
runtimeAPI.projects.revertFile(opts).then(function() {
|
||||
res.status(204).end();
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
});
|
||||
|
||||
// Stage a file
|
||||
app.post("/:id/stage/*", needsPermission("projects.write"), function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id,
|
||||
path: req.params[0]
|
||||
}
|
||||
runtimeAPI.projects.stageFile(opts).then(function() {
|
||||
res.redirect(303,req.baseUrl+"/"+opts.id+"/status");
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
});
|
||||
|
||||
// Stage multiple files
|
||||
app.post("/:id/stage", needsPermission("projects.write"), function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id,
|
||||
path: req.body.files
|
||||
}
|
||||
runtimeAPI.projects.stageFile(opts).then(function() {
|
||||
res.redirect(303,req.baseUrl+"/"+opts.id+"/status");
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
});
|
||||
|
||||
// Commit changes
|
||||
app.post("/:id/commit", needsPermission("projects.write"), function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id,
|
||||
message: req.body.message
|
||||
}
|
||||
runtimeAPI.projects.commit(opts).then(function() {
|
||||
res.redirect(303,req.baseUrl+"/"+opts.id+"/status");
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
});
|
||||
|
||||
// Unstage a file
|
||||
app.delete("/:id/stage/*", needsPermission("projects.write"), function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id,
|
||||
path: req.params[0]
|
||||
}
|
||||
runtimeAPI.projects.unstageFile(opts).then(function() {
|
||||
res.redirect(303,req.baseUrl+"/"+opts.id+"/status");
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
});
|
||||
|
||||
// Unstage multiple files
|
||||
app.delete("/:id/stage", needsPermission("projects.write"), function(req, res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id
|
||||
}
|
||||
runtimeAPI.projects.unstageFile(opts).then(function() {
|
||||
res.redirect(303,req.baseUrl+"/"+opts.id+"/status");
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
});
|
||||
|
||||
// Get a file diff
|
||||
app.get("/:id/diff/:type/*", needsPermission("projects.read"), function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id,
|
||||
path: req.params[0],
|
||||
type: req.params.type
|
||||
}
|
||||
runtimeAPI.projects.getFileDiff(opts).then(function(data) {
|
||||
res.json({
|
||||
diff: data
|
||||
})
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
});
|
||||
|
||||
// Get a list of commits
|
||||
app.get("/:id/commits", needsPermission("projects.read"), function(req, res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id,
|
||||
limit: req.query.limit || 20,
|
||||
before: req.query.before
|
||||
}
|
||||
runtimeAPI.projects.getCommits(opts).then(function(data) {
|
||||
res.json(data);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
});
|
||||
|
||||
// Get an individual commit details
|
||||
app.get("/:id/commits/:sha", needsPermission("projects.read"), function(req, res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id,
|
||||
sha: req.params.sha
|
||||
}
|
||||
runtimeAPI.projects.getCommit(opts).then(function(data) {
|
||||
res.json({commit:data});
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
});
|
||||
|
||||
// Push local commits to remote
|
||||
app.post("/:id/push/?*", needsPermission("projects.write"), function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id,
|
||||
remote: req.params[0],
|
||||
track: req.query.u
|
||||
}
|
||||
runtimeAPI.projects.push(opts).then(function(data) {
|
||||
res.status(204).end();
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
});
|
||||
|
||||
// Pull remote commits
|
||||
app.post("/:id/pull/?*", needsPermission("projects.write"), function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id,
|
||||
remote: req.params[0],
|
||||
track: req.query.setUpstream,
|
||||
allowUnrelatedHistories: req.query.allowUnrelatedHistories
|
||||
}
|
||||
runtimeAPI.projects.pull(opts).then(function(data) {
|
||||
res.status(204).end();
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
});
|
||||
|
||||
// Abort an ongoing merge
|
||||
app.delete("/:id/merge", needsPermission("projects.write"), function(req, res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id
|
||||
}
|
||||
runtimeAPI.projects.abortMerge(opts).then(function() {
|
||||
res.status(204).end();
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
});
|
||||
|
||||
// Resolve a merge
|
||||
app.post("/:id/resolve/*", needsPermission("projects.write"), function(req, res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id,
|
||||
path: req.params[0],
|
||||
resolution: req.body.resolutions
|
||||
}
|
||||
runtimeAPI.projects.resolveMerge(opts).then(function() {
|
||||
res.status(204).end();
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
});
|
||||
|
||||
// Get a list of local branches
|
||||
app.get("/:id/branches", needsPermission("projects.read"), function(req, res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id,
|
||||
remote: false
|
||||
}
|
||||
runtimeAPI.projects.getBranches(opts).then(function(data) {
|
||||
res.json(data);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
});
|
||||
|
||||
// Delete a local branch - ?force=true
|
||||
app.delete("/:id/branches/:branchName", needsPermission("projects.write"), function(req, res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id,
|
||||
branch: req.params.branchName,
|
||||
force: !!req.query.force
|
||||
}
|
||||
runtimeAPI.projects.deleteBranch(opts).then(function(data) {
|
||||
res.status(204).end();
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
});
|
||||
|
||||
// Get a list of remote branches
|
||||
app.get("/:id/branches/remote", needsPermission("projects.read"), function(req, res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id,
|
||||
remote: true
|
||||
}
|
||||
runtimeAPI.projects.getBranches(opts).then(function(data) {
|
||||
res.json(data);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
});
|
||||
|
||||
// Get branch status - commit counts/ahead/behind
|
||||
app.get("/:id/branches/remote/*/status", needsPermission("projects.read"), function(req, res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id,
|
||||
branch: req.params[0]
|
||||
}
|
||||
runtimeAPI.projects.getBranchStatus(opts).then(function(data) {
|
||||
res.json(data);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
});
|
||||
|
||||
// Set the active local branch
|
||||
app.post("/:id/branches", needsPermission("projects.write"), function(req, res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id,
|
||||
branch: req.body.name,
|
||||
create: req.body.create
|
||||
}
|
||||
runtimeAPI.projects.setBranch(opts).then(function(data) {
|
||||
res.json(data);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
});
|
||||
|
||||
// Get a list of remotes
|
||||
app.get("/:id/remotes", needsPermission("projects.read"), function(req, res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id
|
||||
}
|
||||
runtimeAPI.projects.getRemotes(opts).then(function(data) {
|
||||
res.json(data);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
});
|
||||
|
||||
// Add a remote
|
||||
app.post("/:id/remotes", needsPermission("projects.write"), function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id,
|
||||
remote: req.body
|
||||
}
|
||||
if (/^https?:\/\/[^/]+@/i.test(req.body.url)) {
|
||||
res.status(400).json({error:"unexpected_error", message:"Git http url must not include username/password"});
|
||||
return;
|
||||
}
|
||||
runtimeAPI.projects.addRemote(opts).then(function(data) {
|
||||
res.redirect(303,req.baseUrl+"/"+opts.id+"/remotes");
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
});
|
||||
|
||||
// Delete a remote
|
||||
app.delete("/:id/remotes/:remoteName", needsPermission("projects.write"), function(req, res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id,
|
||||
remote: req.params.remoteName
|
||||
}
|
||||
runtimeAPI.projects.removeRemote(opts).then(function(data) {
|
||||
res.redirect(303,req.baseUrl+"/"+opts.id+"/remotes");
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
});
|
||||
|
||||
// Update a remote
|
||||
app.put("/:id/remotes/:remoteName", needsPermission("projects.write"), function(req,res) {
|
||||
var remote = req.body || {};
|
||||
remote.name = req.params.remoteName;
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id,
|
||||
remote: remote
|
||||
}
|
||||
runtimeAPI.projects.updateRemote(opts).then(function() {
|
||||
res.status(204).end();
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
@@ -1,559 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
**/
|
||||
|
||||
var express = require("express");
|
||||
var runtime;
|
||||
var needsPermission = require("../../auth").needsPermission;
|
||||
|
||||
module.exports = {
|
||||
init: function(_runtime) {
|
||||
runtime = _runtime;
|
||||
},
|
||||
app: function() {
|
||||
var app = express();
|
||||
|
||||
app.use(function(req,res,next) {
|
||||
if (!runtime.storage.projects) {
|
||||
res.status(404).end();
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
// Projects
|
||||
|
||||
// List all projects
|
||||
app.get("/", needsPermission("projects.read"), function(req,res) {
|
||||
runtime.storage.projects.listProjects(req.user, req.user).then(function(list) {
|
||||
var active = runtime.storage.projects.getActiveProject(req.user);
|
||||
var response = {
|
||||
projects: list
|
||||
};
|
||||
if (active) {
|
||||
response.active = active.name;
|
||||
}
|
||||
res.json(response);
|
||||
}).catch(function(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()});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Create project
|
||||
app.post("/", needsPermission("projects.write"), function(req,res) {
|
||||
runtime.storage.projects.createProject(req.user, req.body).then(function(data) {
|
||||
res.json(data);
|
||||
}).catch(function(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()});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Update a project
|
||||
app.put("/:id", needsPermission("projects.write"), function(req,res) {
|
||||
//TODO: validate the payload properly
|
||||
if (req.body.active) {
|
||||
var currentProject = runtime.storage.projects.getActiveProject(req.user);
|
||||
if (!currentProject || req.params.id !== currentProject.name) {
|
||||
runtime.storage.projects.setActiveProject(req.user, req.params.id).then(function() {
|
||||
res.redirect(303,req.baseUrl + '/');
|
||||
}).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()});
|
||||
}
|
||||
})
|
||||
} else {
|
||||
res.redirect(303,req.baseUrl + '/'+ req.params.id);
|
||||
}
|
||||
} else if (req.body.initialise) {
|
||||
// Initialised set when creating default files for an empty repo
|
||||
runtime.storage.projects.initialiseProject(req.user, req.params.id, req.body).then(function() {
|
||||
res.redirect(303,req.baseUrl + '/'+ req.params.id);
|
||||
}).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()});
|
||||
}
|
||||
})
|
||||
} else if (req.body.hasOwnProperty('credentialSecret') ||
|
||||
req.body.hasOwnProperty('description') ||
|
||||
req.body.hasOwnProperty('dependencies')||
|
||||
req.body.hasOwnProperty('summary') ||
|
||||
req.body.hasOwnProperty('files') ||
|
||||
req.body.hasOwnProperty('git')) {
|
||||
runtime.storage.projects.updateProject(req.user, req.params.id, req.body).then(function() {
|
||||
res.redirect(303,req.baseUrl + '/'+ req.params.id);
|
||||
}).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()});
|
||||
}
|
||||
})
|
||||
} else {
|
||||
res.status(400).json({error:"unexpected_error", message:"invalid_request"});
|
||||
}
|
||||
});
|
||||
|
||||
// Get project metadata
|
||||
app.get("/:id", needsPermission("projects.read"), function(req,res) {
|
||||
runtime.storage.projects.getProject(req.user, req.params.id).then(function(data) {
|
||||
if (data) {
|
||||
res.json(data);
|
||||
} else {
|
||||
res.status(404).end();
|
||||
}
|
||||
}).catch(function(err) {
|
||||
console.log(err.stack);
|
||||
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||
})
|
||||
});
|
||||
|
||||
// Delete project
|
||||
app.delete("/:id", needsPermission("projects.write"), function(req,res) {
|
||||
runtime.storage.projects.deleteProject(req.user, req.params.id).then(function() {
|
||||
res.status(204).end();
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.log(err.stack);
|
||||
res.status(400).json({error:"unexpected_error", message:err.toString()})
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Get project status - files, commit counts, branch info
|
||||
app.get("/:id/status", needsPermission("projects.read"), function(req,res) {
|
||||
var includeRemote = req.query.remote;
|
||||
|
||||
runtime.storage.projects.getStatus(req.user, req.params.id, includeRemote).then(function(data) {
|
||||
if (data) {
|
||||
res.json(data);
|
||||
} else {
|
||||
res.status(404).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()});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
// Project file listing
|
||||
app.get("/:id/files", needsPermission("projects.read"), function(req,res) {
|
||||
runtime.storage.projects.getFiles(req.user, req.params.id).then(function(data) {
|
||||
// console.log("TODO: REMOVE /:id/files as /:id/status is better!")
|
||||
res.json(data);
|
||||
})
|
||||
.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()});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
// Get file content in a given tree (index/stage)
|
||||
app.get("/:id/files/:treeish/*", needsPermission("projects.read"), function(req,res) {
|
||||
var projectId = req.params.id;
|
||||
var treeish = req.params.treeish;
|
||||
var filePath = req.params[0];
|
||||
|
||||
runtime.storage.projects.getFile(req.user, projectId,filePath,treeish).then(function(data) {
|
||||
res.json({content:data});
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.log(err.stack);
|
||||
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||
})
|
||||
});
|
||||
|
||||
// Revert a file
|
||||
app.delete("/:id/files/_/*", needsPermission("projects.write"), function(req,res) {
|
||||
var projectId = req.params.id;
|
||||
var filePath = req.params[0];
|
||||
|
||||
runtime.storage.projects.revertFile(req.user, projectId,filePath).then(function() {
|
||||
res.status(204).end();
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.log(err.stack);
|
||||
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
|
||||
// Stage a file
|
||||
app.post("/:id/stage/*", needsPermission("projects.write"), function(req,res) {
|
||||
var projectName = req.params.id;
|
||||
var file = req.params[0];
|
||||
|
||||
runtime.storage.projects.stageFile(req.user, projectName,file).then(function(data) {
|
||||
res.redirect(303,req.baseUrl+"/"+projectName+"/status");
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.log(err.stack);
|
||||
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||
})
|
||||
});
|
||||
|
||||
// Stage multiple files
|
||||
app.post("/:id/stage", needsPermission("projects.write"), function(req,res) {
|
||||
var projectName = req.params.id;
|
||||
var files = req.body.files;
|
||||
|
||||
runtime.storage.projects.stageFile(req.user, projectName,files).then(function(data) {
|
||||
res.redirect(303,req.baseUrl+"/"+projectName+"/status");
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.log(err.stack);
|
||||
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||
})
|
||||
});
|
||||
|
||||
// Commit changes
|
||||
app.post("/:id/commit", needsPermission("projects.write"), function(req,res) {
|
||||
var projectName = req.params.id;
|
||||
|
||||
runtime.storage.projects.commit(req.user, projectName,req.body).then(function(data) {
|
||||
res.redirect(303,req.baseUrl+"/"+projectName+"/status");
|
||||
})
|
||||
.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()});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Unstage a file
|
||||
app.delete("/:id/stage/*", needsPermission("projects.write"), function(req,res) {
|
||||
var projectName = req.params.id;
|
||||
var file = req.params[0];
|
||||
|
||||
runtime.storage.projects.unstageFile(req.user, projectName,file).then(function(data) {
|
||||
res.redirect(303,req.baseUrl+"/"+projectName+"/status");
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.log(err.stack);
|
||||
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||
})
|
||||
});
|
||||
|
||||
// Unstage multiple files
|
||||
app.delete("/:id/stage", needsPermission("projects.write"), function(req, res) {
|
||||
var projectName = req.params.id;
|
||||
runtime.storage.projects.unstageFile(req.user, projectName).then(function(data) {
|
||||
res.redirect(303,req.baseUrl+"/"+projectName+"/status");
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.log(err.stack);
|
||||
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||
})
|
||||
});
|
||||
|
||||
// Get a file diff
|
||||
app.get("/:id/diff/:type/*", needsPermission("projects.read"), function(req,res) {
|
||||
var projectName = req.params.id;
|
||||
var type = req.params.type;
|
||||
var file = req.params[0];
|
||||
runtime.storage.projects.getFileDiff(req.user, projectName,file,type).then(function(data) {
|
||||
res.json({
|
||||
diff: data
|
||||
})
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.log(err.stack);
|
||||
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||
})
|
||||
});
|
||||
|
||||
// Get a list of commits
|
||||
app.get("/:id/commits", needsPermission("projects.read"), function(req, res) {
|
||||
var projectName = req.params.id;
|
||||
var options = {
|
||||
limit: req.query.limit||20,
|
||||
before: req.query.before
|
||||
};
|
||||
runtime.storage.projects.getCommits(req.user, projectName,options).then(function(data) {
|
||||
res.json(data);
|
||||
})
|
||||
.catch(function(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 an individual commit details
|
||||
app.get("/:id/commits/:sha", needsPermission("projects.read"), function(req, res) {
|
||||
var projectName = req.params.id;
|
||||
var sha = req.params.sha;
|
||||
|
||||
runtime.storage.projects.getCommit(req.user, projectName,sha).then(function(data) {
|
||||
res.json({commit:data});
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.log(err.stack);
|
||||
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||
})
|
||||
});
|
||||
|
||||
// Push local commits to remote
|
||||
app.post("/:id/push/?*", needsPermission("projects.write"), function(req,res) {
|
||||
var projectName = req.params.id;
|
||||
var remoteBranchName = req.params[0]
|
||||
var setRemote = req.query.u;
|
||||
runtime.storage.projects.push(req.user, projectName,remoteBranchName,setRemote).then(function(data) {
|
||||
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()});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Pull remote commits
|
||||
app.post("/:id/pull/?*", needsPermission("projects.write"), function(req,res) {
|
||||
var projectName = req.params.id;
|
||||
var remoteBranchName = req.params[0];
|
||||
var setUpstream = req.query.setUpstream;
|
||||
var allowUnrelatedHistories = req.query.allowUnrelatedHistories;
|
||||
runtime.storage.projects.pull(req.user, projectName,remoteBranchName,setUpstream,allowUnrelatedHistories).then(function(data) {
|
||||
res.status(204).end();
|
||||
})
|
||||
.catch(function(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()});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Abort an ongoing merge
|
||||
app.delete("/:id/merge", needsPermission("projects.write"), function(req, res) {
|
||||
var projectName = req.params.id;
|
||||
runtime.storage.projects.abortMerge(req.user, projectName).then(function(data) {
|
||||
res.status(204).end();
|
||||
})
|
||||
.catch(function(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()});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Resolve a merge
|
||||
app.post("/:id/resolve/*", needsPermission("projects.write"), function(req, res) {
|
||||
var projectName = req.params.id;
|
||||
var file = req.params[0];
|
||||
var resolution = req.body.resolutions;
|
||||
runtime.storage.projects.resolveMerge(req.user, projectName,file,resolution).then(function(data) {
|
||||
res.status(204).end();
|
||||
})
|
||||
.catch(function(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 a list of local branches
|
||||
app.get("/:id/branches", needsPermission("projects.read"), function(req, res) {
|
||||
var projectName = req.params.id;
|
||||
runtime.storage.projects.getBranches(req.user, projectName,false).then(function(data) {
|
||||
res.json(data);
|
||||
})
|
||||
.catch(function(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()});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Delete a local branch - ?force=true
|
||||
app.delete("/:id/branches/:branchName", needsPermission("projects.write"), function(req, res) {
|
||||
var projectName = req.params.id;
|
||||
var branchName = req.params.branchName;
|
||||
var force = !!req.query.force;
|
||||
runtime.storage.projects.deleteBranch(req.user, projectName, branchName, false, force).then(function(data) {
|
||||
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()});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Get a list of remote branches
|
||||
app.get("/:id/branches/remote", needsPermission("projects.read"), function(req, res) {
|
||||
var projectName = req.params.id;
|
||||
runtime.storage.projects.getBranches(req.user, projectName,true).then(function(data) {
|
||||
res.json(data);
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.log(err);
|
||||
if (err.code) {
|
||||
res.status(400).json({error:err.code, message: err.message, remote: err.remote});
|
||||
} else {
|
||||
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Get branch status - commit counts/ahead/behind
|
||||
app.get("/:id/branches/remote/*/status", needsPermission("projects.read"), function(req, res) {
|
||||
var projectName = req.params.id;
|
||||
var branch = req.params[0];
|
||||
runtime.storage.projects.getBranchStatus(req.user, projectName,branch).then(function(data) {
|
||||
res.json(data);
|
||||
})
|
||||
.catch(function(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()});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Set the active local branch
|
||||
app.post("/:id/branches", needsPermission("projects.write"), function(req, res) {
|
||||
var projectName = req.params.id;
|
||||
var branchName = req.body.name;
|
||||
var isCreate = req.body.create;
|
||||
runtime.storage.projects.setBranch(req.user, projectName,branchName,isCreate).then(function(data) {
|
||||
res.json(data);
|
||||
})
|
||||
.catch(function(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 a list of remotes
|
||||
app.get("/:id/remotes", needsPermission("projects.read"), function(req, res) {
|
||||
var projectName = req.params.id;
|
||||
runtime.storage.projects.getRemotes(req.user, projectName).then(function(data) {
|
||||
res.json(data);
|
||||
})
|
||||
.catch(function(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()});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Add a remote
|
||||
app.post("/:id/remotes", needsPermission("projects.write"), function(req,res) {
|
||||
var projectName = req.params.id;
|
||||
if (/^https?:\/\/[^/]+@/i.test(req.body.url)) {
|
||||
res.status(400).json({error:"unexpected_error", message:"Git http url must not include username/password"});
|
||||
return;
|
||||
}
|
||||
runtime.storage.projects.addRemote(req.user, projectName, req.body).then(function() {
|
||||
res.redirect(303,req.baseUrl+"/"+projectName+"/remotes");
|
||||
}).catch(function(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()});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Delete a remote
|
||||
app.delete("/:id/remotes/:remoteName", needsPermission("projects.write"), function(req, res) {
|
||||
var projectName = req.params.id;
|
||||
var remoteName = req.params.remoteName;
|
||||
runtime.storage.projects.removeRemote(req.user, projectName, remoteName).then(function(data) {
|
||||
res.redirect(303,req.baseUrl+"/"+projectName+"/remotes");
|
||||
})
|
||||
.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()});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Update a remote
|
||||
app.put("/:id/remotes/:remoteName", needsPermission("projects.write"), function(req,res) {
|
||||
var projectName = req.params.id;
|
||||
var remoteName = req.params.remoteName;
|
||||
runtime.storage.projects.updateRemote(req.user, projectName, remoteName, req.body).then(function(data) {
|
||||
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()});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
@@ -13,127 +13,48 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
var theme = require("../editor/theme");
|
||||
var util = require('util');
|
||||
var runtime;
|
||||
var settings;
|
||||
var log;
|
||||
var apiUtils = require("../util");
|
||||
var runtimeAPI;
|
||||
var sshkeys = require("./sshkeys");
|
||||
var theme = require("./theme");
|
||||
|
||||
module.exports = {
|
||||
init: function(_runtime) {
|
||||
runtime = _runtime;
|
||||
settings = runtime.settings;
|
||||
log = runtime.log;
|
||||
init: function(_runtimeAPI) {
|
||||
runtimeAPI = _runtimeAPI;
|
||||
sshkeys.init(runtimeAPI);
|
||||
},
|
||||
runtimeSettings: function(req,res) {
|
||||
var safeSettings = {
|
||||
httpNodeRoot: settings.httpNodeRoot||"/",
|
||||
version: settings.version
|
||||
var opts = {
|
||||
user: req.user
|
||||
}
|
||||
if (req.user) {
|
||||
safeSettings.user = {}
|
||||
var props = ["anonymous","username","image","permissions"];
|
||||
props.forEach(prop => {
|
||||
if (req.user.hasOwnProperty(prop)) {
|
||||
safeSettings.user[prop] = req.user[prop];
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
safeSettings.context = runtime.nodes.listContextStores();
|
||||
|
||||
var themeSettings = theme.settings();
|
||||
if (themeSettings) {
|
||||
safeSettings.editorTheme = themeSettings;
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
runtimeAPI.settings.getRuntimeSettings(opts).then(function(result) {
|
||||
var themeSettings = theme.settings();
|
||||
if (themeSettings) {
|
||||
result.editorTheme = themeSettings;
|
||||
}
|
||||
safeSettings.git = {
|
||||
globalUser: runtime.storage.projects.getGlobalGitUser()
|
||||
}
|
||||
}
|
||||
|
||||
safeSettings.flowEncryptionType = runtime.nodes.getCredentialKeyType();
|
||||
|
||||
settings.exportNodeSettings(safeSettings);
|
||||
res.json(safeSettings);
|
||||
res.json(result);
|
||||
});
|
||||
},
|
||||
userSettings: function(req, res) {
|
||||
var username;
|
||||
if (!req.user || req.user.anonymous) {
|
||||
username = '_';
|
||||
} else {
|
||||
username = req.user.username;
|
||||
var opts = {
|
||||
user: req.user
|
||||
}
|
||||
res.json(settings.getUserSettings(username)||{});
|
||||
runtimeAPI.settings.getUserSettings(opts).then(function(result) {
|
||||
res.json(result);
|
||||
});
|
||||
},
|
||||
updateUserSettings: function(req,res) {
|
||||
var username;
|
||||
if (!req.user || req.user.anonymous) {
|
||||
username = '_';
|
||||
} else {
|
||||
username = req.user.username;
|
||||
}
|
||||
var currentSettings = settings.getUserSettings(username)||{};
|
||||
currentSettings = extend(currentSettings, req.body);
|
||||
try {
|
||||
settings.setUserSettings(username, currentSettings).then(function() {
|
||||
log.audit({event: "settings.update",username:username},req);
|
||||
res.status(204).end();
|
||||
}).catch(function(err) {
|
||||
log.audit({event: "settings.update",username:username,error:err.code||"unexpected_error",message:err.toString()},req);
|
||||
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")}));
|
||||
log.audit({event: "settings.update",username:username,error:err.code||"unexpected_error",message:err.toString()},req);
|
||||
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
|
||||
var opts = {
|
||||
user: req.user,
|
||||
settings: req.body
|
||||
}
|
||||
runtimeAPI.settings.updateUserSettings(opts).then(function(result) {
|
||||
res.status(204).end();
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
});
|
||||
},
|
||||
sshkeys: function() {
|
||||
return sshkeys.app()
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -14,10 +14,9 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var apiUtils = require("../util");
|
||||
var express = require("express");
|
||||
var os = require("os");
|
||||
var runtime;
|
||||
var needsPermission = require("../auth").needsPermission;
|
||||
var runtimeAPI;
|
||||
|
||||
function getUsername(userObj) {
|
||||
var username = '__default';
|
||||
@@ -28,94 +27,72 @@ function getUsername(userObj) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function(_runtime) {
|
||||
runtime = _runtime;
|
||||
init: function(_runtimeAPI) {
|
||||
runtimeAPI = _runtimeAPI;
|
||||
},
|
||||
app: function() {
|
||||
var app = express();
|
||||
|
||||
// SSH keys
|
||||
|
||||
// List all SSH keys
|
||||
app.get("/", needsPermission("settings.read"), function(req,res) {
|
||||
var username = getUsername(req.user);
|
||||
runtime.storage.projects.ssh.listSSHKeys(username)
|
||||
.then(function(list) {
|
||||
app.get("/", function(req,res) {
|
||||
var opts = {
|
||||
user: req.user
|
||||
}
|
||||
runtimeAPI.settings.getUserKeys(opts).then(function(list) {
|
||||
res.json({
|
||||
keys: list
|
||||
});
|
||||
})
|
||||
.catch(function(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()});
|
||||
}
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
});
|
||||
});
|
||||
|
||||
// Get SSH key detail
|
||||
app.get("/:id", needsPermission("settings.read"), function(req,res) {
|
||||
var username = getUsername(req.user);
|
||||
// console.log('username:', username);
|
||||
runtime.storage.projects.ssh.getSSHKey(username, req.params.id)
|
||||
.then(function(data) {
|
||||
if (data) {
|
||||
res.json({
|
||||
publickey: data
|
||||
});
|
||||
} else {
|
||||
res.status(404).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()});
|
||||
}
|
||||
app.get("/:id", function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id
|
||||
}
|
||||
runtimeAPI.settings.getUserKey(opts).then(function(data) {
|
||||
res.json({
|
||||
publickey: data
|
||||
});
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
});
|
||||
});
|
||||
|
||||
// Generate a SSH key
|
||||
app.post("/", needsPermission("settings.write"), function(req,res) {
|
||||
var username = getUsername(req.user);
|
||||
// console.log('req.body:', req.body);
|
||||
if ( req.body && req.body.name && /^[a-zA-Z0-9\-_]+$/.test(req.body.name)) {
|
||||
runtime.storage.projects.ssh.generateSSHKey(username, req.body)
|
||||
.then(function(name) {
|
||||
// console.log('generate key --- success name:', name);
|
||||
res.json({
|
||||
name: name
|
||||
});
|
||||
})
|
||||
.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()});
|
||||
}
|
||||
app.post("/", function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id
|
||||
}
|
||||
// TODO: validate params
|
||||
opts.name = req.body.name;
|
||||
opts.password = req.body.password;
|
||||
opts.comment = req.body.comment;
|
||||
opts.size = req.body.size;
|
||||
|
||||
runtimeAPI.settings.generateUserKey(opts).then(function(name) {
|
||||
res.json({
|
||||
name: name
|
||||
});
|
||||
}
|
||||
else {
|
||||
res.status(400).json({error:"unexpected_error", message:"You need to have body or body.name"});
|
||||
}
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
});
|
||||
});
|
||||
|
||||
// Delete a SSH key
|
||||
app.delete("/:id", needsPermission("settings.write"), function(req,res) {
|
||||
var username = getUsername(req.user);
|
||||
runtime.storage.projects.ssh.deleteSSHKey(username, req.params.id)
|
||||
.then(function() {
|
||||
app.delete("/:id", function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
id: req.params.id
|
||||
}
|
||||
runtimeAPI.settings.removeUserKey(opts).then(function(name) {
|
||||
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()});
|
||||
}
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ var defaultContext = {
|
||||
var theme = null;
|
||||
var themeContext = clone(defaultContext);
|
||||
var themeSettings = null;
|
||||
var runtime = null;
|
||||
|
||||
var themeApp;
|
||||
|
||||
@@ -78,12 +77,8 @@ function serveFilesFromTheme(themeValue, themeApp, directory) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function(runtime) {
|
||||
var settings = runtime.settings;
|
||||
init: function(settings) {
|
||||
themeContext = clone(defaultContext);
|
||||
if (runtime.version) {
|
||||
themeContext.version = runtime.version();
|
||||
}
|
||||
themeSettings = null;
|
||||
theme = settings.editorTheme || {};
|
||||
},
|
||||
|
||||
@@ -17,17 +17,20 @@ var express = require('express');
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
var Mustache = require("mustache");
|
||||
var mime = require("mime");
|
||||
|
||||
var apiUtils = require("../util");
|
||||
|
||||
var theme = require("./theme");
|
||||
|
||||
var redNodes;
|
||||
var runtimeAPI;
|
||||
|
||||
var templateDir = path.resolve(__dirname+"/../../../editor/templates");
|
||||
var editorTemplate;
|
||||
|
||||
module.exports = {
|
||||
init: function(runtime) {
|
||||
redNodes = runtime.nodes;
|
||||
init: function(_runtimeAPI) {
|
||||
runtimeAPI = _runtimeAPI;
|
||||
editorTemplate = fs.readFileSync(path.join(templateDir,"index.mst"),"utf8");
|
||||
Mustache.parse(editorTemplate);
|
||||
},
|
||||
@@ -46,8 +49,18 @@ module.exports = {
|
||||
var icon = req.params.icon;
|
||||
var scope = req.params.scope;
|
||||
var module = scope ? scope + '/' + req.params.module : req.params.module;
|
||||
var iconPath = redNodes.getNodeIconPath(module,icon);
|
||||
res.sendFile(iconPath);
|
||||
var opts = {
|
||||
user: req.user,
|
||||
module: module,
|
||||
icon: icon
|
||||
}
|
||||
runtimeAPI.nodes.getIcon(opts).then(function(data) {
|
||||
var contentType = mime.lookup(icon);
|
||||
res.set("Content-Type", contentType);
|
||||
res.send(data);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
},
|
||||
editor: function(req,res) {
|
||||
res.send(Mustache.render(editorTemplate,theme.context()));
|
||||
|
||||
@@ -26,17 +26,21 @@ var apiUtil = require("./util");
|
||||
|
||||
var adminApp;
|
||||
var server;
|
||||
var runtime;
|
||||
var editor;
|
||||
|
||||
function init(_server,_runtime) {
|
||||
function init(_server,settings,storage,runtimeAPI) {
|
||||
server = _server;
|
||||
runtime = _runtime;
|
||||
var settings = runtime.settings;
|
||||
if (settings.httpAdminRoot !== false) {
|
||||
apiUtil.init(runtime);
|
||||
adminApp = express();
|
||||
auth.init(runtime);
|
||||
|
||||
var cors = require('cors');
|
||||
var corsHandler = cors({
|
||||
origin: "*",
|
||||
methods: "GET,PUT,POST,DELETE"
|
||||
});
|
||||
adminApp.use(corsHandler);
|
||||
|
||||
auth.init(settings,storage);
|
||||
|
||||
var maxApiRequestSize = settings.apiMaxLength || '5mb';
|
||||
adminApp.use(bodyParser.json({limit:maxApiRequestSize}));
|
||||
@@ -61,7 +65,7 @@ function init(_server,_runtime) {
|
||||
// Editor
|
||||
if (!settings.disableEditor) {
|
||||
editor = require("./editor");
|
||||
var editorApp = editor.init(server, runtime);
|
||||
var editorApp = editor.init(server, settings, runtimeAPI);
|
||||
adminApp.use(editorApp);
|
||||
}
|
||||
|
||||
@@ -70,7 +74,7 @@ function init(_server,_runtime) {
|
||||
adminApp.use(corsHandler);
|
||||
}
|
||||
|
||||
var adminApiApp = require("./admin").init(runtime);
|
||||
var adminApiApp = require("./admin").init(runtimeAPI);
|
||||
adminApp.use(adminApiApp);
|
||||
} else {
|
||||
adminApp = null;
|
||||
@@ -93,23 +97,9 @@ module.exports = {
|
||||
init: init,
|
||||
start: start,
|
||||
stop: stop,
|
||||
library: {
|
||||
register: function(type) {
|
||||
if (editor) {
|
||||
editor.registerLibrary(type);
|
||||
}
|
||||
}
|
||||
},
|
||||
auth: {
|
||||
needsPermission: auth.needsPermission
|
||||
},
|
||||
comms: {
|
||||
publish: function(topic,data,retain) {
|
||||
if (editor) {
|
||||
editor.publish(topic,data,retain);
|
||||
}
|
||||
}
|
||||
},
|
||||
get adminApp() { return adminApp; },
|
||||
get server() { return server; }
|
||||
};
|
||||
|
||||
@@ -15,16 +15,12 @@
|
||||
**/
|
||||
|
||||
|
||||
var i18n;
|
||||
var log;
|
||||
var log = require("../util").log; // TODO: separate module
|
||||
var i18n = require("../util").i18n; // TODO: separate module
|
||||
|
||||
|
||||
module.exports = {
|
||||
init: function(_runtime) {
|
||||
log = _runtime.log;
|
||||
i18n = _runtime.i18n;
|
||||
},
|
||||
errorHandler: function(err,req,res,next) {
|
||||
console.error(err.stack);
|
||||
if (err.message === "request entity too large") {
|
||||
log.error(err);
|
||||
} else {
|
||||
@@ -41,5 +37,11 @@ module.exports = {
|
||||
lang = acceptedLanguages[0];
|
||||
}
|
||||
return lang;
|
||||
},
|
||||
rejectHandler: function(req,res,err) {
|
||||
res.status(err.status||500).json({
|
||||
code: err.code||"unexpected_error",
|
||||
message: err.message||err.toString()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
32
red/red.js
32
red/red.js
@@ -18,6 +18,9 @@ var fs = require("fs");
|
||||
var path = require('path');
|
||||
|
||||
var runtime = require("./runtime");
|
||||
var runtimeAPI = require("./runtime-api");
|
||||
var redUtil = require("./util");
|
||||
|
||||
var api = require("./api");
|
||||
|
||||
process.env.NODE_RED_HOME = process.env.NODE_RED_HOME || path.resolve(__dirname+"/..");
|
||||
@@ -64,15 +67,17 @@ module.exports = {
|
||||
if (!userSettings.coreNodesDir) {
|
||||
userSettings.coreNodesDir = path.resolve(path.join(__dirname,"..","nodes"));
|
||||
}
|
||||
|
||||
redUtil.init(userSettings);
|
||||
if (userSettings.httpAdminRoot !== false) {
|
||||
runtime.init(userSettings,api);
|
||||
api.init(httpServer,runtime);
|
||||
runtime.init(userSettings,redUtil,api);
|
||||
runtimeAPI.init(runtime,redUtil);
|
||||
api.init(httpServer,userSettings,runtime.storage,runtimeAPI);
|
||||
|
||||
apiEnabled = true;
|
||||
server = runtime.adminApi.server;
|
||||
runtime.server = runtime.adminApi.server;
|
||||
} else {
|
||||
runtime.init(userSettings);
|
||||
runtime.init(userSettings,redUtil);
|
||||
apiEnabled = false;
|
||||
if (httpServer){
|
||||
server = httpServer;
|
||||
@@ -101,14 +106,25 @@ module.exports = {
|
||||
})
|
||||
},
|
||||
nodes: runtime.nodes,
|
||||
log: runtime.log,
|
||||
log: redUtil.log,
|
||||
settings:runtime.settings,
|
||||
util: runtime.util,
|
||||
version: runtime.version,
|
||||
events: runtime.events,
|
||||
|
||||
comms: api.comms,
|
||||
library: api.library,
|
||||
comms: {
|
||||
publish: function(topic,data,retain) {
|
||||
runtime.events.emit("comms",{
|
||||
topic: topic,
|
||||
data: data,
|
||||
retain: retain
|
||||
})
|
||||
}
|
||||
},
|
||||
library: {
|
||||
register: function(type) {
|
||||
return runtime.library.register(null,type);
|
||||
}
|
||||
},
|
||||
auth: api.auth,
|
||||
|
||||
get app() { console.log("Deprecated use of RED.app - use RED.httpAdmin instead"); return runtime.app },
|
||||
|
||||
131
red/runtime-api/comms.js
Normal file
131
red/runtime-api/comms.js
Normal file
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* 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.comms
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef CommsConnection
|
||||
* @type {object}
|
||||
* @property {string} session - a unique session identifier
|
||||
* @property {Object} user - the user associated with the connection
|
||||
* @property {Function} send - publish a message to the connection
|
||||
*/
|
||||
|
||||
|
||||
var runtime;
|
||||
var retained = {};
|
||||
var connections = [];
|
||||
|
||||
|
||||
function handleCommsEvent(event) {
|
||||
publish(event.topic,event.data,event.retain);
|
||||
}
|
||||
function handleStatusEvent(event) {
|
||||
publish("status/"+event.id,event.status,true);
|
||||
}
|
||||
function handleRuntimeEvent(event) {
|
||||
runtime.log.trace("runtime event: "+JSON.stringify(event));
|
||||
publish("notification/"+event.id,event.payload||{},event.retain);
|
||||
}
|
||||
|
||||
function publish(topic,data,retain) {
|
||||
if (retain) {
|
||||
retained[topic] = data;
|
||||
} else {
|
||||
delete retained[topic];
|
||||
}
|
||||
connections.forEach(connection => connection.send(topic,data))
|
||||
}
|
||||
|
||||
|
||||
var api = module.exports = {
|
||||
init: function(_runtime) {
|
||||
runtime = _runtime;
|
||||
connections = [];
|
||||
retained = {};
|
||||
runtime.events.removeListener("node-status",handleStatusEvent);
|
||||
runtime.events.on("node-status",handleStatusEvent);
|
||||
runtime.events.removeListener("runtime-event",handleRuntimeEvent);
|
||||
runtime.events.on("runtime-event",handleRuntimeEvent);
|
||||
runtime.events.removeListener("comms",handleCommsEvent);
|
||||
runtime.events.on("comms",handleCommsEvent);
|
||||
},
|
||||
|
||||
/**
|
||||
* Registers a new comms connection
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {CommsConnection} opts.client - the client connection
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.comms
|
||||
*/
|
||||
addConnection: function(opts) {
|
||||
connections.push(opts.client);
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
* Unregisters a comms connection
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {CommsConnection} opts.client - the client connection
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.comms
|
||||
*/
|
||||
removeConnection: function(opts) {
|
||||
for (var i=0;i<connections.length;i++) {
|
||||
if (connections[i] === opts.client) {
|
||||
connections.splice(i,1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
* Subscribes a comms connection to a given topic. Currently, all clients get
|
||||
* automatically subscribed to everything and cannot unsubscribe. Sending a subscribe
|
||||
* request will trigger retained messages to be sent.
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {CommsConnection} opts.client - the client connection
|
||||
* @param {String} opts.topic - the topic to subscribe to
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.comms
|
||||
*/
|
||||
subscribe: function(opts) {
|
||||
var re = new RegExp("^"+opts.topic.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$");
|
||||
for (var t in retained) {
|
||||
if (re.test(t)) {
|
||||
opts.client.send(t,retained[t]);
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
* TODO: Unsubscribes a comms connection from a given topic
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {CommsConnection} opts.client - the client connection
|
||||
* @param {String} opts.topic - the topic to unsubscribe from
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.comms
|
||||
*/
|
||||
unsubscribe: function(opts) {}
|
||||
};
|
||||
156
red/runtime-api/context.js
Normal file
156
red/runtime-api/context.js
Normal file
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* 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.context
|
||||
*/
|
||||
|
||||
var runtime;
|
||||
|
||||
// TODO: move runtime/util to util/index
|
||||
var util = require("../runtime/util");
|
||||
|
||||
function exportContextStore(scope,ctx, store, result, callback) {
|
||||
ctx.keys(store,function(err, keys) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
result[store] = {};
|
||||
var c = keys.length;
|
||||
if (c === 0) {
|
||||
callback(null);
|
||||
} else {
|
||||
keys.forEach(function(key) {
|
||||
ctx.get(key,store,function(err, v) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
if (scope !== 'global' ||
|
||||
store === runtime.nodes.listContextStores().default ||
|
||||
!runtime.settings.hasOwnProperty("functionGlobalContext") ||
|
||||
!runtime.settings.functionGlobalContext.hasOwnProperty(key) ||
|
||||
runtime.settings.functionGlobalContext[key] !== v) {
|
||||
result[store][key] = util.encodeObject({msg:v});
|
||||
}
|
||||
c--;
|
||||
if (c === 0) {
|
||||
callback(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
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.scope - the scope of the context
|
||||
* @param {String} opts.id - the id of the context
|
||||
* @param {String} opts.store - the context store
|
||||
* @param {String} opts.key - the context key
|
||||
|
||||
* @return {Promise} - the node information
|
||||
* @memberof RED.nodes
|
||||
*/
|
||||
getValue: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
var scope = opts.scope;
|
||||
var id = opts.id;
|
||||
var store = opts.store;
|
||||
var key = opts.key;
|
||||
|
||||
var availableStores = runtime.nodes.listContextStores();
|
||||
//{ default: 'default', stores: [ 'default', 'file' ] }
|
||||
if (store && availableStores.stores.indexOf(store) === -1) {
|
||||
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key,error:"not_found"});
|
||||
var err = new Error();
|
||||
err.code = "not_found";
|
||||
err.status = 404;
|
||||
return reject(err);
|
||||
}
|
||||
var ctx;
|
||||
if (scope === 'global') {
|
||||
ctx = runtime.nodes.getContext('global');
|
||||
} else if (scope === 'flow') {
|
||||
ctx = runtime.nodes.getContext(id);
|
||||
} else if (scope === 'node') {
|
||||
var node = runtime.nodes.getNode(id);
|
||||
if (node) {
|
||||
ctx = node.context();
|
||||
}
|
||||
}
|
||||
if (ctx) {
|
||||
if (key) {
|
||||
store = store || availableStores.default;
|
||||
ctx.get(key,store,function(err, v) {
|
||||
var encoded = util.encodeObject({msg:v});
|
||||
if (store !== availableStores.default) {
|
||||
encoded.store = store;
|
||||
}
|
||||
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key});
|
||||
resolve(encoded);
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
var stores;
|
||||
if (!store) {
|
||||
stores = availableStores.stores;
|
||||
} else {
|
||||
stores = [store];
|
||||
}
|
||||
|
||||
var result = {};
|
||||
var c = stores.length;
|
||||
var errorReported = false;
|
||||
stores.forEach(function(store) {
|
||||
exportContextStore(scope,ctx,store,result,function(err) {
|
||||
if (err) {
|
||||
// TODO: proper error reporting
|
||||
if (!errorReported) {
|
||||
errorReported = true;
|
||||
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key,error:"unexpected_error"});
|
||||
var err = new Error();
|
||||
err.code = "unexpected_error";
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
c--;
|
||||
if (c === 0) {
|
||||
if (!errorReported) {
|
||||
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key});
|
||||
resolve(result);
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
} else {
|
||||
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key});
|
||||
resolve({});
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
251
red/runtime-api/flows.js
Normal file
251
red/runtime-api/flows.js
Normal file
@@ -0,0 +1,251 @@
|
||||
/**
|
||||
* 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
|
||||
* @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*/);
|
||||
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) {
|
||||
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) {
|
||||
var err;
|
||||
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) {
|
||||
runtime.log.warn(runtime.log._("api.flows.error-"+(deploymentType === 'reload'?'reload':'save'),{message:err.message}));
|
||||
runtime.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.code = "not_found";
|
||||
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() {
|
||||
runtime.log.audit({event: "flow.remove",id:id});
|
||||
return resolve();
|
||||
}).catch(function(err) {
|
||||
runtime.log.audit({event: "flow.remove",id:id,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.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 {
|
||||
runtime.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) {
|
||||
runtime.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);
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
68
red/runtime-api/index.js
Normal file
68
red/runtime-api/index.js
Normal file
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 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}
|
||||
*/
|
||||
|
||||
var runtime;
|
||||
/**
|
||||
* @namespace RED
|
||||
*/
|
||||
var api = module.exports = {
|
||||
init: function(_runtime, redUtil) {
|
||||
runtime = _runtime;
|
||||
api.comms.init(runtime);
|
||||
api.flows.init(runtime);
|
||||
api.nodes.init(runtime);
|
||||
api.settings.init(runtime);
|
||||
api.library.init(runtime);
|
||||
api.projects.init(runtime);
|
||||
api.context.init(runtime);
|
||||
},
|
||||
|
||||
comms: require("./comms"),
|
||||
flows: require("./flows"),
|
||||
library: require("./library"),
|
||||
nodes: require("./nodes"),
|
||||
settings: require("./settings"),
|
||||
projects: require("./projects"),
|
||||
context: require("./context"),
|
||||
|
||||
/**
|
||||
* Returns whether the runtime is started
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @return {Promise<Boolean>} - whether the runtime is started
|
||||
* @memberof RED
|
||||
*/
|
||||
isStarted: function(opts) {
|
||||
return Promise.resolve(runtime.isStarted());
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns version number of the runtime
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @return {Promise<String>} - the runtime version number
|
||||
* @memberof RED
|
||||
*/
|
||||
version: function(opts) {
|
||||
return Promise.resolve(runtime.version());
|
||||
}
|
||||
}
|
||||
120
red/runtime-api/library.js
Normal file
120
red/runtime-api/library.js
Normal file
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* 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.library
|
||||
*/
|
||||
|
||||
var runtime;
|
||||
|
||||
var api = module.exports = {
|
||||
init: function(_runtime) {
|
||||
runtime = _runtime;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets an entry from the library.
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.type - the type of entry
|
||||
* @param {String} opts.path - the path of the entry
|
||||
* @return {Promise<String|Object>} - resolves when complete
|
||||
* @memberof RED.library
|
||||
*/
|
||||
getEntry: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
runtime.library.getEntry(opts.type,opts.path).then(function(result) {
|
||||
runtime.log.audit({event: "library.get",type:opts.type,path:opts.path});
|
||||
return resolve(result);
|
||||
}).catch(function(err) {
|
||||
if (err) {
|
||||
runtime.log.warn(runtime.log._("api.library.error-load-entry",{path:opts.path,message:err.toString()}));
|
||||
if (err.code === 'forbidden') {
|
||||
err.status = 403;
|
||||
return reject(err);
|
||||
} else if (err.code === "not_found") {
|
||||
err.status = 404;
|
||||
} else {
|
||||
err.status = 400;
|
||||
}
|
||||
runtime.log.audit({event: "library.get",type:opts.type,path:opts.path,error:err.code});
|
||||
return reject(err);
|
||||
}
|
||||
runtime.log.audit({event: "library.get",type:opts.type,error:"not_found"});
|
||||
var error = new Error();
|
||||
error.code = "not_found";
|
||||
error.status = 404;
|
||||
return reject(error);
|
||||
});
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Saves an entry to the library
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.type - the type of entry
|
||||
* @param {String} opts.path - the path of the entry
|
||||
* @param {Object} opts.meta - any meta data associated with the entry
|
||||
* @param {String} opts.body - the body of the entry
|
||||
* @return {Promise} - resolves when complete
|
||||
* @memberof RED.library
|
||||
*/
|
||||
saveEntry: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
runtime.library.saveEntry(opts.type,opts.path,opts.meta,opts.body).then(function() {
|
||||
runtime.log.audit({event: "library.set",type:opts.type,path:opts.path});
|
||||
return resolve();
|
||||
}).catch(function(err) {
|
||||
runtime.log.warn(runtime.log._("api.library.error-save-entry",{path:opts.path,message:err.toString()}));
|
||||
if (err.code === 'forbidden') {
|
||||
runtime.log.audit({event: "library.set",type:opts.type,path:opts.path,error:"forbidden"});
|
||||
err.status = 403;
|
||||
return reject(err);
|
||||
}
|
||||
runtime.log.audit({event: "library.set",type:opts.type,path:opts.path,error:"unexpected_error",message:err.toString()});
|
||||
var error = new Error();
|
||||
error.status = 400;
|
||||
return reject(error);
|
||||
});
|
||||
})
|
||||
},
|
||||
/**
|
||||
* Returns a complete listing of all entries of a given type in the library.
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.type - the type of entry
|
||||
* @return {Promise<Object>} - the entry listing
|
||||
* @memberof RED.library
|
||||
*/
|
||||
getEntries: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
if (opts.type !== 'flows') {
|
||||
return reject(new Error("API only supports flows"));
|
||||
|
||||
}
|
||||
runtime.storage.getAllFlows().then(function(flows) {
|
||||
runtime.log.audit({event: "library.get.all",type:"flow"});
|
||||
var examples = runtime.nodes.getNodeExampleFlows();
|
||||
if (examples) {
|
||||
flows.d = flows.d||{};
|
||||
flows.d._examples_ = examples;
|
||||
}
|
||||
return resolve(flows);
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
438
red/runtime-api/nodes.js
Normal file
438
red/runtime-api/nodes.js
Normal file
@@ -0,0 +1,438 @@
|
||||
/**
|
||||
* 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.nodes
|
||||
*/
|
||||
|
||||
var fs = require("fs");
|
||||
|
||||
var runtime;
|
||||
|
||||
function putNode(node, enabled) {
|
||||
var info;
|
||||
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 = runtime.nodes.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 result = runtime.nodes.getModuleInfo(opts.module);
|
||||
if (result) {
|
||||
runtime.log.audit({event: "nodes.module.get",id:opts.module});
|
||||
return resolve(result);
|
||||
} else {
|
||||
runtime.log.audit({event: "nodes.module.get",id:opts.module,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"});
|
||||
var 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"});
|
||||
var err = new Error("Module already loaded");
|
||||
err.code = "module_already_loaded";
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
}
|
||||
if (!existingModule.local) {
|
||||
runtime.log.audit({event: "nodes.install",module:opts.module, version:opts.version, error:"module_not_local"});
|
||||
var 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"});
|
||||
var 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"});
|
||||
var 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(error) {
|
||||
runtime.log.audit({event: "nodes.remove",module:opts.module,error:error.code||"unexpected_error",message:error.toString()});
|
||||
error.status = 400;
|
||||
return reject(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
var mod = opts.module;
|
||||
return new Promise(function(resolve,reject) {
|
||||
if (!runtime.settings.available()) {
|
||||
runtime.log.audit({event: "nodes.module.set",error:"settings_unavailable"});
|
||||
var err = new Error("Settings unavailable");
|
||||
err.code = "settings_unavailable";
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
}
|
||||
try {
|
||||
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(error) {
|
||||
runtime.log.audit({event: "nodes.module.set",module:mod,enabled:opts.enabled,error:error.code||"unexpected_error",message:error.toString()});
|
||||
error.status = 400;
|
||||
return reject(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 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"});
|
||||
var 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(error) {
|
||||
runtime.log.audit({event: "nodes.info.set",id:id,enabled:enabled,error:error.code||"unexpected_error",message:error.toString()});
|
||||
error.status = 400;
|
||||
return reject(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets all registered module message catalogs
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {User} opts.lang - the i18n language to return. If not set, uses runtime default (en-US)
|
||||
* @return {Promise<Object>} - the message catalogs
|
||||
* @memberof RED.nodes
|
||||
*/
|
||||
getModuleCatalogs: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
var namespace = opts.module;
|
||||
var lang = opts.lang;
|
||||
var prevLang = runtime.i18n.i.language;
|
||||
// Trigger a load from disk of the language if it is not the default
|
||||
runtime.i18n.i.changeLanguage(lang, function(){
|
||||
var nodeList = runtime.nodes.getNodeList();
|
||||
var result = {};
|
||||
nodeList.forEach(function(n) {
|
||||
if (n.module !== "node-red") {
|
||||
result[n.id] = runtime.i18n.i.getResourceBundle(lang, n.id)||{};
|
||||
}
|
||||
});
|
||||
resolve(result);
|
||||
});
|
||||
runtime.i18n.i.changeLanguage(prevLang);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a modules message catalog
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {User} opts.module - the module
|
||||
* @param {User} opts.lang - the i18n language to return. If not set, uses runtime default (en-US)
|
||||
* @return {Promise<Object>} - the message catalog
|
||||
* @memberof RED.nodes
|
||||
*/
|
||||
getModuleCatalog: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
var namespace = opts.module;
|
||||
var lang = opts.lang;
|
||||
var prevLang = runtime.i18n.i.lng();
|
||||
// Trigger a load from disk of the language if it is not the default
|
||||
runtime.i18n.i.changeLanguage(lang, function(){
|
||||
var catalog = runtime.i18n.getResourceBundle(lang, namespace);
|
||||
resolve(catalog||{});
|
||||
});
|
||||
runtime.i18n.i.changeLanguage(prevLang);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 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());
|
||||
});
|
||||
|
||||
},
|
||||
/**
|
||||
* Gets a node icon
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.module - the id of the module requesting the icon
|
||||
* @param {String} opts.icon - the name of the icon
|
||||
* @return {Promise<Buffer>} - the icon file as a Buffer
|
||||
* @memberof RED.nodes
|
||||
*/
|
||||
getIcon: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
var iconPath = runtime.nodes.getNodeIconPath(opts.module,opts.icon);
|
||||
fs.readFile(iconPath,function(err,data) {
|
||||
if (err) {
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
}
|
||||
return resolve(data)
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
440
red/runtime-api/projects.js
Normal file
440
red/runtime-api/projects.js
Normal file
@@ -0,0 +1,440 @@
|
||||
/**
|
||||
* 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.projects
|
||||
*/
|
||||
|
||||
var runtime;
|
||||
|
||||
var api = module.exports = {
|
||||
init: function(_runtime) {
|
||||
runtime = _runtime;
|
||||
},
|
||||
available: function(opts) {
|
||||
return Promise.resolve(!!runtime.storage.projects);
|
||||
},
|
||||
|
||||
/**
|
||||
* List projects known to the runtime
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
listProjects: function(opts) {
|
||||
return runtime.storage.projects.listProjects(opts.user).then(function(list) {
|
||||
var active = runtime.storage.projects.getActiveProject(opts.user);
|
||||
var response = {
|
||||
projects: list
|
||||
};
|
||||
if (active) {
|
||||
response.active = active.name;
|
||||
}
|
||||
return response;
|
||||
}).catch(function(err) {
|
||||
err.status = 400;
|
||||
throw err;
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new project
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {Object} opts.project - the project information
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
createProject: function(opts) {
|
||||
return runtime.storage.projects.createProject(opts.user, opts.project)
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialises an empty project
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project to initialise
|
||||
* @param {Object} opts.project - the project information
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
initialiseProject: function(opts) {
|
||||
// Initialised set when creating default files for an empty repo
|
||||
return runtime.storage.projects.initialiseProject(opts.user, opts.id, opts.project)
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the active project
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @return {Promise<Object>} - the active project
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
getActiveProject: function(opts) {
|
||||
return Promise.resolve(runtime.storage.projects.getActiveProject(opts.user));
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project to activate
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
setActiveProject: function(opts) {
|
||||
var currentProject = runtime.storage.projects.getActiveProject(opts.user);
|
||||
if (!currentProject || opts.id !== currentProject.name) {
|
||||
return runtime.storage.projects.setActiveProject(opts.user, opts.id);
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a projects metadata
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project to get
|
||||
* @return {Promise<Object>} - the project metadata
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
getProject: function(opts) {
|
||||
return runtime.storage.projects.getProject(opts.user, opts.id)
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the metadata of an existing project
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project to update
|
||||
* @param {Object} opts.project - the project information
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
updateProject: function(opts) {
|
||||
return runtime.storage.projects.updateProject(opts.user, opts.id, opts.project);
|
||||
},
|
||||
|
||||
/**
|
||||
* Deletes a project
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project to update
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
deleteProject: function(opts) {
|
||||
return runtime.storage.projects.deleteProject(opts.user, opts.id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets current git status of a project
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {Boolean} opts.remote - whether to include status of remote repos
|
||||
* @return {Promise<Object>} - the project status
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
getStatus: function(opts) {
|
||||
return runtime.storage.projects.getStatus(opts.user, opts.id, opts.remote)
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a list of local branches
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {Boolean} opts.remote - whether to return remote branches (true) or local (false)
|
||||
* @return {Promise<Object>} - a list of the local branches
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
getBranches: function(opts) {
|
||||
return runtime.storage.projects.getBranches(opts.user, opts.id, opts.remote);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the status of a branch
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {String} opts.branch - the name of the branch
|
||||
* @return {Promise<Object>} - the status of the branch
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
getBranchStatus: function(opts) {
|
||||
return runtime.storage.projects.getBranchStatus(opts.user, opts.id, opts.branch);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the current local branch
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {String} opts.branch - the name of the branch
|
||||
* @param {Boolean} opts.create - whether to create the branch if it doesn't exist
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
setBranch: function(opts) {
|
||||
return runtime.storage.projects.setBranch(opts.user, opts.id, opts.branch, opts.create)
|
||||
},
|
||||
|
||||
/**
|
||||
* Deletes a branch
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {String} opts.branch - the name of the branch
|
||||
* @param {Boolean} opts.force - whether to force delete
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
deleteBranch: function(opts) {
|
||||
return runtime.storage.projects.deleteBranch(opts.user, opts.id, opts.branch, false, opts.force);
|
||||
},
|
||||
|
||||
/**
|
||||
* Commits the current staged files
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {String} opts.message - the message to associate with the commit
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
commit: function(opts) {
|
||||
return runtime.storage.projects.commit(opts.user, opts.id,{message: opts.message});
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the details of a single commit
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {String} opts.sha - the sha of the commit to return
|
||||
* @return {Promise<Object>} - the commit details
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
getCommit: function(opts) {
|
||||
return runtime.storage.projects.getCommit(opts.user, opts.id, opts.sha);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the commit history of the project
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {String} opts.limit - limit how many to return
|
||||
* @param {String} opts.before - id of the commit to work back from
|
||||
* @return {Promise<Array>} - an array of commits
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
getCommits: function(opts) {
|
||||
return runtime.storage.projects.getCommits(opts.user, opts.id, {
|
||||
limit: opts.limit || 20,
|
||||
before: opts.before
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Abort an in-progress merge
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
abortMerge: function(opts) {
|
||||
return runtime.storage.projects.abortMerge(opts.user, opts.id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Resolves a merge conflict
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {String} opts.path - the path of the file being merged
|
||||
* @param {String} opts.resolutions - how to resolve the merge conflict
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
resolveMerge: function(opts) {
|
||||
return runtime.storage.projects.resolveMerge(opts.user, opts.id, opts.path, opts.resolution);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a listing of the files in the project
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @return {Promise<Object>} - the file listing
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
getFiles: function(opts) {
|
||||
return runtime.storage.projects.getFiles(opts.user, opts.id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the contents of a file
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {String} opts.path - the path of the file
|
||||
* @param {String} opts.tree - the version control tree to use
|
||||
* @return {Promise<String>} - the content of the file
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
getFile: function(opts) {
|
||||
return runtime.storage.projects.getFile(opts.user, opts.id,opts.path,opts.tree);
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {String|Array} opts.path - the path of the file, or an array of paths
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
stageFile: function(opts) {
|
||||
return runtime.storage.projects.stageFile(opts.user, opts.id, opts.path);
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {String} opts.path - the path of the file. If not set, all staged files are unstaged
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
unstageFile: function(opts) {
|
||||
return runtime.storage.projects.unstageFile(opts.user, opts.id, opts.path);
|
||||
},
|
||||
|
||||
/**
|
||||
* Reverts changes to a file back to its commited version
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {String} opts.path - the path of the file
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
revertFile: function(opts) {
|
||||
return runtime.storage.projects.revertFile(opts.user, opts.id,opts.path)
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the diff of a file
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {String} opts.path - the path of the file
|
||||
* @param {String} opts.type - the type of diff
|
||||
* @return {Promise<Object>} - the requested diff
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
getFileDiff: function(opts) {
|
||||
return runtime.storage.projects.getFileDiff(opts.user, opts.id, opts.path, opts.type);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a list of the project remotes
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @return {Promise<Object>} - a list of project remotes
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
getRemotes: function(opts) {
|
||||
return runtime.storage.projects.getRemotes(opts.user, opts.id);
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {Object} opts.remote - the remote metadata
|
||||
* @param {String} opts.remote.name - the name of the remote
|
||||
* @param {String} opts.remote.url - the url of the remote
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
addRemote: function(opts) {
|
||||
return runtime.storage.projects.addRemote(opts.user, opts.id, opts.remote)
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove a project remote
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {String} opts.remote - the name of the remote
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
removeRemote: function(opts) {
|
||||
return runtime.storage.projects.removeRemote(opts.user, opts.id, opts.remote);
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {Object} opts.remote - the remote metadata
|
||||
* @param {String} opts.remote.name - the name of the remote
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
updateRemote: function(opts) {
|
||||
return runtime.storage.projects.updateRemote(opts.user, opts.id, opts.remote.name, opts.remote)
|
||||
},
|
||||
|
||||
/**
|
||||
* Pull changes from the remote
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
pull: function(opts) {
|
||||
return runtime.storage.projects.pull(opts.user, opts.id, opts.remote, opts.track, opts.allowUnrelatedHistories);
|
||||
},
|
||||
|
||||
/**
|
||||
* Push changes to a remote
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {String} opts.remote - the name of the remote
|
||||
* @param {String} opts.track - whether to set the remote as the upstream
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
push: function(opts) {
|
||||
return runtime.storage.projects.push(opts.user, opts.id, opts.remote, opts.track);
|
||||
}
|
||||
|
||||
}
|
||||
267
red/runtime-api/settings.js
Normal file
267
red/runtime-api/settings.js
Normal file
@@ -0,0 +1,267 @@
|
||||
/**
|
||||
* 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 getSSHKeyUsername(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
|
||||
}
|
||||
if (opts.user) {
|
||||
safeSettings.user = {}
|
||||
var props = ["anonymous","username","image","permissions"];
|
||||
props.forEach(prop => {
|
||||
if (opts.user.hasOwnProperty(prop)) {
|
||||
safeSettings.user[prop] = opts.user[prop];
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
safeSettings.context = runtime.nodes.listContextStores();
|
||||
|
||||
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 = getSSHKeyUsername(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 = getSSHKeyUsername(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 = getSSHKeyUsername(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 = getSSHKeyUsername(req.user);
|
||||
runtime.storage.projects.ssh.deleteSSHKey(username, opts.id).then(function() {
|
||||
return resolve();
|
||||
}).catch(function(err) {
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
@@ -14,22 +14,19 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var when = require("when");
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
|
||||
var events = require("../../events");
|
||||
var registry = require("./registry");
|
||||
var loader = require("./loader");
|
||||
var installer = require("./installer");
|
||||
var library = require("./library");
|
||||
|
||||
var settings;
|
||||
|
||||
function init(runtime) {
|
||||
settings = runtime.settings;
|
||||
installer.init(runtime.settings);
|
||||
installer.init(runtime);
|
||||
loader.init(runtime);
|
||||
registry.init(settings,loader);
|
||||
registry.init(settings,loader,runtime.events);
|
||||
library.init();
|
||||
}
|
||||
|
||||
function load() {
|
||||
@@ -51,7 +48,7 @@ function enableNodeSet(typeOrId) {
|
||||
return registry.getNodeInfo(typeOrId);
|
||||
});
|
||||
}
|
||||
return when.resolve(nodeSet);
|
||||
return Promise.resolve(nodeSet);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -84,5 +81,11 @@ module.exports = {
|
||||
|
||||
cleanModuleList: registry.cleanModuleList,
|
||||
|
||||
paletteEditorEnabled: installer.paletteEditorEnabled
|
||||
paletteEditorEnabled: installer.paletteEditorEnabled,
|
||||
|
||||
getNodeExampleFlows: library.getExampleFlows,
|
||||
getNodeExampleFlowPath: library.getExampleFlowPath,
|
||||
|
||||
deprecated: require("./deprecated")
|
||||
|
||||
};
|
||||
@@ -19,9 +19,10 @@ var path = require("path");
|
||||
var fs = require("fs");
|
||||
|
||||
var registry = require("./registry");
|
||||
var log = require("../../log");
|
||||
var library = require("./library");
|
||||
var log;
|
||||
|
||||
var events = require("../../events");
|
||||
var events;
|
||||
|
||||
var child_process = require('child_process');
|
||||
var npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
||||
@@ -31,8 +32,10 @@ var settings;
|
||||
var moduleRe = /^(@[^/]+?[/])?[^/]+?$/;
|
||||
var slashRe = process.platform === "win32" ? /\\|[/]/ : /[/]/;
|
||||
|
||||
function init(_settings) {
|
||||
settings = _settings;
|
||||
function init(runtime) {
|
||||
events = runtime.events;
|
||||
settings = runtime.settings;
|
||||
log = runtime.log;
|
||||
}
|
||||
|
||||
var activePromise = Promise.resolve();
|
||||
@@ -219,8 +222,7 @@ function uninstallModule(module) {
|
||||
} else {
|
||||
log.info(log._("server.install.uninstalled",{name:module}));
|
||||
reportRemovedModules(list);
|
||||
// TODO: tidy up internal event names
|
||||
events.emit("node-module-uninstalled",module)
|
||||
library.removeExamplesDir(module);
|
||||
resolve(list);
|
||||
}
|
||||
}
|
||||
@@ -59,11 +59,11 @@ function getFlowsFromPath(path) {
|
||||
})
|
||||
}
|
||||
|
||||
function addNodeExamplesDir(module) {
|
||||
exampleRoots[module.name] = module.path;
|
||||
getFlowsFromPath(module.path).then(function(result) {
|
||||
function addNodeExamplesDir(module,path) {
|
||||
exampleRoots[module] = path;
|
||||
return getFlowsFromPath(path).then(function(result) {
|
||||
exampleFlows = exampleFlows||{d:{}};
|
||||
exampleFlows.d[module.name] = result;
|
||||
exampleFlows.d[module] = result;
|
||||
});
|
||||
}
|
||||
function removeNodeExamplesDir(module) {
|
||||
@@ -77,17 +77,9 @@ function removeNodeExamplesDir(module) {
|
||||
}
|
||||
|
||||
|
||||
function init(_runtime) {
|
||||
|
||||
runtime = _runtime;
|
||||
|
||||
function init() {
|
||||
exampleRoots = {};
|
||||
exampleFlows = null;
|
||||
|
||||
runtime.events.removeListener("node-examples-dir",addNodeExamplesDir);
|
||||
runtime.events.on("node-examples-dir",addNodeExamplesDir);
|
||||
runtime.events.removeListener("node-module-uninstalled",removeNodeExamplesDir);
|
||||
runtime.events.on("node-module-uninstalled",removeNodeExamplesDir);
|
||||
}
|
||||
|
||||
function getExampleFlows() {
|
||||
@@ -103,6 +95,8 @@ function getExampleFlowPath(module,path) {
|
||||
|
||||
module.exports = {
|
||||
init: init,
|
||||
addExamplesDir: addNodeExamplesDir,
|
||||
removeExamplesDir: removeNodeExamplesDir,
|
||||
getExampleFlows: getExampleFlows,
|
||||
getExampleFlowPath: getExampleFlowPath
|
||||
}
|
||||
@@ -22,6 +22,8 @@ var semver = require("semver");
|
||||
var localfilesystem = require("./localfilesystem");
|
||||
var registry = require("./registry");
|
||||
|
||||
var i18n = require("../util").i18n; // TODO: separate module
|
||||
|
||||
var settings;
|
||||
var runtime;
|
||||
|
||||
@@ -82,7 +84,21 @@ function createNodeApi(node) {
|
||||
events: runtime.events,
|
||||
util: runtime.util,
|
||||
version: runtime.version,
|
||||
require: requireModule
|
||||
require: requireModule,
|
||||
comms: {
|
||||
publish: function(topic,data,retain) {
|
||||
runtime.events.emit("comms",{
|
||||
topic: topic,
|
||||
data: data,
|
||||
retain: retain
|
||||
})
|
||||
}
|
||||
},
|
||||
library: {
|
||||
register: function(type) {
|
||||
return runtime.library.register(node.id,type);
|
||||
}
|
||||
}
|
||||
}
|
||||
copyObjectProperties(runtime.nodes,red.nodes,["createNode","getNode","eachNode","addCredentials","getCredentials","deleteCredentials" ]);
|
||||
red.nodes.registerType = function(type,constructor,opts) {
|
||||
@@ -91,8 +107,6 @@ function createNodeApi(node) {
|
||||
copyObjectProperties(runtime.log,red.log,null,["init"]);
|
||||
copyObjectProperties(runtime.settings,red.settings,null,["init","load","reset"]);
|
||||
if (runtime.adminApi) {
|
||||
red.comms = runtime.adminApi.comms;
|
||||
red.library = runtime.adminApi.library;
|
||||
red.auth = runtime.adminApi.auth;
|
||||
red.httpAdmin = runtime.adminApi.adminApp;
|
||||
red.httpNode = runtime.nodeApp;
|
||||
@@ -100,12 +114,6 @@ function createNodeApi(node) {
|
||||
} else {
|
||||
//TODO: runtime.adminApi is always stubbed if not enabled, so this block
|
||||
// is unused - but may be needed for the unit tests
|
||||
red.comms = {
|
||||
publish: function() {}
|
||||
};
|
||||
red.library = {
|
||||
register: function() {}
|
||||
};
|
||||
red.auth = {
|
||||
needsPermission: function() {}
|
||||
};
|
||||
@@ -116,7 +124,7 @@ function createNodeApi(node) {
|
||||
if (args[0].indexOf(":") === -1) {
|
||||
args[0] = node.namespace+":"+args[0];
|
||||
}
|
||||
return runtime.i18n._.apply(null,args);
|
||||
return i18n._.apply(null,args);
|
||||
}
|
||||
return red;
|
||||
}
|
||||
@@ -257,7 +265,7 @@ function loadNodeConfig(fileInfo) {
|
||||
index = regExp.lastIndex;
|
||||
var help = content.substring(regExp.lastIndex-match[1].length,regExp.lastIndex);
|
||||
|
||||
var lang = runtime.i18n.defaultLang;
|
||||
var lang = i18n.defaultLang;
|
||||
if ((match = langRegExp.exec(help)) !== null) {
|
||||
lang = match[1];
|
||||
}
|
||||
@@ -288,7 +296,7 @@ function loadNodeConfig(fileInfo) {
|
||||
fs.stat(path.join(path.dirname(file),"locales"),function(err,stat) {
|
||||
if (!err) {
|
||||
node.namespace = node.id;
|
||||
runtime.i18n.registerMessageCatalog(node.id,
|
||||
i18n.registerMessageCatalog(node.id,
|
||||
path.join(path.dirname(file),"locales"),
|
||||
path.basename(file,".js")+".json")
|
||||
.then(function() {
|
||||
@@ -432,10 +440,10 @@ function getNodeHelp(node,lang) {
|
||||
}
|
||||
if (help) {
|
||||
node.help[lang] = help;
|
||||
} else if (lang === runtime.i18n.defaultLang) {
|
||||
} else if (lang === i18n.defaultLang) {
|
||||
return null;
|
||||
} else {
|
||||
node.help[lang] = getNodeHelp(node, runtime.i18n.defaultLang);
|
||||
node.help[lang] = getNodeHelp(node, i18n.defaultLang);
|
||||
}
|
||||
}
|
||||
return node.help[lang];
|
||||
@@ -19,7 +19,8 @@ var path = require("path");
|
||||
|
||||
var events;
|
||||
var log;
|
||||
var i18n;
|
||||
|
||||
var i18n = require("../util").i18n; // TODO: separate module
|
||||
|
||||
var settings;
|
||||
var disableNodePathScan = false;
|
||||
@@ -29,7 +30,6 @@ function init(runtime) {
|
||||
settings = runtime.settings;
|
||||
events = runtime.events;
|
||||
log = runtime.log;
|
||||
i18n = runtime.i18n;
|
||||
}
|
||||
|
||||
function isIncluded(name) {
|
||||
@@ -228,7 +228,8 @@ function getModuleNodeFiles(module) {
|
||||
var examplesDir = path.join(moduleDir,"examples");
|
||||
try {
|
||||
fs.statSync(examplesDir)
|
||||
events.emit("node-examples-dir",{name:pkg.name,path:examplesDir});
|
||||
result.examples = {path:examplesDir};
|
||||
// events.emit("node-examples-dir",{name:pkg.name,path:examplesDir});
|
||||
} catch(err) {
|
||||
}
|
||||
return result;
|
||||
@@ -240,7 +241,7 @@ function getNodeFiles(disableNodePathScan) {
|
||||
var nodeFiles = [];
|
||||
var results;
|
||||
|
||||
var dir = path.resolve(__dirname + '/../../../../public/icons');
|
||||
var dir = path.resolve(__dirname + '/../../public/icons');
|
||||
var iconList = [{path:dir,icons:scanIconDir(dir)}];
|
||||
|
||||
if (settings.coreNodesDir) {
|
||||
@@ -298,7 +299,8 @@ function getNodeFiles(disableNodePathScan) {
|
||||
path: moduleFile.dir,
|
||||
local: moduleFile.local||false,
|
||||
nodes: {},
|
||||
icons: nodeModuleFiles.icons
|
||||
icons: nodeModuleFiles.icons,
|
||||
examples: nodeModuleFiles.examples
|
||||
};
|
||||
if (moduleFile.package['node-red'].version) {
|
||||
nodeList[moduleFile.package.name].redVersion = moduleFile.package['node-red'].version;
|
||||
@@ -331,7 +333,8 @@ function getModuleFiles(module) {
|
||||
name: moduleFile.package.name,
|
||||
version: moduleFile.package.version,
|
||||
nodes: {},
|
||||
icons: nodeModuleFiles.icons
|
||||
icons: nodeModuleFiles.icons,
|
||||
examples: nodeModuleFiles.examples
|
||||
};
|
||||
if (moduleFile.package['node-red'].version) {
|
||||
nodeList[moduleFile.package.name].redVersion = moduleFile.package['node-red'].version;
|
||||
@@ -15,17 +15,13 @@
|
||||
**/
|
||||
|
||||
//var UglifyJS = require("uglify-js");
|
||||
var util = require("util");
|
||||
var when = require("when");
|
||||
var path = require("path");
|
||||
var fs = require("fs");
|
||||
|
||||
var events = require("../../events");
|
||||
var library = require("./library");
|
||||
|
||||
var events;
|
||||
var settings;
|
||||
|
||||
var Node;
|
||||
|
||||
var loader;
|
||||
|
||||
var nodeConfigCache = null;
|
||||
@@ -35,18 +31,15 @@ var nodeConstructors = {};
|
||||
var nodeTypeToId = {};
|
||||
var moduleNodes = {};
|
||||
|
||||
function init(_settings,_loader) {
|
||||
function init(_settings,_loader, _events) {
|
||||
settings = _settings;
|
||||
loader = _loader;
|
||||
events = _events;
|
||||
moduleNodes = {};
|
||||
nodeTypeToId = {};
|
||||
nodeConstructors = {};
|
||||
nodeList = [];
|
||||
nodeConfigCache = null;
|
||||
Node = require("../Node");
|
||||
events.removeListener("node-icon-dir",nodeIconDir);
|
||||
events.on("node-icon-dir",nodeIconDir);
|
||||
|
||||
}
|
||||
|
||||
function load() {
|
||||
@@ -133,7 +126,7 @@ function saveNodeList() {
|
||||
if (settings.available()) {
|
||||
return settings.set("nodes",moduleList);
|
||||
} else {
|
||||
return when.reject("Settings unavailable");
|
||||
return Promise.reject("Settings unavailable");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,6 +207,9 @@ function addModule(module) {
|
||||
icon_paths[module.name] = [];
|
||||
module.icons.forEach(icon=>icon_paths[module.name].push(path.resolve(icon.path)) )
|
||||
}
|
||||
if (module.examples) {
|
||||
library.addExamplesDir(module.name,module.examples.path);
|
||||
}
|
||||
nodeConfigCache = null;
|
||||
}
|
||||
|
||||
@@ -369,27 +365,6 @@ function getCaller(){
|
||||
return stack[0].getFileName();
|
||||
}
|
||||
|
||||
function inheritNode(constructor) {
|
||||
if(Object.getPrototypeOf(constructor.prototype) === Object.prototype) {
|
||||
util.inherits(constructor,Node);
|
||||
} else {
|
||||
var proto = constructor.prototype;
|
||||
while(Object.getPrototypeOf(proto) !== Object.prototype) {
|
||||
proto = Object.getPrototypeOf(proto);
|
||||
}
|
||||
//TODO: This is a partial implementation of util.inherits >= node v5.0.0
|
||||
// which should be changed when support for node < v5.0.0 is dropped
|
||||
// see: https://github.com/nodejs/node/pull/3455
|
||||
proto.constructor.super_ = Node;
|
||||
if(Object.setPrototypeOf) {
|
||||
Object.setPrototypeOf(proto, Node.prototype);
|
||||
} else {
|
||||
// hack for node v0.10
|
||||
proto.__proto__ = Node.prototype;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function registerNodeConstructor(nodeSet,type,constructor) {
|
||||
if (nodeConstructors.hasOwnProperty(type)) {
|
||||
throw new Error(type+" already registered");
|
||||
@@ -397,9 +372,6 @@ function registerNodeConstructor(nodeSet,type,constructor) {
|
||||
//TODO: Ensure type is known - but doing so will break some tests
|
||||
// that don't have a way to register a node template ahead
|
||||
// of registering the constructor
|
||||
if(!(constructor.prototype instanceof Node)) {
|
||||
inheritNode(constructor);
|
||||
}
|
||||
|
||||
var nodeSetInfo = getFullNodeInfo(nodeSet);
|
||||
if (nodeSetInfo) {
|
||||
@@ -584,37 +556,15 @@ function setModulePendingUpdated(module,version) {
|
||||
}
|
||||
|
||||
var icon_paths = {
|
||||
"node-red":[path.resolve(__dirname + '/../../../../public/icons')]
|
||||
"node-red":[path.resolve(__dirname + '/../../public/icons')]
|
||||
};
|
||||
var iconCache = {};
|
||||
var defaultIcon = path.resolve(__dirname + '/../../../../public/icons/arrow-in.png');
|
||||
|
||||
function nodeIconDir(dir) {
|
||||
return;
|
||||
icon_paths[dir.name] = icon_paths[dir.name] || [];
|
||||
icon_paths[dir.name].push(path.resolve(dir.path));
|
||||
|
||||
if (dir.icons) {
|
||||
if (!moduleConfigs[dir.name]) {
|
||||
moduleConfigs[dir.name] = {
|
||||
name: dir.name,
|
||||
nodes: {},
|
||||
icons: []
|
||||
};
|
||||
}
|
||||
var module = moduleConfigs[dir.name];
|
||||
if (module.icons === undefined) {
|
||||
module.icons = [];
|
||||
}
|
||||
dir.icons.forEach(function(icon) {
|
||||
if (module.icons.indexOf(icon) === -1) {
|
||||
module.icons.push(icon);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
var defaultIcon = path.resolve(__dirname + '/../../public/icons/arrow-in.png');
|
||||
|
||||
function getNodeIconPath(module,icon) {
|
||||
if (/\.\./.test(icon)) {
|
||||
throw new Error();
|
||||
}
|
||||
var iconName = module+"/"+icon;
|
||||
if (iconCache[iconName]) {
|
||||
return iconCache[iconName];
|
||||
@@ -647,7 +597,7 @@ function getNodeIcons() {
|
||||
if (moduleConfigs.hasOwnProperty(module)) {
|
||||
if (moduleConfigs[module].icons) {
|
||||
iconList[module] = [];
|
||||
moduleConfigs[module].icons.forEach(icon=>{ iconList[module] = iconList[module].concat(icon.icons) });
|
||||
moduleConfigs[module].icons.forEach(icon=>{ iconList[module] = iconList[module].concat(icon.icons)})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,7 @@ var when = require('when');
|
||||
|
||||
var redNodes = require("./nodes");
|
||||
var storage = require("./storage");
|
||||
var log = require("./log");
|
||||
var i18n = require("./i18n");
|
||||
var library = require("./library");
|
||||
var events = require("./events");
|
||||
var settings = require("./settings");
|
||||
|
||||
@@ -28,6 +27,10 @@ var path = require('path');
|
||||
var fs = require("fs");
|
||||
var os = require("os");
|
||||
|
||||
var redUtil;
|
||||
var log;
|
||||
var i18n;
|
||||
|
||||
var runtimeMetricInterval = null;
|
||||
|
||||
var started = false;
|
||||
@@ -39,9 +42,6 @@ var stubbedExpressApp = {
|
||||
delete: function() {}
|
||||
}
|
||||
var adminApi = {
|
||||
library: {
|
||||
register: function() {}
|
||||
},
|
||||
auth: {
|
||||
needsPermission: function() {}
|
||||
},
|
||||
@@ -54,9 +54,12 @@ var adminApi = {
|
||||
|
||||
var nodeApp;
|
||||
|
||||
function init(userSettings,_adminApi) {
|
||||
function init(userSettings,_redUtil,_adminApi) {
|
||||
redUtil = _redUtil;
|
||||
log = redUtil.log;
|
||||
i18n = redUtil.i18n;
|
||||
|
||||
userSettings.version = getVersion();
|
||||
log.init(userSettings);
|
||||
settings.init(userSettings);
|
||||
|
||||
nodeApp = express();
|
||||
@@ -65,6 +68,7 @@ function init(userSettings,_adminApi) {
|
||||
adminApi = _adminApi;
|
||||
}
|
||||
redNodes.init(runtime);
|
||||
library.init(runtime);
|
||||
}
|
||||
|
||||
var version;
|
||||
@@ -84,10 +88,8 @@ function getVersion() {
|
||||
}
|
||||
|
||||
function start() {
|
||||
return i18n.init()
|
||||
.then(function() {
|
||||
return i18n.registerMessageCatalog("runtime",path.resolve(path.join(__dirname,"locales")),"runtime.json")
|
||||
})
|
||||
|
||||
return i18n.registerMessageCatalog("runtime",path.resolve(path.join(__dirname,"locales")),"runtime.json")
|
||||
.then(function() { return storage.init(runtime)})
|
||||
.then(function() { return settings.load(storage)})
|
||||
.then(function() {
|
||||
@@ -243,12 +245,13 @@ var runtime = module.exports = {
|
||||
|
||||
version: getVersion,
|
||||
|
||||
log: log,
|
||||
i18n: i18n,
|
||||
get log() { return log },
|
||||
get i18n() { return i18n },
|
||||
settings: settings,
|
||||
storage: storage,
|
||||
events: events,
|
||||
nodes: redNodes,
|
||||
library: library,
|
||||
util: require("./util"),
|
||||
get adminApi() { return adminApi },
|
||||
get nodeApp() { return nodeApp },
|
||||
|
||||
103
red/runtime/library/index.js
Normal file
103
red/runtime/library/index.js
Normal file
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 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.
|
||||
**/
|
||||
|
||||
var fs = require('fs');
|
||||
var fspath = require('path');
|
||||
|
||||
var runtime;
|
||||
var knownTypes = {};
|
||||
|
||||
var storage;
|
||||
|
||||
function init(_runtime) {
|
||||
runtime = _runtime;
|
||||
storage = runtime.storage;
|
||||
knownTypes = {};
|
||||
}
|
||||
|
||||
function registerType(id,type) {
|
||||
// TODO: would like to enforce this, but currently the tests register the same type multiple
|
||||
// times and have no way to remove themselves.
|
||||
// if (knownTypes.hasOwnProperty(type)) {
|
||||
// throw new Error(`Library type '${type}' already registered by ${id}'`)
|
||||
// }
|
||||
knownTypes[type] = id;
|
||||
}
|
||||
|
||||
// function getAllEntries(type) {
|
||||
// if (!knownTypes.hasOwnProperty(type)) {
|
||||
// throw new Error(`Unknown library type '${type}'`);
|
||||
// }
|
||||
// }
|
||||
|
||||
function getEntry(type,path) {
|
||||
if (type !== 'flows') {
|
||||
if (!knownTypes.hasOwnProperty(type)) {
|
||||
throw new Error(`Unknown library type '${type}'`);
|
||||
}
|
||||
return storage.getLibraryEntry(type,path);
|
||||
} else {
|
||||
return new Promise(function(resolve,reject) {
|
||||
if (path.indexOf("_examples_/") === 0) {
|
||||
var m = /^_examples_\/(@.*?\/[^\/]+|[^\/]+)\/(.*)$/.exec(path);
|
||||
if (m) {
|
||||
var module = m[1];
|
||||
var entryPath = m[2];
|
||||
var fullPath = runtime.nodes.getNodeExampleFlowPath(module,entryPath);
|
||||
if (fullPath) {
|
||||
try {
|
||||
fs.readFile(fullPath,'utf8',function(err, data) {
|
||||
runtime.log.audit({event: "library.get",type:"flow",path:path});
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
return resolve(data);
|
||||
})
|
||||
} catch(err) {
|
||||
return reject(err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
// IF we get here, we didn't find the file
|
||||
var error = new Error("not_found");
|
||||
error.code = "not_found";
|
||||
return reject(error);
|
||||
} else {
|
||||
resolve(storage.getFlow(path));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
function saveEntry(type,path,meta,body) {
|
||||
if (type !== 'flows') {
|
||||
if (!knownTypes.hasOwnProperty(type)) {
|
||||
throw new Error(`Unknown library type '${type}'`);
|
||||
}
|
||||
return storage.saveLibraryEntry(type,path,meta,body);
|
||||
} else {
|
||||
return storage.saveFlow(path,body);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: init,
|
||||
register: registerType,
|
||||
// getAllEntries: getAllEntries,
|
||||
getEntry: getEntry,
|
||||
saveEntry: saveEntry
|
||||
|
||||
}
|
||||
@@ -19,7 +19,7 @@ var EventEmitter = require("events").EventEmitter;
|
||||
var when = require("when");
|
||||
|
||||
var redUtil = require("../util");
|
||||
var Log = require("../log");
|
||||
var Log = require("../../util").log; // TODO: separate module
|
||||
var context = require("./context");
|
||||
var flows = require("./flows");
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
**/
|
||||
|
||||
var clone = require("clone");
|
||||
var log = require("../../log");
|
||||
var log = require("../../../util").log; // TODO: separate module
|
||||
var memory = require("./memory");
|
||||
|
||||
var settings;
|
||||
|
||||
@@ -20,6 +20,7 @@ var runtime;
|
||||
var settings;
|
||||
var log;
|
||||
|
||||
|
||||
var encryptedCredentials = null;
|
||||
var credentialCache = {};
|
||||
var credentialsDef = {};
|
||||
|
||||
@@ -16,10 +16,11 @@
|
||||
|
||||
var when = require("when");
|
||||
var clone = require("clone");
|
||||
var typeRegistry = require("../registry");
|
||||
var Log = require("../../log");
|
||||
var typeRegistry = require("../../../runtime-registry");
|
||||
var Log;
|
||||
var redUtil = require("../../util");
|
||||
var flowUtil = require("./util");
|
||||
var Node;
|
||||
|
||||
var nodeCloseTimeout = 15000;
|
||||
|
||||
@@ -292,6 +293,7 @@ function Flow(global,flow) {
|
||||
|
||||
function createNode(type,config) {
|
||||
var nn = null;
|
||||
try {
|
||||
var nt = typeRegistry.get(type);
|
||||
if (nt) {
|
||||
var conf = clone(config);
|
||||
@@ -315,6 +317,9 @@ function createNode(type,config) {
|
||||
} else {
|
||||
Log.error(Log._("nodes.flow.unknown-type", {type:type}));
|
||||
}
|
||||
} catch(err) {
|
||||
Log.error(err);
|
||||
}
|
||||
return nn;
|
||||
}
|
||||
|
||||
@@ -495,8 +500,10 @@ function createSubflow(sf,sfn,subflows,globalSubflows,activeNodes) {
|
||||
|
||||
|
||||
module.exports = {
|
||||
init: function(settings) {
|
||||
nodeCloseTimeout = settings.nodeCloseTimeout || 15000;
|
||||
init: function(runtime) {
|
||||
nodeCloseTimeout = runtime.settings.nodeCloseTimeout || 15000;
|
||||
Log = runtime.log;
|
||||
Node = require("../Node");
|
||||
},
|
||||
create: function(global,conf) {
|
||||
return new Flow(global,conf);
|
||||
|
||||
@@ -19,15 +19,17 @@ var when = require("when");
|
||||
|
||||
var Flow = require('./Flow');
|
||||
|
||||
var typeRegistry = require("../registry");
|
||||
var typeRegistry = require("../../../runtime-registry");
|
||||
var deprecated = typeRegistry.deprecated;
|
||||
|
||||
|
||||
var context = require("../context")
|
||||
var credentials = require("../credentials");
|
||||
|
||||
var flowUtil = require("./util");
|
||||
var log = require("../../log");
|
||||
var log;
|
||||
var events = require("../../events");
|
||||
var redUtil = require("../../util");
|
||||
var deprecated = require("../registry/deprecated");
|
||||
|
||||
var storage = null;
|
||||
var settings = null;
|
||||
@@ -50,6 +52,7 @@ function init(runtime) {
|
||||
}
|
||||
settings = runtime.settings;
|
||||
storage = runtime.storage;
|
||||
log = runtime.log;
|
||||
started = false;
|
||||
if (!typeEventRegistered) {
|
||||
events.on('type-registered',function(type) {
|
||||
@@ -67,7 +70,7 @@ function init(runtime) {
|
||||
});
|
||||
typeEventRegistered = true;
|
||||
}
|
||||
Flow.init(settings);
|
||||
Flow.init(runtime);
|
||||
}
|
||||
|
||||
function loadFlows() {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
var clone = require("clone");
|
||||
var redUtil = require("../../util");
|
||||
var subflowInstanceRE = /^subflow:(.+)$/;
|
||||
var typeRegistry = require("../registry");
|
||||
var typeRegistry = require("../../../runtime-registry");
|
||||
|
||||
function diffNodes(oldNode,newNode) {
|
||||
if (oldNode == null) {
|
||||
|
||||
@@ -18,15 +18,16 @@ var when = require("when");
|
||||
var path = require("path");
|
||||
var fs = require("fs");
|
||||
var clone = require("clone");
|
||||
var util = require("util");
|
||||
|
||||
var registry = require("../../runtime-registry");
|
||||
|
||||
var registry = require("./registry");
|
||||
var credentials = require("./credentials");
|
||||
var flows = require("./flows");
|
||||
var flowUtil = require("./flows/util")
|
||||
var context = require("./context");
|
||||
var Node = require("./Node");
|
||||
var log = null;
|
||||
var library = require("./library");
|
||||
var log;
|
||||
|
||||
var events = require("../events");
|
||||
|
||||
@@ -61,6 +62,26 @@ function registerType(nodeSet,type,constructor,opts) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!(constructor.prototype instanceof Node)) {
|
||||
if(Object.getPrototypeOf(constructor.prototype) === Object.prototype) {
|
||||
util.inherits(constructor,Node);
|
||||
} else {
|
||||
var proto = constructor.prototype;
|
||||
while(Object.getPrototypeOf(proto) !== Object.prototype) {
|
||||
proto = Object.getPrototypeOf(proto);
|
||||
}
|
||||
//TODO: This is a partial implementation of util.inherits >= node v5.0.0
|
||||
// which should be changed when support for node < v5.0.0 is dropped
|
||||
// see: https://github.com/nodejs/node/pull/3455
|
||||
proto.constructor.super_ = Node;
|
||||
if(Object.setPrototypeOf) {
|
||||
Object.setPrototypeOf(proto, Node.prototype);
|
||||
} else {
|
||||
// hack for node v0.10
|
||||
proto.__proto__ = Node.prototype;
|
||||
}
|
||||
}
|
||||
}
|
||||
registry.registerType(nodeSet,type,constructor);
|
||||
}
|
||||
|
||||
@@ -99,7 +120,6 @@ function init(runtime) {
|
||||
flows.init(runtime);
|
||||
registry.init(runtime);
|
||||
context.init(runtime.settings);
|
||||
library.init(runtime);
|
||||
}
|
||||
|
||||
function disableNode(id) {
|
||||
@@ -190,8 +210,8 @@ module.exports = {
|
||||
getNodeConfig: registry.getNodeConfig,
|
||||
getNodeIconPath: registry.getNodeIconPath,
|
||||
getNodeIcons: registry.getNodeIcons,
|
||||
getNodeExampleFlows: library.getExampleFlows,
|
||||
getNodeExampleFlowPath: library.getExampleFlowPath,
|
||||
getNodeExampleFlows: registry.getNodeExampleFlows,
|
||||
getNodeExampleFlowPath: registry.getNodeExampleFlowPath,
|
||||
|
||||
clearRegistry: registry.clear,
|
||||
cleanModuleList: registry.cleanModuleList,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
var when = require("when");
|
||||
var clone = require("clone");
|
||||
var assert = require("assert");
|
||||
var log = require("./log");
|
||||
var log = require("../util").log; // TODO: separate module
|
||||
var util = require("./util");
|
||||
|
||||
// localSettings are those provided in the runtime settings.js file
|
||||
|
||||
@@ -18,7 +18,7 @@ var when = require('when');
|
||||
var Path = require('path');
|
||||
var crypto = require('crypto');
|
||||
|
||||
var log = require("../log");
|
||||
var log = require("../../util").log; // TODO: separate module
|
||||
|
||||
var runtime;
|
||||
var storageModule;
|
||||
|
||||
@@ -18,7 +18,8 @@ var fs = require('fs-extra');
|
||||
var when = require('when');
|
||||
var fspath = require("path");
|
||||
|
||||
var log = require("../../log");
|
||||
var log = require("../../../util").log; // TODO: separate module
|
||||
|
||||
var util = require("./util");
|
||||
var library = require("./library");
|
||||
var sessions = require("./sessions");
|
||||
|
||||
@@ -107,6 +107,11 @@ function getSSHKey(username, name) {
|
||||
function generateSSHKey(username, options) {
|
||||
options = options || {};
|
||||
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)
|
||||
.then(function(result) {
|
||||
if ( result ) {
|
||||
|
||||
@@ -18,7 +18,8 @@ var when = require('when');
|
||||
var fs = require('fs-extra');
|
||||
var fspath = require("path");
|
||||
|
||||
var log = require("../../log");
|
||||
var log = require("../../../util").log; // TODO: separate module
|
||||
|
||||
var util = require("./util");
|
||||
|
||||
var sessionsFile;
|
||||
|
||||
@@ -18,7 +18,7 @@ var when = require('when');
|
||||
var fs = require('fs-extra');
|
||||
var fspath = require("path");
|
||||
|
||||
var log = require("../../log");
|
||||
var log = require("../../../util").log; // TODO: separate module
|
||||
var util = require("./util");
|
||||
|
||||
var globalSettingsFile;
|
||||
|
||||
@@ -18,7 +18,7 @@ var fs = require('fs-extra');
|
||||
var when = require('when');
|
||||
var nodeFn = require('when/node/function');
|
||||
|
||||
var log = require("../../log");
|
||||
var log = require("../../../util").log; // TODO: separate module
|
||||
|
||||
function parseJSON(data) {
|
||||
if (data.charCodeAt(0) === 0xFEFF) {
|
||||
|
||||
@@ -24,6 +24,7 @@ var defaultLang = "en-US";
|
||||
|
||||
var resourceMap = {};
|
||||
var resourceCache = {};
|
||||
var initPromise;
|
||||
|
||||
function registerMessageCatalogs(catalogs) {
|
||||
var promises = catalogs.map(function(catalog) {
|
||||
@@ -33,10 +34,12 @@ function registerMessageCatalogs(catalogs) {
|
||||
}
|
||||
|
||||
function registerMessageCatalog(namespace,dir,file) {
|
||||
return when.promise(function(resolve,reject) {
|
||||
resourceMap[namespace] = { basedir:dir, file:file};
|
||||
i18n.loadNamespaces(namespace,function() {
|
||||
resolve();
|
||||
return initPromise.then(function() {
|
||||
return new Promise((resolve,reject) => {
|
||||
resourceMap[namespace] = { basedir:dir, file:file};
|
||||
i18n.loadNamespaces(namespace,function() {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -96,28 +99,32 @@ function getCurrentLocale() {
|
||||
}
|
||||
|
||||
function init() {
|
||||
return when.promise(function(resolve,reject) {
|
||||
i18n.use(MessageFileLoader);
|
||||
var opt = {
|
||||
// debug: true,
|
||||
defaultNS: "runtime",
|
||||
ns: [],
|
||||
fallbackLng: defaultLang,
|
||||
interpolation: {
|
||||
unescapeSuffix: 'HTML',
|
||||
escapeValue: false,
|
||||
prefix: '__',
|
||||
suffix: '__'
|
||||
if (!initPromise) {
|
||||
// Keep this as a 'when' promise as top-level red.js uses 'otherwise'
|
||||
// and embedded users of NR may have copied that.
|
||||
initPromise = when.promise((resolve,reject) => {
|
||||
i18n.use(MessageFileLoader);
|
||||
var opt = {
|
||||
// debug: true,
|
||||
defaultNS: "runtime",
|
||||
ns: [],
|
||||
fallbackLng: defaultLang,
|
||||
interpolation: {
|
||||
unescapeSuffix: 'HTML',
|
||||
escapeValue: false,
|
||||
prefix: '__',
|
||||
suffix: '__'
|
||||
}
|
||||
};
|
||||
var lang = getCurrentLocale();
|
||||
if (lang) {
|
||||
opt.lng = lang;
|
||||
}
|
||||
};
|
||||
var lang = getCurrentLocale();
|
||||
if (lang) {
|
||||
opt.lng = lang;
|
||||
}
|
||||
i18n.init(opt ,function() {
|
||||
resolve();
|
||||
i18n.init(opt ,function() {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getCatalog(namespace,lang) {
|
||||
28
red/util/index.js
Normal file
28
red/util/index.js
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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.
|
||||
**/
|
||||
|
||||
|
||||
var log = require("./log");
|
||||
var i18n = require("./i18n");
|
||||
|
||||
module.exports = {
|
||||
init: function(settings) {
|
||||
log.init(settings);
|
||||
i18n.init();
|
||||
},
|
||||
log: log,
|
||||
i18n: i18n
|
||||
}
|
||||
@@ -26,6 +26,9 @@ var Context = require("../../../../red/runtime/nodes/context");
|
||||
var Util = require("../../../../red/runtime/util");
|
||||
|
||||
describe("api/admin/context", function() {
|
||||
it.skip("NEEDS TESTS WRITING",function() {});
|
||||
});
|
||||
/*
|
||||
var app = undefined;
|
||||
|
||||
before(function (done) {
|
||||
@@ -58,7 +61,7 @@ describe("api/admin/context", function() {
|
||||
},
|
||||
util: Util
|
||||
});
|
||||
|
||||
|
||||
Context.init({
|
||||
contextStorage: {
|
||||
memory0: {
|
||||
@@ -103,7 +106,7 @@ describe("api/admin/context", function() {
|
||||
function check_scope(scope, prefix, id) {
|
||||
describe('# '+scope, function () {
|
||||
var xid = id ? ("/"+id) : "";
|
||||
|
||||
|
||||
it('should return '+scope+' contexts', function (done) {
|
||||
request(app)
|
||||
.get('/context/'+scope+xid)
|
||||
@@ -229,3 +232,4 @@ describe("api/admin/context", function() {
|
||||
});
|
||||
|
||||
});
|
||||
*/
|
||||
|
||||
@@ -38,18 +38,23 @@ describe("api/admin/flow", function() {
|
||||
|
||||
describe("get", function() {
|
||||
before(function() {
|
||||
var opts;
|
||||
flow.init({
|
||||
settings:{},
|
||||
nodes: {
|
||||
getFlow: function(id) {
|
||||
if (id === '123') {
|
||||
return {id:'123'}
|
||||
flows: {
|
||||
getFlow: function(_opts) {
|
||||
opts = _opts;
|
||||
if (opts.id === '123') {
|
||||
return Promise.resolve({id:'123'});
|
||||
} else {
|
||||
return null;
|
||||
var err = new Error("message");
|
||||
err.code = "not_found";
|
||||
err.status = 404;
|
||||
var p = Promise.reject(err);
|
||||
p.catch(()=>{});
|
||||
return p;
|
||||
}
|
||||
}
|
||||
},
|
||||
log:{ audit: sinon.stub() }
|
||||
}
|
||||
});
|
||||
})
|
||||
it('gets a known flow', function(done) {
|
||||
@@ -75,19 +80,24 @@ describe("api/admin/flow", function() {
|
||||
});
|
||||
|
||||
describe("add", function() {
|
||||
var opts;
|
||||
before(function() {
|
||||
flow.init({
|
||||
settings:{},
|
||||
nodes: {
|
||||
addFlow: function(f) {
|
||||
if (f.id === "123") {
|
||||
return when.resolve('123')
|
||||
flows: {
|
||||
addFlow: function(_opts) {
|
||||
opts = _opts;
|
||||
if (opts.flow.id === "123") {
|
||||
return Promise.resolve('123')
|
||||
} else {
|
||||
return when.reject(new Error("test error"));
|
||||
var err = new Error("random error");
|
||||
err.code = "random_error";
|
||||
err.status = 400;
|
||||
var p = Promise.reject(err);
|
||||
p.catch(()=>{});
|
||||
return p;
|
||||
}
|
||||
}
|
||||
},
|
||||
log:{ audit: sinon.stub() }
|
||||
}
|
||||
});
|
||||
})
|
||||
it('adds a new flow', function(done) {
|
||||
@@ -114,8 +124,8 @@ describe("api/admin/flow", function() {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.has.a.property('error','unexpected_error');
|
||||
res.body.should.has.a.property('message','Error: test error');
|
||||
res.body.should.has.a.property('code','random_error');
|
||||
res.body.should.has.a.property('message','random error');
|
||||
|
||||
done();
|
||||
});
|
||||
@@ -123,35 +133,29 @@ describe("api/admin/flow", function() {
|
||||
})
|
||||
|
||||
describe("update", function() {
|
||||
var nodes;
|
||||
|
||||
var opts;
|
||||
before(function() {
|
||||
nodes = {
|
||||
updateFlow: function(id,f) {
|
||||
var err;
|
||||
if (id === "123") {
|
||||
return when.resolve()
|
||||
} else if (id === "unknown") {
|
||||
err = new Error();
|
||||
err.code = 404;
|
||||
throw err;
|
||||
} else if (id === "unexpected") {
|
||||
err = new Error();
|
||||
err.code = 500;
|
||||
throw err;
|
||||
} else {
|
||||
return when.reject(new Error("test error"));
|
||||
flow.init({
|
||||
flows: {
|
||||
updateFlow: function(_opts) {
|
||||
opts = _opts;
|
||||
if (opts.id === "123") {
|
||||
return Promise.resolve('123')
|
||||
} else {
|
||||
var err = new Error("random error");
|
||||
err.code = "random_error";
|
||||
err.status = 400;
|
||||
var p = Promise.reject(err);
|
||||
p.catch(()=>{});
|
||||
return p;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
flow.init({
|
||||
settings:{},
|
||||
nodes: nodes,
|
||||
log:{ audit: sinon.stub() }
|
||||
});
|
||||
})
|
||||
|
||||
it('updates an existing flow', function(done) {
|
||||
sinon.spy(nodes,"updateFlow");
|
||||
request(app)
|
||||
.put('/flow/123')
|
||||
.set('Accept', 'application/json')
|
||||
@@ -162,115 +166,79 @@ describe("api/admin/flow", function() {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.has.a.property('id','123');
|
||||
nodes.updateFlow.calledOnce.should.be.true();
|
||||
nodes.updateFlow.lastCall.args[0].should.eql('123');
|
||||
nodes.updateFlow.lastCall.args[1].should.eql({id:'123'});
|
||||
nodes.updateFlow.restore();
|
||||
opts.should.have.property('id','123');
|
||||
opts.should.have.property('flow',{id:'123'})
|
||||
done();
|
||||
});
|
||||
})
|
||||
|
||||
it('404s on an unknown flow', function(done) {
|
||||
it('400 an invalid flow', function(done) {
|
||||
request(app)
|
||||
.put('/flow/unknown')
|
||||
.put('/flow/456')
|
||||
.set('Accept', 'application/json')
|
||||
.send({id:'123'})
|
||||
.expect(404)
|
||||
.end(done);
|
||||
})
|
||||
|
||||
it('400 on async update error', function(done) {
|
||||
request(app)
|
||||
.put('/flow/async_error')
|
||||
.set('Accept', 'application/json')
|
||||
.send({id:'123'})
|
||||
.send({id:'456'})
|
||||
.expect(400)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.has.a.property('error','unexpected_error');
|
||||
res.body.should.has.a.property('message','Error: test error');
|
||||
done();
|
||||
});
|
||||
})
|
||||
res.body.should.has.a.property('code','random_error');
|
||||
res.body.should.has.a.property('message','random error');
|
||||
|
||||
it('400 on sync update error', function(done) {
|
||||
request(app)
|
||||
.put('/flow/unexpected')
|
||||
.set('Accept', 'application/json')
|
||||
.send({id:'123'})
|
||||
.expect(400)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.has.a.property('error',500);
|
||||
res.body.should.has.a.property('message','Error');
|
||||
done();
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
describe("delete", function() {
|
||||
var nodes;
|
||||
|
||||
var opts;
|
||||
before(function() {
|
||||
nodes = {
|
||||
removeFlow: function(id) {
|
||||
var err;
|
||||
if (id === "123") {
|
||||
return when.resolve()
|
||||
} else if (id === "unknown") {
|
||||
err = new Error();
|
||||
err.code = 404;
|
||||
throw err;
|
||||
} else if (id === "unexpected") {
|
||||
err = new Error();
|
||||
err.code = 500;
|
||||
throw err;
|
||||
flow.init({
|
||||
flows: {
|
||||
deleteFlow: function(_opts) {
|
||||
opts = _opts;
|
||||
if (opts.id === "123") {
|
||||
return Promise.resolve()
|
||||
} else {
|
||||
var err = new Error("random error");
|
||||
err.code = "random_error";
|
||||
err.status = 400;
|
||||
var p = Promise.reject(err);
|
||||
p.catch(()=>{});
|
||||
return p;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
flow.init({
|
||||
settings:{},
|
||||
nodes: nodes,
|
||||
log:{ audit: sinon.stub() }
|
||||
});
|
||||
})
|
||||
|
||||
it('updates an existing flow', function(done) {
|
||||
sinon.spy(nodes,"removeFlow");
|
||||
it('deletes an existing flow', function(done) {
|
||||
request(app)
|
||||
.delete('/flow/123')
|
||||
.del('/flow/123')
|
||||
.set('Accept', 'application/json')
|
||||
.expect(204)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
nodes.removeFlow.calledOnce.should.be.true();
|
||||
nodes.removeFlow.lastCall.args[0].should.eql('123');
|
||||
nodes.removeFlow.restore();
|
||||
opts.should.have.property('id','123');
|
||||
done();
|
||||
});
|
||||
})
|
||||
|
||||
it('404s on an unknown flow', function(done) {
|
||||
it('400 an invalid flow', function(done) {
|
||||
request(app)
|
||||
.delete('/flow/unknown')
|
||||
.expect(404)
|
||||
.end(done);
|
||||
})
|
||||
|
||||
it('400 on remove error', function(done) {
|
||||
request(app)
|
||||
.delete('/flow/unexpected')
|
||||
.del('/flow/456')
|
||||
.set('Accept', 'application/json')
|
||||
.expect(400)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.has.a.property('error',500);
|
||||
res.body.should.has.a.property('message','Error');
|
||||
res.body.should.has.a.property('code','random_error');
|
||||
res.body.should.has.a.property('message','random error');
|
||||
|
||||
done();
|
||||
});
|
||||
})
|
||||
|
||||
@@ -19,7 +19,6 @@ var request = require('supertest');
|
||||
var express = require('express');
|
||||
var bodyParser = require('body-parser');
|
||||
var sinon = require('sinon');
|
||||
var when = require('when');
|
||||
|
||||
var flows = require("../../../../red/api/admin/flows");
|
||||
|
||||
@@ -36,10 +35,8 @@ describe("api/admin/flows", function() {
|
||||
|
||||
it('returns flow - v1', function(done) {
|
||||
flows.init({
|
||||
settings: {},
|
||||
log:{warn:function(){},_:function(){},audit:function(){}},
|
||||
nodes:{
|
||||
getFlows: function() { return {rev:"123",flows:[1,2,3]}; }
|
||||
flows:{
|
||||
getFlows: function() { return Promise.resolve({rev:"123",flows:[1,2,3]}); }
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
@@ -60,10 +57,8 @@ describe("api/admin/flows", function() {
|
||||
});
|
||||
it('returns flow - v2', function(done) {
|
||||
flows.init({
|
||||
settings: {},
|
||||
log:{warn:function(){},_:function(){},audit:function(){}},
|
||||
nodes:{
|
||||
getFlows: function() { return {rev:"123",flows:[1,2,3]}; }
|
||||
flows:{
|
||||
getFlows: function() { return Promise.resolve({rev:"123",flows:[1,2,3]}); }
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
@@ -104,10 +99,9 @@ describe("api/admin/flows", function() {
|
||||
});
|
||||
});
|
||||
it('sets flows - default - v1', function(done) {
|
||||
var setFlows = sinon.spy(function() { return when.resolve();});
|
||||
var setFlows = sinon.spy(function() { return Promise.resolve();});
|
||||
flows.init({
|
||||
log:{warn:function(){},_:function(){},audit:function(){}},
|
||||
nodes:{
|
||||
flows:{
|
||||
setFlows: setFlows
|
||||
}
|
||||
});
|
||||
@@ -120,15 +114,14 @@ describe("api/admin/flows", function() {
|
||||
return done(err);
|
||||
}
|
||||
setFlows.calledOnce.should.be.true();
|
||||
setFlows.lastCall.args[1].should.eql('full');
|
||||
setFlows.lastCall.args[0].should.have.property('deploymentType','full');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('sets flows - non-default - v1', function(done) {
|
||||
var setFlows = sinon.spy(function() { return when.resolve();});
|
||||
var setFlows = sinon.spy(function() { return Promise.resolve();});
|
||||
flows.init({
|
||||
log:{warn:function(){},_:function(){},audit:function(){}},
|
||||
nodes:{
|
||||
flows:{
|
||||
setFlows: setFlows
|
||||
}
|
||||
});
|
||||
@@ -142,19 +135,22 @@ describe("api/admin/flows", function() {
|
||||
return done(err);
|
||||
}
|
||||
setFlows.calledOnce.should.be.true();
|
||||
setFlows.lastCall.args[1].should.eql('nodes');
|
||||
setFlows.lastCall.args[0].should.have.property('deploymentType','nodes');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('set flows - rejects mismatched revision - v2', function(done) {
|
||||
var setFlows = sinon.spy(function() { return when.resolve();});
|
||||
var getFlows = sinon.spy(function() { return {rev:123,flows:[1,2,3]}});
|
||||
flows.init({
|
||||
log:{warn:function(){},_:function(){},audit:function(){}},
|
||||
nodes:{
|
||||
setFlows: setFlows,
|
||||
getFlows: getFlows
|
||||
flows:{
|
||||
setFlows: function() {
|
||||
var err = new Error("mismatch");
|
||||
err.code = "version_mismatch";
|
||||
err.status = 409;
|
||||
var p = Promise.reject(err);
|
||||
p.catch(()=>{});
|
||||
return p;
|
||||
}
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
@@ -171,54 +167,6 @@ describe("api/admin/flows", function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('set flows - rev provided - v2', function(done) {
|
||||
var setFlows = sinon.spy(function() { return when.resolve(456);});
|
||||
var getFlows = sinon.spy(function() { return {rev:123,flows:[1,2,3]}});
|
||||
flows.init({
|
||||
log:{warn:function(){},_:function(){},audit:function(){}},
|
||||
nodes:{
|
||||
setFlows: setFlows,
|
||||
getFlows: getFlows
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.post('/flows')
|
||||
.set('Accept', 'application/json')
|
||||
.set('Node-RED-API-Version','v2')
|
||||
.send({rev:123,flows:[4,5,6]})
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property("rev",456);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('set flows - no rev provided - v2', function(done) {
|
||||
var setFlows = sinon.spy(function() { return when.resolve(456);});
|
||||
var getFlows = sinon.spy(function() { return {rev:123,flows:[1,2,3]}});
|
||||
flows.init({
|
||||
log:{warn:function(){},_:function(){},audit:function(){}},
|
||||
nodes:{
|
||||
setFlows: setFlows,
|
||||
getFlows: getFlows
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.post('/flows')
|
||||
.set('Accept', 'application/json')
|
||||
.set('Node-RED-API-Version','v2')
|
||||
.send({flows:[4,5,6]})
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property("rev",456);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('sets flow - bad version', function(done) {
|
||||
request(app)
|
||||
.post('/flows')
|
||||
@@ -238,11 +186,10 @@ describe("api/admin/flows", function() {
|
||||
});
|
||||
});
|
||||
it('reloads flows', function(done) {
|
||||
var loadFlows = sinon.spy(function() { return when.resolve(); });
|
||||
var setFlows = sinon.spy(function() { return Promise.resolve();});
|
||||
flows.init({
|
||||
log:{warn:function(){},_:function(){},audit:function(){}},
|
||||
nodes:{
|
||||
loadFlows: loadFlows
|
||||
flows:{
|
||||
setFlows: setFlows
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
@@ -254,29 +201,9 @@ describe("api/admin/flows", function() {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
loadFlows.called.should.be.true();
|
||||
setFlows.called.should.be.true();
|
||||
setFlows.lastCall.args[0].should.not.have.property('flows');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error when set fails', function(done) {
|
||||
flows.init({
|
||||
log:{warn:function(){},_:function(){},audit:function(){}},
|
||||
nodes:{
|
||||
setFlows: function() { return when.reject(new Error("expected error")); }
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.post('/flows')
|
||||
.set('Accept', 'application/json')
|
||||
.expect(500)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property("message","expected error");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -66,7 +66,8 @@ describe("api/admin/index", function() {
|
||||
sinon.stub(nodes,"delete",stubApp);
|
||||
sinon.stub(nodes,"getSet",stubApp);
|
||||
sinon.stub(nodes,"putSet",stubApp);
|
||||
|
||||
sinon.stub(nodes,"getModuleCatalog",stubApp);
|
||||
sinon.stub(nodes,"getModuleCatalogs",stubApp);
|
||||
});
|
||||
after(function() {
|
||||
mockList.forEach(function(m) {
|
||||
@@ -87,6 +88,8 @@ describe("api/admin/index", function() {
|
||||
nodes.delete.restore();
|
||||
nodes.getSet.restore();
|
||||
nodes.putSet.restore();
|
||||
nodes.getModuleCatalog.restore();
|
||||
nodes.getModuleCatalogs.restore();
|
||||
|
||||
});
|
||||
|
||||
@@ -281,5 +284,36 @@ describe("api/admin/index", function() {
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('GET /nodes/messages', function(done) {
|
||||
request(app).get("/nodes/messages").expect(200).end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
permissionChecks.should.have.property('nodes.read',1);
|
||||
|
||||
done();
|
||||
})
|
||||
});
|
||||
it('GET /nodes/module/set/messages', function(done) {
|
||||
request(app).get("/nodes/module/set/messages").expect(200).end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
permissionChecks.should.have.property('nodes.read',1);
|
||||
lastRequest.params.should.have.property(0,'module/set');
|
||||
done();
|
||||
})
|
||||
});
|
||||
it('GET /nodes/@scope/module/set/messages', function(done) {
|
||||
request(app).get("/nodes/@scope/module/set/messages").expect(200).end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
permissionChecks.should.have.property('nodes.read',1);
|
||||
lastRequest.params.should.have.property(0,'@scope/module/set');
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,31 +27,19 @@ var apiUtil = require("../../../../red/api/util");
|
||||
describe("api/admin/nodes", function() {
|
||||
|
||||
var app;
|
||||
function initNodes(runtime) {
|
||||
runtime.log = {
|
||||
audit:function(e){},//console.log(e)},
|
||||
_:function(){},
|
||||
info: function(){},
|
||||
warn: function(){}
|
||||
}
|
||||
runtime.events = {
|
||||
emit: function(){}
|
||||
}
|
||||
nodes.init(runtime);
|
||||
|
||||
}
|
||||
|
||||
before(function() {
|
||||
app = express();
|
||||
app.use(bodyParser.json());
|
||||
app.get("/nodes",nodes.getAll);
|
||||
app.post("/nodes",nodes.post);
|
||||
app.get(/\/nodes\/messages/,nodes.getModuleCatalogs);
|
||||
app.get(/\/nodes\/((@[^\/]+\/)?[^\/]+\/[^\/]+)\/messages/,nodes.getModuleCatalog);
|
||||
app.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,nodes.getModule);
|
||||
app.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,nodes.getSet);
|
||||
app.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,nodes.putModule);
|
||||
app.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,nodes.getSet);
|
||||
app.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,nodes.putSet);
|
||||
app.get("/getIcons",nodes.getIcons);
|
||||
app.delete("/nodes/:id",nodes.delete);
|
||||
app.delete(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,nodes.delete);
|
||||
sinon.stub(apiUtil,"determineLangFromHeaders", function() {
|
||||
return "en-US";
|
||||
});
|
||||
@@ -62,10 +50,10 @@ describe("api/admin/nodes", function() {
|
||||
|
||||
describe('get nodes', function() {
|
||||
it('returns node list', function(done) {
|
||||
initNodes({
|
||||
nodes.init({
|
||||
nodes:{
|
||||
getNodeList: function() {
|
||||
return [1,2,3];
|
||||
return Promise.resolve([1,2,3]);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -84,10 +72,10 @@ describe("api/admin/nodes", function() {
|
||||
});
|
||||
|
||||
it('returns node configs', function(done) {
|
||||
initNodes({
|
||||
nodes.init({
|
||||
nodes:{
|
||||
getNodeConfigs: function() {
|
||||
return "<script></script>";
|
||||
return Promise.resolve("<script></script>");
|
||||
}
|
||||
},
|
||||
i18n: {
|
||||
@@ -108,10 +96,10 @@ describe("api/admin/nodes", function() {
|
||||
});
|
||||
|
||||
it('returns node module info', function(done) {
|
||||
initNodes({
|
||||
nodes.init({
|
||||
nodes:{
|
||||
getModuleInfo: function(id) {
|
||||
return {"node-red":{name:"node-red"}}[id];
|
||||
getModuleInfo: function(opts) {
|
||||
return Promise.resolve({"node-red":{name:"node-red"}}[opts.module]);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -128,10 +116,15 @@ describe("api/admin/nodes", function() {
|
||||
});
|
||||
|
||||
it('returns 404 for unknown module', function(done) {
|
||||
initNodes({
|
||||
nodes.init({
|
||||
nodes:{
|
||||
getModuleInfo: function(id) {
|
||||
return {"node-red":{name:"node-red"}}[id];
|
||||
getModuleInfo: function(opts) {
|
||||
var errInstance = new Error("Not Found");
|
||||
errInstance.code = "not_found";
|
||||
errInstance.status = 404;
|
||||
var p = Promise.reject(errInstance);
|
||||
p.catch(()=>{});
|
||||
return p;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -147,10 +140,10 @@ describe("api/admin/nodes", function() {
|
||||
});
|
||||
|
||||
it('returns individual node info', function(done) {
|
||||
initNodes({
|
||||
nodes.init({
|
||||
nodes:{
|
||||
getNodeInfo: function(id) {
|
||||
return {"node-red/123":{id:"node-red/123"}}[id];
|
||||
getNodeInfo: function(opts) {
|
||||
return Promise.resolve({"node-red/123":{id:"node-red/123"}}[opts.id]);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -168,10 +161,10 @@ describe("api/admin/nodes", function() {
|
||||
});
|
||||
|
||||
it('returns individual node configs', function(done) {
|
||||
initNodes({
|
||||
nodes.init({
|
||||
nodes:{
|
||||
getNodeConfig: function(id) {
|
||||
return {"node-red/123":"<script></script>"}[id];
|
||||
getNodeConfig: function(opts) {
|
||||
return Promise.resolve({"node-red/123":"<script></script>"}[opts.id]);
|
||||
}
|
||||
},
|
||||
i18n: {
|
||||
@@ -190,12 +183,16 @@ describe("api/admin/nodes", function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns 404 for unknown node', function(done) {
|
||||
initNodes({
|
||||
nodes.init({
|
||||
nodes:{
|
||||
getNodeInfo: function(id) {
|
||||
return {"node-red/123":{id:"node-red/123"}}[id];
|
||||
getNodeInfo: function(opts) {
|
||||
var errInstance = new Error("Not Found");
|
||||
errInstance.code = "not_found";
|
||||
errInstance.status = 404;
|
||||
var p = Promise.reject(errInstance);
|
||||
p.catch(()=>{});
|
||||
return p;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -213,142 +210,96 @@ describe("api/admin/nodes", function() {
|
||||
});
|
||||
|
||||
describe('install', function() {
|
||||
|
||||
it('returns 400 if settings are unavailable', function(done) {
|
||||
initNodes({
|
||||
settings:{available:function(){return false}}
|
||||
it('installs the module and returns module info', function(done) {
|
||||
var opts;
|
||||
nodes.init({
|
||||
nodes:{
|
||||
addModule: function(_opts) {
|
||||
opts = _opts;
|
||||
return Promise.resolve({
|
||||
name:"foo",
|
||||
nodes:[{id:"123"}]
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.post('/nodes')
|
||||
.send({module: 'foo',version:"1.2.3"})
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.have.property("name","foo");
|
||||
res.body.should.have.property("nodes");
|
||||
res.body.nodes[0].should.have.property("id","123");
|
||||
opts.should.have.property("module","foo");
|
||||
opts.should.have.property("version","1.2.3");
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('returns error', function(done) {
|
||||
nodes.init({
|
||||
nodes:{
|
||||
addModule: function(opts) {
|
||||
var errInstance = new Error("Message");
|
||||
errInstance.code = "random_error";
|
||||
errInstance.status = 400;
|
||||
var p = Promise.reject(errInstance);
|
||||
p.catch(()=>{});
|
||||
return p;
|
||||
}
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.post('/nodes')
|
||||
.send({module: 'foo',version:"1.2.3"})
|
||||
.expect(400)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.have.a.property('code','random_error');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns 400 if request is invalid', function(done) {
|
||||
initNodes({
|
||||
settings:{available:function(){return true}}
|
||||
});
|
||||
request(app)
|
||||
.post('/nodes')
|
||||
.send({})
|
||||
.expect(400)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('by module', function() {
|
||||
it('installs the module and returns module info', function(done) {
|
||||
initNodes({
|
||||
settings:{available:function(){return true}},
|
||||
nodes:{
|
||||
getModuleInfo: function(id) { return null; },
|
||||
installModule: function() {
|
||||
return when.resolve({
|
||||
name:"foo",
|
||||
nodes:[{id:"123"}]
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.post('/nodes')
|
||||
.send({module: 'foo'})
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.have.property("name","foo");
|
||||
res.body.should.have.property("nodes");
|
||||
res.body.nodes[0].should.have.property("id","123");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fails the install if already installed', function(done) {
|
||||
initNodes({
|
||||
settings:{available:function(){return true}},
|
||||
nodes:{
|
||||
getModuleInfo: function(id) { return {nodes:{id:"123"}}; },
|
||||
installModule: function() {
|
||||
return when.resolve({id:"123"});
|
||||
}
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.post('/nodes')
|
||||
.send({module: 'foo'})
|
||||
.expect(400)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fails the install if module error', function(done) {
|
||||
initNodes({
|
||||
settings:{available:function(){return true}},
|
||||
nodes:{
|
||||
getModuleInfo: function(id) { return null },
|
||||
installModule: function() {
|
||||
return when.reject(new Error("test error"));
|
||||
}
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.post('/nodes')
|
||||
.send({module: 'foo'})
|
||||
.expect(400)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.have.property("message","Error: test error");
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('fails the install if module not found', function(done) {
|
||||
initNodes({
|
||||
settings:{available:function(){return true}},
|
||||
nodes:{
|
||||
getModuleInfo: function(id) { return null },
|
||||
installModule: function() {
|
||||
var err = new Error("test error");
|
||||
err.code = 404;
|
||||
return when.reject(err);
|
||||
}
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.post('/nodes')
|
||||
.send({module: 'foo'})
|
||||
.expect(404)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('delete', function() {
|
||||
it('returns 400 if settings are unavailable', function(done) {
|
||||
initNodes({
|
||||
settings:{available:function(){return false}}
|
||||
it('uninstalls the module', function(done) {
|
||||
var opts;
|
||||
nodes.init({
|
||||
nodes:{
|
||||
removeModule: function(_opts) {
|
||||
opts = _opts;
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.del('/nodes/123')
|
||||
.expect(204)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
opts.should.have.property("module","123");
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('returns error', function(done) {
|
||||
nodes.init({
|
||||
nodes:{
|
||||
removeModule: function(opts) {
|
||||
var errInstance = new Error("Message");
|
||||
errInstance.code = "random_error";
|
||||
errInstance.status = 400;
|
||||
var p = Promise.reject(errInstance);
|
||||
p.catch(()=>{});
|
||||
return p;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
request(app)
|
||||
.del('/nodes/123')
|
||||
.expect(400)
|
||||
@@ -356,93 +307,18 @@ describe("api/admin/nodes", function() {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.have.a.property('code','random_error');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('by module', function() {
|
||||
it('uninstalls the module', function(done) {
|
||||
initNodes({
|
||||
settings:{available:function(){return true}},
|
||||
nodes:{
|
||||
getModuleInfo: function(id) { return {nodes:[{id:"123"}]} },
|
||||
getNodeInfo: function() { return null },
|
||||
uninstallModule: function() { return when.resolve({id:"123"});}
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.del('/nodes/foo')
|
||||
.expect(204)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fails the uninstall if the module is not installed', function(done) {
|
||||
initNodes({
|
||||
settings:{available:function(){return true}},
|
||||
nodes:{
|
||||
getModuleInfo: function(id) { return null },
|
||||
getNodeInfo: function() { return null }
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.del('/nodes/foo')
|
||||
.expect(404)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fails the uninstall if the module is not installed', function(done) {
|
||||
initNodes({
|
||||
settings:{available:function(){return true}},
|
||||
nodes:{
|
||||
getModuleInfo: function(id) { return {nodes:[{id:"123"}]} },
|
||||
getNodeInfo: function() { return null },
|
||||
uninstallModule: function() { return when.reject(new Error("test error"));}
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.del('/nodes/foo')
|
||||
.expect(400)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.have.property("message","Error: test error");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('enable/disable', function() {
|
||||
it('returns 400 if settings are unavailable', function(done) {
|
||||
initNodes({
|
||||
settings:{available:function(){return false}}
|
||||
});
|
||||
request(app)
|
||||
.put('/nodes/123')
|
||||
.expect(400)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns 400 for invalid node payload', function(done) {
|
||||
initNodes({
|
||||
settings:{available:function(){return true}}
|
||||
describe('enable/disable node set', function() {
|
||||
it('returns 400 for invalid request payload', function(done) {
|
||||
nodes.init({
|
||||
nodes:{
|
||||
setNodeSetState: function(opts) {return Promise.resolve()}
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.put('/nodes/node-red/foo')
|
||||
@@ -452,77 +328,23 @@ describe("api/admin/nodes", function() {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.have.property("code","invalid_request");
|
||||
res.body.should.have.property("message","Invalid request");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns 400 for invalid module payload', function(done) {
|
||||
initNodes({
|
||||
settings:{available:function(){return true}}
|
||||
});
|
||||
request(app)
|
||||
.put('/nodes/foo')
|
||||
.send({})
|
||||
.expect(400)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.have.property("message","Invalid request");
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns 404 for unknown node', function(done) {
|
||||
initNodes({
|
||||
settings:{available:function(){return true}},
|
||||
it('sets node state and returns node info', function(done) {
|
||||
var opts;
|
||||
nodes.init({
|
||||
nodes:{
|
||||
getNodeInfo: function() { return null }
|
||||
setNodeSetState: function(_opts) {
|
||||
opts = _opts;
|
||||
return Promise.resolve({id:"123",enabled: true });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
request(app)
|
||||
.put('/nodes/node-red/foo')
|
||||
.send({enabled:false})
|
||||
.expect(404)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns 404 for unknown module', function(done) {
|
||||
initNodes({
|
||||
settings:{available:function(){return true}},
|
||||
nodes:{
|
||||
getModuleInfo: function(id) { return null }
|
||||
}
|
||||
});
|
||||
|
||||
request(app)
|
||||
.put('/nodes/node-blue')
|
||||
.send({enabled:false})
|
||||
.expect(404)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('enables disabled node', function(done) {
|
||||
initNodes({
|
||||
settings:{available:function(){return true}},
|
||||
nodes:{
|
||||
getNodeInfo: function() { return {id:"123",enabled: false} },
|
||||
enableNode: function() { return when.resolve({id:"123",enabled: true,types:['a']}); }
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.put('/nodes/node-red/foo')
|
||||
.send({enabled:true})
|
||||
@@ -533,130 +355,41 @@ describe("api/admin/nodes", function() {
|
||||
}
|
||||
res.body.should.have.property("id","123");
|
||||
res.body.should.have.property("enabled",true);
|
||||
opts.should.have.property("enabled",true);
|
||||
opts.should.have.property("id","node-red/foo");
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('disables enabled node', function(done) {
|
||||
initNodes({
|
||||
settings:{available:function(){return true}},
|
||||
});
|
||||
describe('enable/disable module' ,function() {
|
||||
it('returns 400 for invalid request payload', function(done) {
|
||||
nodes.init({
|
||||
nodes:{
|
||||
getNodeInfo: function() { return {id:"123",enabled: true} },
|
||||
disableNode: function() { return when.resolve({id:"123",enabled: false,types:['a']}); }
|
||||
setModuleState: function(opts) {return Promise.resolve()}
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.put('/nodes/node-red/foo')
|
||||
.send({enabled:false})
|
||||
.expect(200)
|
||||
.put('/nodes/node-red')
|
||||
.send({})
|
||||
.expect(400)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.have.property("id","123");
|
||||
res.body.should.have.property("enabled",false);
|
||||
|
||||
res.body.should.have.property("code","invalid_request");
|
||||
res.body.should.have.property("message","Invalid request");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('no-ops if already in the right state', function() {
|
||||
function run(state,done) {
|
||||
var enableNode = sinon.spy(function() { return when.resolve({id:"123",enabled: true,types:['a']}) });
|
||||
var disableNode = sinon.spy(function() { return when.resolve({id:"123",enabled: false,types:['a']}) });
|
||||
|
||||
initNodes({
|
||||
settings:{available:function(){return true}},
|
||||
nodes:{
|
||||
getNodeInfo: function() { return {id:"123",enabled: state} },
|
||||
enableNode: enableNode,
|
||||
disableNode: disableNode
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.put('/nodes/node-red/foo')
|
||||
.send({enabled:state})
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
var enableNodeCalled = enableNode.called;
|
||||
var disableNodeCalled = disableNode.called;
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
enableNodeCalled.should.be.false();
|
||||
disableNodeCalled.should.be.false();
|
||||
res.body.should.have.property("id","123");
|
||||
res.body.should.have.property("enabled",state);
|
||||
|
||||
done();
|
||||
});
|
||||
}
|
||||
it('already enabled', function(done) {
|
||||
run(true,done);
|
||||
});
|
||||
it('already disabled', function(done) {
|
||||
run(false,done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('does not no-op if err on node', function() {
|
||||
function run(state,done) {
|
||||
var enableNode = sinon.spy(function() { return when.resolve({id:"123",enabled: true,types:['a']}) });
|
||||
var disableNode = sinon.spy(function() { return when.resolve({id:"123",enabled: false,types:['a']}) });
|
||||
|
||||
initNodes({
|
||||
settings:{available:function(){return true}},
|
||||
nodes:{
|
||||
getNodeInfo: function() { return {id:"123",enabled: state, err:"foo"} },
|
||||
enableNode: enableNode,
|
||||
disableNode: disableNode
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.put('/nodes/node-red/foo')
|
||||
.send({enabled:state})
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
var enableNodeCalled = enableNode.called;
|
||||
var disableNodeCalled = disableNode.called;
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
enableNodeCalled.should.be.equal(state);
|
||||
disableNodeCalled.should.be.equal(!state);
|
||||
res.body.should.have.property("id","123");
|
||||
res.body.should.have.property("enabled",state);
|
||||
|
||||
done();
|
||||
});
|
||||
}
|
||||
it('already enabled', function(done) {
|
||||
run(true,done);
|
||||
});
|
||||
it('already disabled', function(done) {
|
||||
run(false,done);
|
||||
});
|
||||
});
|
||||
|
||||
it('enables disabled module', function(done) {
|
||||
var n1 = {id:"123",enabled:false,types:['a']};
|
||||
var n2 = {id:"456",enabled:false,types:['b']};
|
||||
var enableNode = sinon.stub();
|
||||
enableNode.onFirstCall().returns((function() {
|
||||
n1.enabled = true;
|
||||
return when.resolve(n1);
|
||||
})());
|
||||
enableNode.onSecondCall().returns((function() {
|
||||
n2.enabled = true;
|
||||
return when.resolve(n2);
|
||||
})());
|
||||
enableNode.returns(null);
|
||||
initNodes({
|
||||
settings:{available:function(){return true}},
|
||||
it('sets module state and returns module info', function(done) {
|
||||
var opts;
|
||||
nodes.init({
|
||||
nodes:{
|
||||
getModuleInfo: function() { return {name:"node-red", nodes:[n1, n2]} },
|
||||
enableNode: enableNode
|
||||
setModuleState: function(_opts) {
|
||||
opts = _opts;
|
||||
return Promise.resolve({name:"node-red"});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -669,154 +402,20 @@ describe("api/admin/nodes", function() {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.have.property("name","node-red");
|
||||
res.body.should.have.property("nodes");
|
||||
res.body.nodes[0].should.have.property("enabled",true);
|
||||
res.body.nodes[1].should.have.property("enabled",true);
|
||||
opts.should.have.property("enabled",true);
|
||||
opts.should.have.property("module","node-red");
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('disables enabled module', function(done) {
|
||||
var n1 = {id:"123",enabled:true,types:['a']};
|
||||
var n2 = {id:"456",enabled:true,types:['b']};
|
||||
var disableNode = sinon.stub();
|
||||
disableNode.onFirstCall().returns((function() {
|
||||
n1.enabled = false;
|
||||
return when.resolve(n1);
|
||||
})());
|
||||
disableNode.onSecondCall().returns((function() {
|
||||
n2.enabled = false;
|
||||
return when.resolve(n2);
|
||||
})());
|
||||
disableNode.returns(null);
|
||||
initNodes({
|
||||
settings:{available:function(){return true}},
|
||||
nodes:{
|
||||
getModuleInfo: function() { return {name:"node-red", nodes:[n1, n2]} },
|
||||
disableNode: disableNode
|
||||
}
|
||||
});
|
||||
|
||||
request(app)
|
||||
.put('/nodes/node-red')
|
||||
.send({enabled:false})
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.have.property("name","node-red");
|
||||
res.body.should.have.property("nodes");
|
||||
res.body.nodes[0].should.have.property("enabled",false);
|
||||
res.body.nodes[1].should.have.property("enabled",false);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('no-ops if a node in module already in the right state', function() {
|
||||
function run(state,done) {
|
||||
var node = {id:"123",enabled:state,types:['a']};
|
||||
var enableNode = sinon.spy(function(id) {
|
||||
node.enabled = true;
|
||||
return when.resolve(node);
|
||||
});
|
||||
var disableNode = sinon.spy(function(id) {
|
||||
node.enabled = false;
|
||||
return when.resolve(node);
|
||||
});
|
||||
|
||||
initNodes({
|
||||
settings:{available:function(){return true}},
|
||||
nodes:{
|
||||
getModuleInfo: function() { return {name:"node-red", nodes:[node]}; },
|
||||
enableNode: enableNode,
|
||||
disableNode: disableNode
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.put('/nodes/node-red')
|
||||
.send({enabled:state})
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
var enableNodeCalled = enableNode.called;
|
||||
var disableNodeCalled = disableNode.called;
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
enableNodeCalled.should.be.false();
|
||||
disableNodeCalled.should.be.false();
|
||||
res.body.should.have.property("name","node-red");
|
||||
res.body.should.have.property("nodes");
|
||||
res.body.nodes[0].should.have.property("enabled",state);
|
||||
|
||||
done();
|
||||
});
|
||||
}
|
||||
it('already enabled', function(done) {
|
||||
run(true,done);
|
||||
});
|
||||
it('already disabled', function(done) {
|
||||
run(false,done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('does not no-op if err on a node in module', function() {
|
||||
function run(state,done) {
|
||||
var node = {id:"123",enabled:state,types:['a'],err:"foo"};
|
||||
var enableNode = sinon.spy(function(id) {
|
||||
node.enabled = true;
|
||||
return when.resolve(node);
|
||||
});
|
||||
var disableNode = sinon.spy(function(id) {
|
||||
node.enabled = false;
|
||||
return when.resolve(node);
|
||||
});
|
||||
|
||||
initNodes({
|
||||
settings:{available:function(){return true}},
|
||||
nodes:{
|
||||
getModuleInfo: function() { return {name:"node-red", nodes:[node]}; },
|
||||
enableNode: enableNode,
|
||||
disableNode: disableNode
|
||||
}
|
||||
});
|
||||
|
||||
request(app)
|
||||
.put('/nodes/node-red')
|
||||
.send({enabled:state})
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
var enableNodeCalled = enableNode.called;
|
||||
var disableNodeCalled = disableNode.called;
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
enableNodeCalled.should.be.equal(state);
|
||||
disableNodeCalled.should.be.equal(!state);
|
||||
res.body.should.have.property("name","node-red");
|
||||
res.body.should.have.property("nodes");
|
||||
res.body.nodes[0].should.have.property("enabled",state);
|
||||
|
||||
done();
|
||||
});
|
||||
}
|
||||
it('already enabled', function(done) {
|
||||
run(true,done);
|
||||
});
|
||||
it('already disabled', function(done) {
|
||||
run(false,done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('get icons', function() {
|
||||
it('returns icon list', function(done) {
|
||||
initNodes({
|
||||
nodes.init({
|
||||
nodes:{
|
||||
getNodeIcons: function() {
|
||||
return {"module":["1.png","2.png","3.png"]};
|
||||
getIconList: function() {
|
||||
return Promise.resolve({module:[1,2,3]});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -827,7 +426,6 @@ describe("api/admin/nodes", function() {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
console.log(res.body);
|
||||
res.body.should.have.property("module");
|
||||
res.body.module.should.be.an.Array();
|
||||
res.body.module.should.have.lengthOf(3);
|
||||
@@ -835,4 +433,45 @@ describe("api/admin/nodes", function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('get module messages', function() {
|
||||
it('returns message catalog', function(done) {
|
||||
nodes.init({
|
||||
nodes:{
|
||||
getModuleCatalog: function(opts) {
|
||||
return Promise.resolve(opts);
|
||||
}
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.get('/nodes/module/set/messages')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.eql({ module: 'module/set' });
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('returns all node catalogs', function(done) {
|
||||
nodes.init({
|
||||
nodes:{
|
||||
getModuleCatalogs: function(opts) {
|
||||
return Promise.resolve({a:1});
|
||||
}
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.get('/nodes/messages')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.eql({a:1});
|
||||
done();
|
||||
});
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
@@ -23,6 +23,7 @@ var passport = require("passport");
|
||||
var auth = require("../../../../red/api/auth");
|
||||
var Users = require("../../../../red/api/auth/users");
|
||||
var Tokens = require("../../../../red/api/auth/tokens");
|
||||
var Permissions = require("../../../../red/api/auth/permissions");
|
||||
|
||||
describe("api/auth/index",function() {
|
||||
|
||||
@@ -30,7 +31,7 @@ describe("api/auth/index",function() {
|
||||
|
||||
describe("ensureClientSecret", function() {
|
||||
before(function() {
|
||||
auth.init({settings:{},log:{audit:function(){}}})
|
||||
auth.init({},{})
|
||||
});
|
||||
it("leaves client_secret alone if not present",function(done) {
|
||||
var req = {
|
||||
@@ -85,7 +86,7 @@ describe("api/auth/index",function() {
|
||||
Users.init.restore();
|
||||
});
|
||||
it("returns login details - credentials", function(done) {
|
||||
auth.init({settings:{adminAuth:{type:"credentials"}},log:{audit:function(){}}})
|
||||
auth.init({adminAuth:{type:"credentials"}},{})
|
||||
auth.login(null,{json: function(resp) {
|
||||
resp.should.have.a.property("type","credentials");
|
||||
resp.should.have.a.property("prompts");
|
||||
@@ -94,14 +95,14 @@ describe("api/auth/index",function() {
|
||||
}});
|
||||
});
|
||||
it("returns login details - none", function(done) {
|
||||
auth.init({settings:{},log:{audit:function(){}}})
|
||||
auth.init({},{})
|
||||
auth.login(null,{json: function(resp) {
|
||||
resp.should.eql({});
|
||||
done();
|
||||
}});
|
||||
});
|
||||
it("returns login details - strategy", function(done) {
|
||||
auth.init({settings:{adminAuth:{type:"strategy",strategy:{label:"test-strategy",icon:"test-icon"}}},log:{audit:function(){}}})
|
||||
auth.init({adminAuth:{type:"strategy",strategy:{label:"test-strategy",icon:"test-icon"}}},{})
|
||||
auth.login(null,{json: function(resp) {
|
||||
resp.should.have.a.property("type","strategy");
|
||||
resp.should.have.a.property("prompts");
|
||||
@@ -115,5 +116,100 @@ describe("api/auth/index",function() {
|
||||
});
|
||||
|
||||
});
|
||||
describe("needsPermission", function() {
|
||||
beforeEach(function() {
|
||||
sinon.stub(Tokens,"init",function(){});
|
||||
sinon.stub(Users,"init",function(){});
|
||||
});
|
||||
afterEach(function() {
|
||||
Tokens.init.restore();
|
||||
Users.init.restore();
|
||||
if (passport.authenticate.restore) {
|
||||
passport.authenticate.restore();
|
||||
}
|
||||
if (Permissions.hasPermission.restore) {
|
||||
Permissions.hasPermission.restore();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
it('no-ops if adminAuth not set', function(done) {
|
||||
sinon.stub(passport,"authenticate",function(scopes,opts) {
|
||||
return function(req,res,next) {
|
||||
}
|
||||
});
|
||||
auth.init({});
|
||||
var func = auth.needsPermission("foo");
|
||||
func({},{},function() {
|
||||
passport.authenticate.called.should.be.false();
|
||||
done();
|
||||
})
|
||||
});
|
||||
it('skips auth if req.user undefined', function(done) {
|
||||
sinon.stub(passport,"authenticate",function(scopes,opts) {
|
||||
return function(req,res,next) {
|
||||
next();
|
||||
}
|
||||
});
|
||||
sinon.stub(Permissions,"hasPermission",function(perm) { return true });
|
||||
auth.init({adminAuth:{}});
|
||||
var func = auth.needsPermission("foo");
|
||||
func({user:null},{},function() {
|
||||
try {
|
||||
passport.authenticate.called.should.be.true();
|
||||
Permissions.hasPermission.called.should.be.false();
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
it('passes for valid user permission', function(done) {
|
||||
sinon.stub(passport,"authenticate",function(scopes,opts) {
|
||||
return function(req,res,next) {
|
||||
next();
|
||||
}
|
||||
});
|
||||
sinon.stub(Permissions,"hasPermission",function(perm) { return true });
|
||||
auth.init({adminAuth:{}});
|
||||
var func = auth.needsPermission("foo");
|
||||
func({user:true,authInfo: { scope: "read"}},{},function() {
|
||||
try {
|
||||
passport.authenticate.called.should.be.true();
|
||||
Permissions.hasPermission.called.should.be.true();
|
||||
Permissions.hasPermission.lastCall.args[0].should.eql("read");
|
||||
Permissions.hasPermission.lastCall.args[1].should.eql("foo");
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
it('rejects for invalid user permission', function(done) {
|
||||
sinon.stub(passport,"authenticate",function(scopes,opts) {
|
||||
return function(req,res,next) {
|
||||
next();
|
||||
}
|
||||
});
|
||||
sinon.stub(Permissions,"hasPermission",function(perm) { return false });
|
||||
auth.init({adminAuth:{}});
|
||||
var func = auth.needsPermission("foo");
|
||||
func({user:true,authInfo: { scope: "read"}},{
|
||||
status: function(status) {
|
||||
return { end: function() {
|
||||
try {
|
||||
status.should.eql(401);
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
}}
|
||||
}
|
||||
},function() {
|
||||
done(new Error("hasPermission unexpected passed"))
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,9 +24,6 @@ var Tokens = require("../../../../red/api/auth/tokens");
|
||||
var Clients = require("../../../../red/api/auth/clients");
|
||||
|
||||
describe("api/auth/strategies", function() {
|
||||
before(function() {
|
||||
strategies.init({log:{audit:function(){}}})
|
||||
});
|
||||
describe("Password Token Exchange", function() {
|
||||
var userAuthentication;
|
||||
afterEach(function() {
|
||||
@@ -108,7 +105,6 @@ describe("api/auth/strategies", function() {
|
||||
user.should.equal("anon");
|
||||
strategies.anonymousStrategy.success = strategies.anonymousStrategy._success;
|
||||
delete strategies.anonymousStrategy._success;
|
||||
userDefault.restore();
|
||||
done();
|
||||
};
|
||||
strategies.anonymousStrategy.authenticate({});
|
||||
@@ -122,11 +118,13 @@ describe("api/auth/strategies", function() {
|
||||
err.should.equal(401);
|
||||
strategies.anonymousStrategy.fail = strategies.anonymousStrategy._fail;
|
||||
delete strategies.anonymousStrategy._fail;
|
||||
userDefault.restore();
|
||||
done();
|
||||
};
|
||||
strategies.anonymousStrategy.authenticate({});
|
||||
});
|
||||
afterEach(function() {
|
||||
Users.default.restore();
|
||||
})
|
||||
});
|
||||
|
||||
describe("Bearer Strategy", function() {
|
||||
|
||||
@@ -21,6 +21,9 @@ var sinon = require('sinon');
|
||||
var Users = require("../../../../red/api/auth/users");
|
||||
|
||||
describe("api/auth/users", function() {
|
||||
after(function() {
|
||||
Users.init({});
|
||||
})
|
||||
describe('Initalised with a credentials object, no anon',function() {
|
||||
before(function() {
|
||||
Users.init({
|
||||
|
||||
@@ -33,6 +33,24 @@ var listenPort = 0; // use ephemeral port
|
||||
|
||||
|
||||
describe("api/editor/comms", function() {
|
||||
var connections = [];
|
||||
var mockComms = {
|
||||
addConnection: function(opts) {
|
||||
connections.push(opts.client);
|
||||
return Promise.resolve()
|
||||
},
|
||||
removeConnection: function(opts) {
|
||||
for (var i=0;i<connections.length;i++) {
|
||||
if (connections[i] === opts.client) {
|
||||
connections.splice(i,1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Promise.resolve()
|
||||
},
|
||||
subscribe: function() { return Promise.resolve()},
|
||||
unsubscribe: function() { return Promise.resolve(); }
|
||||
}
|
||||
|
||||
describe("with default keepalive", function() {
|
||||
var server;
|
||||
@@ -41,11 +59,7 @@ describe("api/editor/comms", function() {
|
||||
before(function(done) {
|
||||
sinon.stub(Users,"default",function() { return when.resolve(null);});
|
||||
server = stoppable(http.createServer(function(req,res){app(req,res)}));
|
||||
comms.init(server, {
|
||||
settings:{},
|
||||
log:{warn:function(){},_:function(){},trace:function(){},audit:function(){}},
|
||||
events:{on:function(){},removeListener:function(){}}
|
||||
});
|
||||
comms.init(server, {}, {comms: mockComms});
|
||||
server.listen(listenPort, address);
|
||||
server.on('listening', function() {
|
||||
port = server.address().port;
|
||||
@@ -63,9 +77,15 @@ describe("api/editor/comms", function() {
|
||||
|
||||
it('accepts connection', function(done) {
|
||||
var ws = new WebSocket(url);
|
||||
connections.length.should.eql(0);
|
||||
ws.on('open', function() {
|
||||
ws.close();
|
||||
done();
|
||||
try {
|
||||
connections.length.should.eql(1);
|
||||
ws.close();
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -73,7 +93,8 @@ describe("api/editor/comms", function() {
|
||||
var ws = new WebSocket(url);
|
||||
ws.on('open', function() {
|
||||
ws.send('{"subscribe":"topic1"}');
|
||||
comms.publish('topic1', 'foo');
|
||||
connections.length.should.eql(1);
|
||||
connections[0].send('topic1', 'foo');
|
||||
});
|
||||
ws.on('message', function(msg) {
|
||||
msg.should.equal('[{"topic":"topic1","data":"foo"}]');
|
||||
@@ -82,43 +103,13 @@ describe("api/editor/comms", function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('publishes retained message for subscription', function(done) {
|
||||
comms.publish('topic2', 'bar', true);
|
||||
var ws = new WebSocket(url);
|
||||
ws.on('open', function() {
|
||||
ws.send('{"subscribe":"topic2"}');
|
||||
});
|
||||
ws.on('message', function(msg) {
|
||||
console.log(msg);
|
||||
msg.should.equal('[{"topic":"topic2","data":"bar"}]');
|
||||
ws.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('retained message is deleted by non-retained message', function(done) {
|
||||
comms.publish('topic3', 'retained', true);
|
||||
comms.publish('topic3', 'non-retained');
|
||||
var ws = new WebSocket(url);
|
||||
ws.on('open', function() {
|
||||
ws.send('{"subscribe":"topic3"}');
|
||||
comms.publish('topic3', 'new');
|
||||
});
|
||||
ws.on('message', function(msg) {
|
||||
console.log(msg);
|
||||
msg.should.equal('[{"topic":"topic3","data":"new"}]');
|
||||
ws.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('malformed messages are ignored',function(done) {
|
||||
var ws = new WebSocket(url);
|
||||
ws.on('open', function() {
|
||||
ws.send('not json');
|
||||
ws.send('[]');
|
||||
ws.send('{"subscribe":"topic3"}');
|
||||
comms.publish('topic3', 'correct');
|
||||
connections[0].send('topic3', 'correct');
|
||||
});
|
||||
ws.on('message', function(msg) {
|
||||
console.log(msg);
|
||||
@@ -127,40 +118,16 @@ describe("api/editor/comms", function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// The following test currently fails due to minimum viable
|
||||
// implementation. More test should be written to test topic
|
||||
// matching once this one is passing
|
||||
|
||||
it.skip('receives message on correct topic', function(done) {
|
||||
var ws = new WebSocket(url);
|
||||
ws.on('open', function() {
|
||||
ws.send('{"subscribe":"topic4"}');
|
||||
comms.publish('topic5', 'foo');
|
||||
comms.publish('topic4', 'bar');
|
||||
});
|
||||
ws.on('message', function(msg) {
|
||||
console.log(msg);
|
||||
msg.should.equal('[{"topic":"topic4","data":"bar"}]');
|
||||
ws.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('listens for node/status events');
|
||||
});
|
||||
|
||||
describe("disabled editor", function() {
|
||||
var server;
|
||||
var url;
|
||||
var port;
|
||||
before(function(done) {
|
||||
sinon.stub(Users,"default",function() { return when.resolve(null);});
|
||||
sinon.stub(Users,"default",function() { return Promise.resolve(null);});
|
||||
server = stoppable(http.createServer(function(req,res){app(req,res)}));
|
||||
comms.init(server, {
|
||||
settings:{disableEditor:true},
|
||||
log:{warn:function(){},_:function(){},trace:function(){},audit:function(){}},
|
||||
events:{on:function(){},removeListener:function(){}}
|
||||
});
|
||||
comms.init(server, {disableEditor:true}, {comms: mockComms});
|
||||
server.listen(listenPort, address);
|
||||
server.on('listening', function() {
|
||||
port = server.address().port;
|
||||
@@ -177,12 +144,14 @@ describe("api/editor/comms", function() {
|
||||
});
|
||||
|
||||
it('rejects websocket connections',function(done) {
|
||||
connections.length.should.eql(0);
|
||||
var ws = new WebSocket(url);
|
||||
ws.on('open', function() {
|
||||
done(new Error("Socket connection unexpectedly accepted"));
|
||||
ws.close();
|
||||
});
|
||||
ws.on('error', function() {
|
||||
connections.length.should.eql(0);
|
||||
done();
|
||||
});
|
||||
|
||||
@@ -196,11 +165,7 @@ describe("api/editor/comms", function() {
|
||||
before(function(done) {
|
||||
sinon.stub(Users,"default",function() { return when.resolve(null);});
|
||||
server = stoppable(http.createServer(function(req,res){app(req,res)}));
|
||||
comms.init(server, {
|
||||
settings:{httpAdminRoot:"/adminPath"},
|
||||
log:{warn:function(){},_:function(){},trace:function(){},audit:function(){}},
|
||||
events:{on:function(){},removeListener:function(){}}
|
||||
});
|
||||
comms.init(server, {httpAdminRoot:"/adminPath"}, {comms: mockComms});
|
||||
server.listen(listenPort, address);
|
||||
server.on('listening', function() {
|
||||
port = server.address().port;
|
||||
@@ -217,8 +182,10 @@ describe("api/editor/comms", function() {
|
||||
});
|
||||
|
||||
it('accepts connections',function(done) {
|
||||
connections.length.should.eql(0);
|
||||
var ws = new WebSocket(url);
|
||||
ws.on('open', function() {
|
||||
connections.length.should.eql(1);
|
||||
ws.close();
|
||||
done();
|
||||
});
|
||||
@@ -236,11 +203,7 @@ describe("api/editor/comms", function() {
|
||||
before(function(done) {
|
||||
sinon.stub(Users,"default",function() { return when.resolve(null);});
|
||||
server = stoppable(http.createServer(function(req,res){app(req,res)}));
|
||||
comms.init(server,{
|
||||
settings:{httpAdminRoot:"/adminPath"},
|
||||
log:{warn:function(){},_:function(){},trace:function(){},audit:function(){}},
|
||||
events:{on:function(){},removeListener:function(){}}
|
||||
});
|
||||
comms.init(server, {httpAdminRoot:"/adminPath/"}, {comms: mockComms});
|
||||
server.listen(listenPort, address);
|
||||
server.on('listening', function() {
|
||||
port = server.address().port;
|
||||
@@ -257,8 +220,10 @@ describe("api/editor/comms", function() {
|
||||
});
|
||||
|
||||
it('accepts connections',function(done) {
|
||||
connections.length.should.eql(0);
|
||||
var ws = new WebSocket(url);
|
||||
ws.on('open', function() {
|
||||
connections.length.should.eql(1);
|
||||
ws.close();
|
||||
done();
|
||||
});
|
||||
@@ -276,11 +241,7 @@ describe("api/editor/comms", function() {
|
||||
before(function(done) {
|
||||
sinon.stub(Users,"default",function() { return when.resolve(null);});
|
||||
server = stoppable(http.createServer(function(req,res){app(req,res)}));
|
||||
comms.init(server, {
|
||||
settings:{httpAdminRoot:"adminPath"},
|
||||
log:{warn:function(){},_:function(){},trace:function(){},audit:function(){}},
|
||||
events:{on:function(){},removeListener:function(){}}
|
||||
});
|
||||
comms.init(server, {httpAdminRoot:"adminPath"}, {comms: mockComms});
|
||||
server.listen(listenPort, address);
|
||||
server.on('listening', function() {
|
||||
port = server.address().port;
|
||||
@@ -297,8 +258,10 @@ describe("api/editor/comms", function() {
|
||||
});
|
||||
|
||||
it('accepts connections',function(done) {
|
||||
connections.length.should.eql(0);
|
||||
var ws = new WebSocket(url);
|
||||
ws.on('open', function() {
|
||||
connections.length.should.eql(1);
|
||||
ws.close();
|
||||
done();
|
||||
});
|
||||
@@ -316,11 +279,7 @@ describe("api/editor/comms", function() {
|
||||
before(function(done) {
|
||||
sinon.stub(Users,"default",function() { return when.resolve(null);});
|
||||
server = stoppable(http.createServer(function(req,res){app(req,res)}));
|
||||
comms.init(server, {
|
||||
settings:{webSocketKeepAliveTime: 100},
|
||||
log:{warn:function(){},_:function(){},trace:function(){},audit:function(){}},
|
||||
events:{on:function(){},removeListener:function(){}}
|
||||
});
|
||||
comms.init(server, {webSocketKeepAliveTime: 100}, {comms: mockComms});
|
||||
server.listen(listenPort, address);
|
||||
server.on('listening', function() {
|
||||
port = server.address().port;
|
||||
@@ -355,7 +314,7 @@ describe("api/editor/comms", function() {
|
||||
ws.on('open', function() {
|
||||
ws.send('{"subscribe":"foo"}');
|
||||
interval = setInterval(function() {
|
||||
comms.publish('foo', 'bar');
|
||||
connections[0].send('foo', 'bar');
|
||||
}, 50);
|
||||
});
|
||||
ws.on('message', function(data) {
|
||||
@@ -403,11 +362,7 @@ describe("api/editor/comms", function() {
|
||||
|
||||
|
||||
server = stoppable(http.createServer(function(req,res){app(req,res)}));
|
||||
comms.init(server,{
|
||||
settings:{adminAuth:{}},
|
||||
log:{warn:function(){},_:function(){},trace:function(){},audit:function(){}},
|
||||
events:{on:function(){},removeListener:function(){}}
|
||||
});
|
||||
comms.init(server, {adminAuth:{}}, {comms: mockComms});
|
||||
server.listen(listenPort, address);
|
||||
server.on('listening', function() {
|
||||
port = server.address().port;
|
||||
@@ -447,7 +402,7 @@ describe("api/editor/comms", function() {
|
||||
if (received == 1) {
|
||||
msg.should.equal('{"auth":"ok"}');
|
||||
ws.send('{"subscribe":"foo"}');
|
||||
comms.publish('foo', 'correct');
|
||||
connections[0].send('foo', 'correct');
|
||||
} else {
|
||||
msg.should.equal('[{"topic":"foo","data":"correct"}]');
|
||||
ws.close();
|
||||
@@ -494,11 +449,7 @@ describe("api/editor/comms", function() {
|
||||
before(function(done) {
|
||||
getDefaultUser = sinon.stub(Users,"default",function() { return when.resolve({permissions:"read"});});
|
||||
server = stoppable(http.createServer(function(req,res){app(req,res)}));
|
||||
comms.init(server, {
|
||||
settings:{adminAuth:{}},
|
||||
log:{warn:function(){},_:function(){},trace:function(){},audit:function(){}},
|
||||
events:{on:function(){},removeListener:function(){}}
|
||||
});
|
||||
comms.init(server, {adminAuth:{}}, {comms: mockComms});
|
||||
server.listen(listenPort, address);
|
||||
server.on('listening', function() {
|
||||
port = server.address().port;
|
||||
@@ -520,7 +471,7 @@ describe("api/editor/comms", function() {
|
||||
ws.on('open', function() {
|
||||
ws.send('{"subscribe":"foo"}');
|
||||
setTimeout(function() {
|
||||
comms.publish('foo', 'correct');
|
||||
connections[0].send('foo', 'correct');
|
||||
},200);
|
||||
});
|
||||
ws.on('message', function(msg) {
|
||||
|
||||
@@ -29,64 +29,30 @@ describe('api/editor/credentials', function() {
|
||||
app = express();
|
||||
app.get('/credentials/:type/:id',credentials.get);
|
||||
credentials.init({
|
||||
log:{audit:function(){}},
|
||||
nodes:{
|
||||
getCredentials: function(id) {
|
||||
if (id === "n1") {
|
||||
return {user1:"abc",password1:"123"};
|
||||
flows: {
|
||||
getNodeCredentials: function(opts) {
|
||||
if (opts.type === "known-type" && opts.id === "n1") {
|
||||
return Promise.resolve({
|
||||
user1:"abc",
|
||||
has_password1: true
|
||||
});
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
getCredentialDefinition:function(type) {
|
||||
if (type === "known-type") {
|
||||
return {user1:{type:"text"},password1:{type:"password"}};
|
||||
} else {
|
||||
return null;
|
||||
var err = new Error("message");
|
||||
err.code = "test_code";
|
||||
var p = Promise.reject(err);
|
||||
p.catch(()=>{});
|
||||
return p;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
it('returns empty credentials if unknown type',function(done) {
|
||||
request(app)
|
||||
.get("/credentials/unknown-type/n1")
|
||||
.expect(200)
|
||||
.expect("Content-Type",/json/)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
done(err);
|
||||
} else {
|
||||
try {
|
||||
res.body.should.eql({});
|
||||
done();
|
||||
} catch(e) {
|
||||
done(e);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
it('returns empty credentials if none are stored',function(done) {
|
||||
request(app)
|
||||
.get("/credentials/known-type/n2")
|
||||
.expect("Content-Type",/json/)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
done(err);
|
||||
} else {
|
||||
try {
|
||||
res.body.should.eql({});
|
||||
done();
|
||||
} catch(e) {
|
||||
done(e);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
it('returns stored credentials',function(done) {
|
||||
request(app)
|
||||
.get("/credentials/known-type/n1")
|
||||
.expect("Content-Type",/json/)
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
done(err);
|
||||
@@ -102,5 +68,26 @@ describe('api/editor/credentials', function() {
|
||||
}
|
||||
})
|
||||
});
|
||||
it('returns any error',function(done) {
|
||||
request(app)
|
||||
.get("/credentials/unknown-type/n2")
|
||||
.expect("Content-Type",/json/)
|
||||
.expect(500)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
done(err);
|
||||
} else {
|
||||
try {
|
||||
res.body.should.have.property('code');
|
||||
res.body.code.should.be.equal("test_code");
|
||||
res.body.should.have.property('message');
|
||||
res.body.message.should.be.equal('message');
|
||||
done();
|
||||
} catch(e) {
|
||||
done(e);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -22,6 +22,10 @@ var editorApi = require("../../../../red/api/editor");
|
||||
var comms = require("../../../../red/api/editor/comms");
|
||||
var info = require("../../../../red/api/editor/settings");
|
||||
var auth = require("../../../../red/api/auth");
|
||||
|
||||
var log = require("../../../../red/util/log");
|
||||
|
||||
|
||||
var when = require("when");
|
||||
|
||||
|
||||
@@ -37,9 +41,7 @@ describe("api/editor/index", function() {
|
||||
info.init.restore();
|
||||
});
|
||||
it("disables the editor", function() {
|
||||
var editorApp = editorApi.init({},{
|
||||
settings:{disableEditor:true}
|
||||
});
|
||||
var editorApp = editorApi.init({},{disableEditor:true},{});
|
||||
should.not.exist(editorApp);
|
||||
comms.init.called.should.be.false();
|
||||
info.init.called.should.be.false();
|
||||
@@ -67,15 +69,13 @@ describe("api/editor/index", function() {
|
||||
})
|
||||
require("../../../../red/api/editor/theme").app.restore();
|
||||
auth.needsPermission.restore();
|
||||
log.error.restore();
|
||||
});
|
||||
|
||||
before(function() {
|
||||
app = editorApi.init({},{
|
||||
log:{audit:function(){},error:function(msg){errors.push(msg)}},
|
||||
settings:{httpNodeRoot:true, httpAdminRoot: true,disableEditor:false,exportNodeSettings:function(){}},
|
||||
events:{on:function(){},removeListener:function(){}},
|
||||
isStarted: function() { return isStarted; },
|
||||
nodes: {paletteEditorEnabled: function() { return false }}
|
||||
sinon.stub(log,"error",function(err) { errors.push(err)})
|
||||
app = editorApi.init({},{httpNodeRoot:true, httpAdminRoot: true,disableEditor:false,exportNodeSettings:function(){}},{
|
||||
isStarted: () => Promise.resolve(isStarted)
|
||||
});
|
||||
});
|
||||
it('serves the editor', function(done) {
|
||||
@@ -117,7 +117,7 @@ describe("api/editor/index", function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
// it('GET /settings', function(done) {
|
||||
// it.skip('GET /settings', function(done) {
|
||||
// request(app).get("/settings").expect(200).end(function(err,res) {
|
||||
// if (err) {
|
||||
// return done(err);
|
||||
|
||||
@@ -16,334 +16,287 @@
|
||||
|
||||
var should = require("should");
|
||||
var sinon = require("sinon");
|
||||
var fs = require("fs");
|
||||
var fspath = require('path');
|
||||
var request = require('supertest');
|
||||
var express = require('express');
|
||||
var bodyParser = require('body-parser');
|
||||
|
||||
var when = require('when');
|
||||
|
||||
var app;
|
||||
|
||||
var library = require("../../../../red/api/editor/library");
|
||||
var auth = require("../../../../red/api/auth");
|
||||
|
||||
describe("api/editor/library", function() {
|
||||
|
||||
function initLibrary(_flows,_libraryEntries,_examples,_exampleFlowPathFunction) {
|
||||
var flows = _flows;
|
||||
var libraryEntries = _libraryEntries;
|
||||
library.init(app,{
|
||||
log:{audit:function(){},_:function(){},warn:function(){}},
|
||||
storage: {
|
||||
init: function() {
|
||||
return when.resolve();
|
||||
},
|
||||
getAllFlows: function() {
|
||||
return when.resolve(flows);
|
||||
},
|
||||
getFlow: function(fn) {
|
||||
if (flows[fn]) {
|
||||
return when.resolve(flows[fn]);
|
||||
} else if (fn.indexOf("..")!==-1) {
|
||||
var err = new Error();
|
||||
err.code = 'forbidden';
|
||||
return when.reject(err);
|
||||
} else {
|
||||
return when.reject();
|
||||
}
|
||||
},
|
||||
saveFlow: function(fn,data) {
|
||||
if (fn.indexOf("..")!==-1) {
|
||||
var err = new Error();
|
||||
err.code = 'forbidden';
|
||||
return when.reject(err);
|
||||
}
|
||||
flows[fn] = data;
|
||||
return when.resolve();
|
||||
},
|
||||
getLibraryEntry: function(type,path) {
|
||||
if (path.indexOf("..")!==-1) {
|
||||
var err = new Error();
|
||||
err.code = 'forbidden';
|
||||
return when.reject(err);
|
||||
}
|
||||
if (libraryEntries[type] && libraryEntries[type][path]) {
|
||||
return when.resolve(libraryEntries[type][path]);
|
||||
} else {
|
||||
return when.reject();
|
||||
}
|
||||
},
|
||||
saveLibraryEntry: function(type,path,meta,body) {
|
||||
if (path.indexOf("..")!==-1) {
|
||||
var err = new Error();
|
||||
err.code = 'forbidden';
|
||||
return when.reject(err);
|
||||
}
|
||||
libraryEntries[type][path] = body;
|
||||
return when.resolve();
|
||||
before(function() {
|
||||
app = express();
|
||||
app.use(bodyParser.json());
|
||||
|
||||
app.get("/library/flows",library.getAll);
|
||||
app.post(/library\/([^\/]+)\/(.*)/,library.saveEntry);
|
||||
app.get(/library\/([^\/]+)(?:$|\/(.*))/,library.getEntry);
|
||||
});
|
||||
after(function() {
|
||||
});
|
||||
it('returns all flows', function(done) {
|
||||
library.init({
|
||||
library: {
|
||||
getEntries: function(opts) {
|
||||
return Promise.resolve({a:1,b:2});
|
||||
}
|
||||
},
|
||||
events: {
|
||||
on: function(){},
|
||||
removeListener: function(){}
|
||||
},
|
||||
nodes: {
|
||||
getNodeExampleFlows: function() {
|
||||
return _examples;
|
||||
},
|
||||
getNodeExampleFlowPath: _exampleFlowPathFunction
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
describe("flows", function() {
|
||||
before(function() {
|
||||
app = express();
|
||||
app.use(bodyParser.json());
|
||||
app.get("/library/flows",library.getAll);
|
||||
app.post(new RegExp("/library/flows\/(.*)"),library.post);
|
||||
app.get(new RegExp("/library/flows\/(.*)"),library.get);
|
||||
app.response.sendFile = function (path) {
|
||||
app.response.json.call(this, {sendFile: path});
|
||||
};
|
||||
sinon.stub(fs,"statSync",function() { return true; });
|
||||
});
|
||||
after(function() {
|
||||
fs.statSync.restore();
|
||||
});
|
||||
it('returns empty result', function(done) {
|
||||
initLibrary({},{flows:{}});
|
||||
request(app)
|
||||
.get('/library/flows')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.not.have.property('f');
|
||||
res.body.should.not.have.property('d');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns 404 for non-existent entry', function(done) {
|
||||
initLibrary({},{flows:{}});
|
||||
request(app)
|
||||
.get('/library/flows/foo')
|
||||
.expect(404)
|
||||
.end(done);
|
||||
});
|
||||
|
||||
|
||||
it('can store and retrieve item', function(done) {
|
||||
initLibrary({},{flows:{}});
|
||||
var flow = '[]';
|
||||
request(app)
|
||||
.post('/library/flows/foo')
|
||||
.set('Content-Type', 'application/json')
|
||||
.send(flow)
|
||||
.expect(204).end(function (err, res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
request(app)
|
||||
.get('/library/flows/foo')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.text.should.equal(flow);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('lists a stored item', function(done) {
|
||||
initLibrary({f:["bar"]});
|
||||
request(app)
|
||||
.get('/library/flows')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.have.property('f');
|
||||
should.deepEqual(res.body.f,['bar']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns 403 for malicious get attempt', function(done) {
|
||||
initLibrary({});
|
||||
// without the userDir override the malicious url would be
|
||||
// http://127.0.0.1:1880/library/flows/../../package to
|
||||
// obtain package.json from the node-red root.
|
||||
request(app)
|
||||
.get('/library/flows/../../../../../package')
|
||||
.expect(403)
|
||||
.end(done);
|
||||
});
|
||||
it('returns 403 for malicious post attempt', function(done) {
|
||||
initLibrary({});
|
||||
// without the userDir override the malicious url would be
|
||||
// http://127.0.0.1:1880/library/flows/../../package to
|
||||
// obtain package.json from the node-red root.
|
||||
request(app)
|
||||
.post('/library/flows/../../../../../package')
|
||||
.expect(403)
|
||||
.end(done);
|
||||
});
|
||||
it('includes examples flows if set', function(done) {
|
||||
var examples = {"d":{"node-module":{"f":["example-one"]}}};
|
||||
initLibrary({},{},examples);
|
||||
request(app)
|
||||
.get('/library/flows')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.have.property('d');
|
||||
res.body.d.should.have.property('_examples_');
|
||||
should.deepEqual(res.body.d._examples_,examples);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can retrieve an example flow', function(done) {
|
||||
var examples = {"d":{"node-module":{"f":["example-one"]}}};
|
||||
initLibrary({},{},examples,function(module,path) {
|
||||
return module + ':' + path
|
||||
request(app)
|
||||
.get('/library/flows')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property('a',1);
|
||||
res.body.should.have.property('b',2);
|
||||
done();
|
||||
});
|
||||
request(app)
|
||||
.get('/library/flows/_examples_/node-module/example-one')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.have.property('sendFile',
|
||||
'node-module:example-one');
|
||||
done();
|
||||
});
|
||||
})
|
||||
it('returns an error on all flows', function(done) {
|
||||
library.init({
|
||||
library: {
|
||||
getEntries: function(opts) {
|
||||
var err = new Error("message");
|
||||
err.code = "random_error";
|
||||
err.status = 400;
|
||||
var p = Promise.reject(err);
|
||||
p.catch(()=>{});
|
||||
return p;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('can retrieve an example flow in an org scoped package', function(done) {
|
||||
var examples = {"d":{"@org_scope/node_package":{"f":["example-one"]}}};
|
||||
initLibrary({},{},examples,function(module,path) {
|
||||
return module + ':' + path
|
||||
request(app)
|
||||
.get('/library/flows')
|
||||
.expect(400)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property('code');
|
||||
res.body.code.should.be.equal("random_error");
|
||||
res.body.should.have.property('message');
|
||||
res.body.message.should.be.equal("message");
|
||||
done();
|
||||
});
|
||||
request(app)
|
||||
.get('/library/flows/_examples_/@org_scope/node_package/example-one')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.have.property('sendFile',
|
||||
'@org_scope/node_package:example-one');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("type", function() {
|
||||
before(function() {
|
||||
|
||||
app = express();
|
||||
app.use(bodyParser.json());
|
||||
initLibrary({},{});
|
||||
auth.init({settings:{}});
|
||||
library.register("test");
|
||||
it('returns an individual entry - flow type', function(done) {
|
||||
var opts;
|
||||
library.init({
|
||||
library: {
|
||||
getEntry: function(_opts) {
|
||||
opts = _opts;
|
||||
return Promise.resolve('{"a":1,"b":2}');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('returns empty result', function(done) {
|
||||
initLibrary({},{'test':{"":[]}});
|
||||
request(app)
|
||||
.get('/library/test')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.not.have.property('f');
|
||||
done();
|
||||
});
|
||||
request(app)
|
||||
.get('/library/flows/abc')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property('a',1);
|
||||
res.body.should.have.property('b',2);
|
||||
opts.should.have.property('type','flows');
|
||||
opts.should.have.property('path','abc');
|
||||
done();
|
||||
});
|
||||
})
|
||||
it('returns a directory listing - flow type', function(done) {
|
||||
var opts;
|
||||
library.init({
|
||||
library: {
|
||||
getEntry: function(_opts) {
|
||||
opts = _opts;
|
||||
return Promise.resolve({"a":1,"b":2});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('returns 404 for non-existent entry', function(done) {
|
||||
initLibrary({},{});
|
||||
request(app)
|
||||
.get('/library/test/foo')
|
||||
.expect(404)
|
||||
.end(done);
|
||||
request(app)
|
||||
.get('/library/flows/abc/def')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property('a',1);
|
||||
res.body.should.have.property('b',2);
|
||||
opts.should.have.property('type','flows');
|
||||
opts.should.have.property('path','abc/def');
|
||||
done();
|
||||
});
|
||||
})
|
||||
it('returns an individual entry - non-flow type', function(done) {
|
||||
var opts;
|
||||
library.init({
|
||||
library: {
|
||||
getEntry: function(_opts) {
|
||||
opts = _opts;
|
||||
return Promise.resolve('{"a":1,"b":2}');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('can store and retrieve item', function(done) {
|
||||
initLibrary({},{'test':{}});
|
||||
var flow = {text:"test content"};
|
||||
request(app)
|
||||
.post('/library/test/foo')
|
||||
.set('Content-Type', 'application/json')
|
||||
.send(flow)
|
||||
.expect(204).end(function (err, res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
request(app)
|
||||
.get('/library/test/foo')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.text.should.equal(flow.text);
|
||||
done();
|
||||
});
|
||||
});
|
||||
request(app)
|
||||
.get('/library/non-flow/abc')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
opts.should.have.property('type','non-flow');
|
||||
opts.should.have.property('path','abc');
|
||||
res.text.should.eql('{"a":1,"b":2}');
|
||||
done();
|
||||
});
|
||||
})
|
||||
it('returns a directory listing - non-flow type', function(done) {
|
||||
var opts;
|
||||
library.init({
|
||||
library: {
|
||||
getEntry: function(_opts) {
|
||||
opts = _opts;
|
||||
return Promise.resolve({"a":1,"b":2});
|
||||
}
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.get('/library/non-flow/abc/def')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property('a',1);
|
||||
res.body.should.have.property('b',2);
|
||||
opts.should.have.property('type','non-flow');
|
||||
opts.should.have.property('path','abc/def');
|
||||
done();
|
||||
});
|
||||
})
|
||||
|
||||
it('lists a stored item', function(done) {
|
||||
initLibrary({},{'test':{'a':['abc','def']}});
|
||||
request(app)
|
||||
.get('/library/test/a')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
// This response isn't strictly accurate - but it
|
||||
// verifies the api returns what storage gave it
|
||||
should.deepEqual(res.body,['abc','def']);
|
||||
done();
|
||||
});
|
||||
it('returns an error on individual get', function(done) {
|
||||
var opts;
|
||||
library.init({
|
||||
library: {
|
||||
getEntry: function(_opts) {
|
||||
opts = _opts;
|
||||
var err = new Error("message");
|
||||
err.code = "random_error";
|
||||
err.status = 400;
|
||||
var p = Promise.reject(err);
|
||||
p.catch(()=>{});
|
||||
return p;
|
||||
}
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.get('/library/flows/123')
|
||||
.expect(400)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
opts.should.have.property('type','flows');
|
||||
opts.should.have.property('path','123');
|
||||
|
||||
res.body.should.have.property('code');
|
||||
res.body.code.should.be.equal("random_error");
|
||||
res.body.should.have.property('message');
|
||||
res.body.message.should.be.equal("message");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('returns 403 for malicious access attempt', function(done) {
|
||||
request(app)
|
||||
.get('/library/test/../../../../../../../../../../etc/passwd')
|
||||
.expect(403)
|
||||
.end(done);
|
||||
it('saves an individual entry - flow type', function(done) {
|
||||
var opts;
|
||||
library.init({
|
||||
library: {
|
||||
saveEntry: function(_opts) {
|
||||
opts = _opts;
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.post('/library/flows/abc/def')
|
||||
.expect(204)
|
||||
.send({a:1,b:2,c:3})
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
opts.should.have.property('type','flows');
|
||||
opts.should.have.property('path','abc/def');
|
||||
opts.should.have.property('meta',{});
|
||||
opts.should.have.property('body',JSON.stringify({a:1,b:2,c:3}));
|
||||
done();
|
||||
});
|
||||
})
|
||||
|
||||
it('returns 403 for malicious access attempt', function(done) {
|
||||
request(app)
|
||||
.get('/library/test/..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\etc\\passwd')
|
||||
.expect(403)
|
||||
.end(done);
|
||||
it('saves an individual entry - non-flow type', function(done) {
|
||||
var opts;
|
||||
library.init({
|
||||
library: {
|
||||
saveEntry: function(_opts) {
|
||||
opts = _opts;
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.post('/library/non-flow/abc/def')
|
||||
.expect(204)
|
||||
.send({a:1,b:2,text:"123"})
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
opts.should.have.property('type','non-flow');
|
||||
opts.should.have.property('path','abc/def');
|
||||
opts.should.have.property('meta',{a:1,b:2});
|
||||
opts.should.have.property('body',"123");
|
||||
done();
|
||||
});
|
||||
})
|
||||
|
||||
it('returns 403 for malicious access attempt', function(done) {
|
||||
request(app)
|
||||
.post('/library/test/../../../../../../../../../../etc/passwd')
|
||||
.set('Content-Type', 'text/plain')
|
||||
.send('root:x:0:0:root:/root:/usr/bin/tclsh')
|
||||
.expect(403)
|
||||
.end(done);
|
||||
it('returns an error on individual save', function(done) {
|
||||
var opts;
|
||||
library.init({
|
||||
library: {
|
||||
saveEntry: function(_opts) {
|
||||
opts = _opts;
|
||||
var err = new Error("message");
|
||||
err.code = "random_error";
|
||||
err.status = 400;
|
||||
var p = Promise.reject(err);
|
||||
p.catch(()=>{});
|
||||
return p;
|
||||
}
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.post('/library/non-flow/abc/def')
|
||||
.send({a:1,b:2,text:"123"})
|
||||
.expect(400)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
opts.should.have.property('type','non-flow');
|
||||
opts.should.have.property('path','abc/def');
|
||||
|
||||
res.body.should.have.property('code');
|
||||
res.body.code.should.be.equal("random_error");
|
||||
res.body.should.have.property('message');
|
||||
res.body.message.should.be.equal("message");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,6 +20,7 @@ var express = require('express');
|
||||
var sinon = require('sinon');
|
||||
|
||||
var locales = require("../../../../red/api/editor/locales");
|
||||
var i18n = require("../../../../red/util").i18n;
|
||||
|
||||
describe("api/editor/locales", function() {
|
||||
beforeEach(function() {
|
||||
@@ -29,25 +30,40 @@ describe("api/editor/locales", function() {
|
||||
describe('get named resource catalog',function() {
|
||||
var app;
|
||||
before(function() {
|
||||
// locales.init({
|
||||
// i18n: {
|
||||
// i: {
|
||||
// language: function() { return 'en-US'},
|
||||
// changeLanguage: function(lang,callback) {
|
||||
// if (callback) {
|
||||
// callback();
|
||||
// }
|
||||
// },
|
||||
// getResourceBundle: function(lang, namespace) {
|
||||
// return {namespace:namespace, lang:lang};
|
||||
// }
|
||||
// },
|
||||
// }
|
||||
// });
|
||||
locales.init({});
|
||||
|
||||
// bit of a mess of internal workings
|
||||
locales.init({
|
||||
i18n: {
|
||||
i: {
|
||||
language: function() { return 'en-US'},
|
||||
changeLanguage: function(lang,callback) {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
getResourceBundle: function(lang, namespace) {
|
||||
return {namespace:namespace, lang:lang};
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
sinon.stub(i18n.i,'changeLanguage',function(lang,callback) { if (callback) {callback();}});
|
||||
if (i18n.i.getResourceBundle) {
|
||||
sinon.stub(i18n.i,'getResourceBundle',function(lang, namespace) {return {namespace:namespace, lang:lang};});
|
||||
} else {
|
||||
// If i18n.init has not been called, then getResourceBundle isn't
|
||||
// defined - so hardcode a stub
|
||||
i18n.i.getResourceBundle = function(lang, namespace) {return {namespace:namespace, lang:lang};};
|
||||
i18n.i.getResourceBundle.restore = function() { delete i18n.i.getResourceBundle };
|
||||
}
|
||||
app = express();
|
||||
app.get(/locales\/(.+)\/?$/,locales.get);
|
||||
});
|
||||
after(function() {
|
||||
i18n.i.changeLanguage.restore();
|
||||
i18n.i.getResourceBundle.restore();
|
||||
})
|
||||
it('returns with default language', function(done) {
|
||||
request(app)
|
||||
.get("/locales/message-catalog")
|
||||
@@ -75,50 +91,49 @@ describe("api/editor/locales", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('get all node resource catalogs',function() {
|
||||
var app;
|
||||
before(function() {
|
||||
// bit of a mess of internal workings
|
||||
locales.init({
|
||||
i18n: {
|
||||
i:{
|
||||
getResourceBundle: function(lang, namespace) {
|
||||
return {
|
||||
"node-red": "should not return",
|
||||
"test-module-a-id": "test-module-a-catalog",
|
||||
"test-module-b-id": "test-module-b-catalog",
|
||||
"test-module-c-id": "test-module-c-catalog"
|
||||
}[namespace]
|
||||
}
|
||||
}
|
||||
},
|
||||
nodes: {
|
||||
getNodeList: function() {
|
||||
return [
|
||||
{module:"node-red",id:"node-red-id"},
|
||||
{module:"test-module-a",id:"test-module-a-id"},
|
||||
{module:"test-module-b",id:"test-module-b-id"}
|
||||
];
|
||||
}
|
||||
}
|
||||
});
|
||||
app = express();
|
||||
app.get("/locales/nodes",locales.getAllNodes);
|
||||
});
|
||||
it('returns with the node catalogs', function(done) {
|
||||
request(app)
|
||||
.get("/locales/nodes")
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.eql({
|
||||
'test-module-a-id': 'test-module-a-catalog',
|
||||
'test-module-b-id': 'test-module-b-catalog'
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
// describe('get all node resource catalogs',function() {
|
||||
// var app;
|
||||
// before(function() {
|
||||
// // bit of a mess of internal workings
|
||||
// sinon.stub(i18n,'catalog',function(namespace, lang) {
|
||||
// return {
|
||||
// "node-red": "should not return",
|
||||
// "test-module-a-id": "test-module-a-catalog",
|
||||
// "test-module-b-id": "test-module-b-catalog",
|
||||
// "test-module-c-id": "test-module-c-catalog"
|
||||
// }[namespace]
|
||||
// });
|
||||
// locales.init({
|
||||
// nodes: {
|
||||
// getNodeList: function(opts) {
|
||||
// return Promise.resolve([
|
||||
// {module:"node-red",id:"node-red-id"},
|
||||
// {module:"test-module-a",id:"test-module-a-id"},
|
||||
// {module:"test-module-b",id:"test-module-b-id"}
|
||||
// ]);
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// app = express();
|
||||
// app.get("/locales/nodes",locales.getAllNodes);
|
||||
// });
|
||||
// after(function() {
|
||||
// i18n.catalog.restore();
|
||||
// })
|
||||
// it('returns with the node catalogs', function(done) {
|
||||
// request(app)
|
||||
// .get("/locales/nodes")
|
||||
// .expect(200)
|
||||
// .end(function(err,res) {
|
||||
// if (err) {
|
||||
// return done(err);
|
||||
// }
|
||||
// res.body.should.eql({
|
||||
// 'test-module-a-id': 'test-module-a-catalog',
|
||||
// 'test-module-b-id': 'test-module-b-catalog'
|
||||
// });
|
||||
// done();
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
});
|
||||
|
||||
@@ -17,267 +17,103 @@
|
||||
var should = require("should");
|
||||
var request = require('supertest');
|
||||
var express = require('express');
|
||||
var bodyParser = require("body-parser");
|
||||
var sinon = require('sinon');
|
||||
var when = require('when');
|
||||
|
||||
var app = express();
|
||||
var app;
|
||||
var info = require("../../../../red/api/editor/settings");
|
||||
var theme = require("../../../../red/api/editor/theme");
|
||||
|
||||
describe("api/editor/settings", function() {
|
||||
describe("settings handler", function() {
|
||||
before(function() {
|
||||
sinon.stub(theme,"settings",function() { return { test: 456 };});
|
||||
app = express();
|
||||
app.get("/settings",info.runtimeSettings);
|
||||
app.get("/settingsWithUser",function(req,res,next) {
|
||||
req.user = {
|
||||
username: "nick",
|
||||
permissions: "*",
|
||||
image: "http://example.com",
|
||||
anonymous: false,
|
||||
private: "secret"
|
||||
before(function() {
|
||||
sinon.stub(theme,"settings",function() { return { test: 456 };});
|
||||
app = express();
|
||||
app.use(bodyParser.json());
|
||||
app.get("/settings",info.runtimeSettings);
|
||||
app.get("/settings/user",function(req,res,next) {req.user = "fred"; next()}, info.userSettings);
|
||||
app.post("/settings/user",function(req,res,next) {req.user = "fred"; next()},info.updateUserSettings);
|
||||
});
|
||||
|
||||
after(function() {
|
||||
theme.settings.restore();
|
||||
});
|
||||
|
||||
it('returns the runtime settings', function(done) {
|
||||
info.init({
|
||||
settings: {
|
||||
getRuntimeSettings: function(opts) {
|
||||
return Promise.resolve({
|
||||
a:1,
|
||||
b:2
|
||||
})
|
||||
}
|
||||
next();
|
||||
},info.runtimeSettings);
|
||||
}
|
||||
});
|
||||
|
||||
after(function() {
|
||||
theme.settings.restore();
|
||||
request(app)
|
||||
.get("/settings")
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property("a",1);
|
||||
res.body.should.have.property("b",2);
|
||||
res.body.should.have.property("editorTheme",{test:456});
|
||||
done();
|
||||
});
|
||||
|
||||
it('returns the filtered settings', function(done) {
|
||||
info.init({
|
||||
settings: {
|
||||
foo: 123,
|
||||
httpNodeRoot: "testHttpNodeRoot",
|
||||
version: "testVersion",
|
||||
paletteCategories :["red","blue","green"],
|
||||
exportNodeSettings: function(obj) {
|
||||
obj.testNodeSetting = "helloWorld";
|
||||
}
|
||||
},
|
||||
nodes: {
|
||||
paletteEditorEnabled: function() { return true; },
|
||||
getCredentialKeyType: function() { return "test-key-type"},
|
||||
listContextStores: function() { return {default: "foo", stores: ["foo","bar"]}}
|
||||
},
|
||||
log: { error: console.error },
|
||||
storage: {}
|
||||
});
|
||||
request(app)
|
||||
.get("/settings")
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property("httpNodeRoot","testHttpNodeRoot");
|
||||
res.body.should.have.property("version","testVersion");
|
||||
res.body.should.have.property("context",{default: "foo", stores: ["foo","bar"]});
|
||||
res.body.should.have.property("paletteCategories",["red","blue","green"]);
|
||||
res.body.should.have.property("editorTheme",{test:456});
|
||||
res.body.should.have.property("testNodeSetting","helloWorld");
|
||||
res.body.should.not.have.property("foo",123);
|
||||
res.body.should.have.property("flowEncryptionType","test-key-type");
|
||||
res.body.should.not.have.property("user");
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('returns the filtered user in settings', function(done) {
|
||||
info.init({
|
||||
settings: {
|
||||
foo: 123,
|
||||
httpNodeRoot: "testHttpNodeRoot",
|
||||
version: "testVersion",
|
||||
paletteCategories :["red","blue","green"],
|
||||
exportNodeSettings: function(obj) {
|
||||
obj.testNodeSetting = "helloWorld";
|
||||
}
|
||||
},
|
||||
nodes: {
|
||||
paletteEditorEnabled: function() { return true; },
|
||||
getCredentialKeyType: function() { return "test-key-type"},
|
||||
listContextStores: function() { return {default: "foo", stores: ["foo","bar"]}}
|
||||
},
|
||||
log: { error: console.error },
|
||||
storage: {}
|
||||
});
|
||||
request(app)
|
||||
.get("/settingsWithUser")
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property("user");
|
||||
res.body.user.should.have.property("username","nick");
|
||||
res.body.user.should.have.property("permissions","*");
|
||||
res.body.user.should.have.property("image","http://example.com");
|
||||
res.body.user.should.have.property("anonymous",false);
|
||||
res.body.user.should.not.have.property("private");
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('includes project settings if projects available', function(done) {
|
||||
info.init({
|
||||
settings: {
|
||||
foo: 123,
|
||||
httpNodeRoot: "testHttpNodeRoot",
|
||||
version: "testVersion",
|
||||
paletteCategories :["red","blue","green"],
|
||||
exportNodeSettings: function(obj) {
|
||||
obj.testNodeSetting = "helloWorld";
|
||||
}
|
||||
},
|
||||
nodes: {
|
||||
paletteEditorEnabled: function() { return true; },
|
||||
getCredentialKeyType: function() { return "test-key-type"},
|
||||
listContextStores: function() { return {default: "foo", stores: ["foo","bar"]}}
|
||||
},
|
||||
log: { error: console.error },
|
||||
storage: {
|
||||
projects: {
|
||||
getActiveProject: () => 'test-active-project',
|
||||
getFlowFilename: () => 'test-flow-file',
|
||||
getCredentialsFilename: () => 'test-creds-file',
|
||||
getGlobalGitUser: () => {return {name:'foo',email:'foo@example.com'}}
|
||||
});
|
||||
it('returns the user settings', function(done) {
|
||||
info.init({
|
||||
settings: {
|
||||
getUserSettings: function(opts) {
|
||||
if (opts.user !== "fred") {
|
||||
return Promise.reject(new Error("Unknown user"));
|
||||
}
|
||||
return Promise.resolve({
|
||||
c:3,
|
||||
d:4
|
||||
})
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.get("/settings")
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property("project","test-active-project");
|
||||
res.body.should.not.have.property("files");
|
||||
res.body.should.have.property("git");
|
||||
res.body.git.should.have.property("globalUser",{name:'foo',email:'foo@example.com'});
|
||||
done();
|
||||
});
|
||||
}
|
||||
});
|
||||
it('includes existing files details if projects enabled but no active project and files exist', function(done) {
|
||||
info.init({
|
||||
settings: {
|
||||
foo: 123,
|
||||
httpNodeRoot: "testHttpNodeRoot",
|
||||
version: "testVersion",
|
||||
paletteCategories :["red","blue","green"],
|
||||
exportNodeSettings: function(obj) {
|
||||
obj.testNodeSetting = "helloWorld";
|
||||
}
|
||||
},
|
||||
nodes: {
|
||||
paletteEditorEnabled: function() { return true; },
|
||||
getCredentialKeyType: function() { return "test-key-type"},
|
||||
listContextStores: function() { return {default: "foo", stores: ["foo","bar"]}}
|
||||
},
|
||||
log: { error: console.error },
|
||||
storage: {
|
||||
projects: {
|
||||
flowFileExists: () => true,
|
||||
getActiveProject: () => false,
|
||||
getFlowFilename: () => 'test-flow-file',
|
||||
getCredentialsFilename: () => 'test-creds-file',
|
||||
getGlobalGitUser: () => {return {name:'foo',email:'foo@example.com'}}
|
||||
request(app)
|
||||
.get("/settings/user")
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.eql({c:3,d:4});
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('updates the user settings', function(done) {
|
||||
var update;
|
||||
info.init({
|
||||
settings: {
|
||||
updateUserSettings: function(opts) {
|
||||
if (opts.user !== "fred") {
|
||||
return Promise.reject(new Error("Unknown user"));
|
||||
}
|
||||
update = opts.settings;
|
||||
return Promise.resolve()
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.get("/settings")
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.not.have.property("project");
|
||||
res.body.should.have.property("files");
|
||||
res.body.files.should.have.property("flow",'test-flow-file');
|
||||
res.body.files.should.have.property("credentials",'test-creds-file');
|
||||
res.body.should.have.property("git");
|
||||
res.body.git.should.have.property("globalUser",{name:'foo',email:'foo@example.com'});
|
||||
done();
|
||||
});
|
||||
}
|
||||
});
|
||||
it('does not include file details if projects enabled but no active project and files do not exist', function(done) {
|
||||
info.init({
|
||||
settings: {
|
||||
foo: 123,
|
||||
httpNodeRoot: "testHttpNodeRoot",
|
||||
version: "testVersion",
|
||||
paletteCategories :["red","blue","green"],
|
||||
exportNodeSettings: function(obj) {
|
||||
obj.testNodeSetting = "helloWorld";
|
||||
}
|
||||
},
|
||||
nodes: {
|
||||
paletteEditorEnabled: function() { return true; },
|
||||
getCredentialKeyType: function() { return "test-key-type"},
|
||||
listContextStores: function() { return {default: "foo", stores: ["foo","bar"]}}
|
||||
},
|
||||
log: { error: console.error },
|
||||
storage: {
|
||||
projects: {
|
||||
flowFileExists: () => false,
|
||||
getActiveProject: () => false,
|
||||
getFlowFilename: () => 'test-flow-file',
|
||||
getCredentialsFilename: () => 'test-creds-file',
|
||||
getGlobalGitUser: () => {return {name:'foo',email:'foo@example.com'}}
|
||||
}
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.get("/settings")
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.not.have.property("project");
|
||||
res.body.should.not.have.property("files");
|
||||
res.body.should.have.property("git");
|
||||
res.body.git.should.have.property("globalUser",{name:'foo',email:'foo@example.com'});
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('overrides palette editable if runtime says it is disabled', function(done) {
|
||||
info.init({
|
||||
settings: {
|
||||
httpNodeRoot: "testHttpNodeRoot",
|
||||
version: "testVersion",
|
||||
paletteCategories :["red","blue","green"],
|
||||
exportNodeSettings: function() {}
|
||||
},
|
||||
nodes: {
|
||||
paletteEditorEnabled: function() { return false; },
|
||||
getCredentialKeyType: function() { return "test-key-type"},
|
||||
listContextStores: function() { return {default: "foo", stores: ["foo","bar"]}}
|
||||
},
|
||||
log: { error: console.error },
|
||||
storage: {}
|
||||
|
||||
});
|
||||
request(app)
|
||||
.get("/settings")
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property("httpNodeRoot","testHttpNodeRoot");
|
||||
res.body.should.have.property("version","testVersion");
|
||||
res.body.should.have.property("paletteCategories",["red","blue","green"]);
|
||||
res.body.should.have.property("editorTheme");
|
||||
res.body.editorTheme.should.have.property("test",456);
|
||||
|
||||
res.body.editorTheme.should.have.property("palette",{editable:false});
|
||||
done();
|
||||
});
|
||||
request(app)
|
||||
.post("/settings/user")
|
||||
.send({
|
||||
e:4,
|
||||
f:5
|
||||
})
|
||||
.expect(204)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
update.should.eql({e:4,f:5});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -18,83 +18,42 @@ var should = require("should");
|
||||
var sinon = require("sinon");
|
||||
var request = require("supertest");
|
||||
var express = require("express");
|
||||
var editorApi = require("../../../../red/api/editor");
|
||||
var comms = require("../../../../red/api/editor/comms");
|
||||
var info = require("../../../../red/api/editor/settings");
|
||||
var auth = require("../../../../red/api/auth");
|
||||
var sshkeys = require("../../../../red/api/editor/sshkeys");
|
||||
var when = require("when");
|
||||
var bodyParser = require("body-parser");
|
||||
var fs = require("fs-extra");
|
||||
var fspath = require("path");
|
||||
|
||||
|
||||
describe("api/editor/sshkeys", function() {
|
||||
var app;
|
||||
var mockList = [
|
||||
'library','theme','locales','credentials','comms'
|
||||
]
|
||||
var isStarted = true;
|
||||
var errors = [];
|
||||
var session_data = {};
|
||||
|
||||
var mockRuntime = {
|
||||
settings:{
|
||||
httpNodeRoot: true,
|
||||
httpAdminRoot: true,
|
||||
disableEditor: false,
|
||||
exportNodeSettings:function(){},
|
||||
storage: {
|
||||
getSessions: function(){
|
||||
return when.resolve(session_data);
|
||||
},
|
||||
setSessions: function(_session) {
|
||||
session_data = _session;
|
||||
return when.resolve();
|
||||
}
|
||||
}
|
||||
},
|
||||
log:{audit:function(){},error:function(msg){errors.push(msg)},trace:function(){}},
|
||||
storage: {
|
||||
projects: {
|
||||
ssh: {
|
||||
init: function(){},
|
||||
listSSHKeys: function(){},
|
||||
getSSHKey: function(){},
|
||||
generateSSHKey: function(){},
|
||||
deleteSSHKey: function(){}
|
||||
}
|
||||
}
|
||||
},
|
||||
events:{on:function(){},removeListener:function(){}},
|
||||
isStarted: function() { return isStarted; },
|
||||
nodes: {paletteEditorEnabled: function() { return false }}
|
||||
};
|
||||
|
||||
settings: {
|
||||
getUserKeys: function() {},
|
||||
getUserKey: function() {},
|
||||
generateUserKey: function() {},
|
||||
removeUserKey: function() {}
|
||||
}
|
||||
}
|
||||
before(function() {
|
||||
auth.init(mockRuntime);
|
||||
sshkeys.init(mockRuntime);
|
||||
app = express();
|
||||
app.use(bodyParser.json());
|
||||
app.use(editorApi.init({},mockRuntime));
|
||||
app.use("/settings/user/keys", sshkeys.app());
|
||||
});
|
||||
after(function() {
|
||||
})
|
||||
|
||||
beforeEach(function() {
|
||||
sinon.stub(mockRuntime.storage.projects.ssh, "listSSHKeys");
|
||||
sinon.stub(mockRuntime.storage.projects.ssh, "getSSHKey");
|
||||
sinon.stub(mockRuntime.storage.projects.ssh, "generateSSHKey");
|
||||
sinon.stub(mockRuntime.storage.projects.ssh, "deleteSSHKey");
|
||||
sinon.stub(mockRuntime.settings, "getUserKeys");
|
||||
sinon.stub(mockRuntime.settings, "getUserKey");
|
||||
sinon.stub(mockRuntime.settings, "generateUserKey");
|
||||
sinon.stub(mockRuntime.settings, "removeUserKey");
|
||||
})
|
||||
afterEach(function() {
|
||||
mockRuntime.storage.projects.ssh.listSSHKeys.restore();
|
||||
mockRuntime.storage.projects.ssh.getSSHKey.restore();
|
||||
mockRuntime.storage.projects.ssh.generateSSHKey.restore();
|
||||
mockRuntime.storage.projects.ssh.deleteSSHKey.restore();
|
||||
mockRuntime.settings.getUserKeys.restore();
|
||||
mockRuntime.settings.getUserKey.restore();
|
||||
mockRuntime.settings.generateUserKey.restore();
|
||||
mockRuntime.settings.removeUserKey.restore();
|
||||
})
|
||||
|
||||
it('GET /settings/user/keys --- return empty list', function(done) {
|
||||
mockRuntime.storage.projects.ssh.listSSHKeys.returns(Promise.resolve([]));
|
||||
mockRuntime.settings.getUserKeys.returns(Promise.resolve([]));
|
||||
request(app)
|
||||
.get("/settings/user/keys")
|
||||
.expect(200)
|
||||
@@ -118,7 +77,7 @@ describe("api/editor/sshkeys", function() {
|
||||
name: elem
|
||||
};
|
||||
});
|
||||
mockRuntime.storage.projects.ssh.listSSHKeys.returns(Promise.resolve(retList));
|
||||
mockRuntime.settings.getUserKeys.returns(Promise.resolve(retList));
|
||||
request(app)
|
||||
.get("/settings/user/keys")
|
||||
.expect(200)
|
||||
@@ -139,16 +98,16 @@ describe("api/editor/sshkeys", function() {
|
||||
errInstance.code = "test_code";
|
||||
var p = Promise.reject(errInstance);
|
||||
p.catch(()=>{});
|
||||
mockRuntime.storage.projects.ssh.listSSHKeys.returns(p);
|
||||
mockRuntime.settings.getUserKeys.returns(p);
|
||||
request(app)
|
||||
.get("/settings/user/keys")
|
||||
.expect(400)
|
||||
.expect(500)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property('error');
|
||||
res.body.error.should.be.equal(errInstance.code);
|
||||
res.body.should.have.property('code');
|
||||
res.body.code.should.be.equal(errInstance.code);
|
||||
res.body.should.have.property('message');
|
||||
res.body.message.should.be.equal(errInstance.message);
|
||||
done();
|
||||
@@ -156,7 +115,12 @@ describe("api/editor/sshkeys", function() {
|
||||
});
|
||||
|
||||
it('GET /settings/user/keys/<key_file_name> --- return 404', function(done) {
|
||||
mockRuntime.storage.projects.ssh.getSSHKey.returns(Promise.resolve(null));
|
||||
var errInstance = new Error("Not Found.");
|
||||
errInstance.code = "not_found";
|
||||
errInstance.status = 404;
|
||||
var p = Promise.reject(errInstance);
|
||||
p.catch(()=>{});
|
||||
mockRuntime.settings.getUserKey.returns(p);
|
||||
request(app)
|
||||
.get("/settings/user/keys/NOT_REAL")
|
||||
.expect(404)
|
||||
@@ -168,19 +132,19 @@ describe("api/editor/sshkeys", function() {
|
||||
});
|
||||
});
|
||||
it('GET /settings/user/keys --- return Unexpected Error', function(done) {
|
||||
var errInstance = new Error("Messages.....");
|
||||
var errInstance = new Error();
|
||||
var p = Promise.reject(errInstance);
|
||||
p.catch(()=>{});
|
||||
mockRuntime.storage.projects.ssh.listSSHKeys.returns(p);
|
||||
mockRuntime.settings.getUserKeys.returns(p)
|
||||
request(app)
|
||||
.get("/settings/user/keys")
|
||||
.expect(400)
|
||||
.expect(500)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property('error');
|
||||
res.body.error.should.be.equal("unexpected_error");
|
||||
res.body.should.have.property('code');
|
||||
res.body.code.should.be.equal("unexpected_error");
|
||||
res.body.should.have.property('message');
|
||||
res.body.message.should.be.equal(errInstance.toString());
|
||||
done();
|
||||
@@ -190,7 +154,7 @@ describe("api/editor/sshkeys", function() {
|
||||
it('GET /settings/user/keys/<key_file_name> --- return content', function(done) {
|
||||
var key_file_name = "test_key";
|
||||
var fileContent = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQD3a+sgtgzSbbliWxmOq5p6+H/mE+0gjWfLWrkIVmHENd1mifV4uCmIHAR2NfuadUYMQ3+bQ90kpmmEKTMYPsyentsKpHQZxTzG7wOCAIpJnbPTHDMxEJhVTaAwEjbVyMSIzTTPfnhoavWIBu0+uMgKDDlBm+RjlgkFlyhXyCN6UwFrIUUMH6Gw+eQHLiooKIl8ce7uDxIlt+9b7hFCU+sQ3kvuse239DZluu6+8buMWqJvrEHgzS9adRFKku8nSPAEPYn85vDi7OgVAcLQufknNgs47KHBAx9h04LeSrFJ/P5J1b//ItRpMOIme+O9d1BR46puzhvUaCHLdvO9czj+OmW+dIm+QIk6lZIOOMnppG72kZxtLfeKT16ur+2FbwAdL9ItBp4BI/YTlBPoa5mLMxpuWfmX1qHntvtGc9wEwS1P7YFfmF3XiK5apxalzrn0Qlr5UmDNbVIqJb1OlbC0w03Z0oktti1xT+R2DGOLWM4lBbpXDHV1BhQ7oYOvbUD8Cnof55lTP0WHHsOHlQc/BGDti1XA9aBX/OzVyzBUYEf0pkimsD0RYo6aqt7QwehJYdlz9x1NBguBffT0s4NhNb9IWr+ASnFPvNl2sw4XH/8U0J0q8ZkMpKkbLM1Zdp1Fv00GF0f5UNRokai6uM3w/ccantJ3WvZ6GtctqytWrw== \n";
|
||||
mockRuntime.storage.projects.ssh.getSSHKey.returns(Promise.resolve(fileContent));
|
||||
mockRuntime.settings.getUserKey.returns(Promise.resolve(fileContent));
|
||||
request(app)
|
||||
.get("/settings/user/keys/" + key_file_name)
|
||||
.expect(200)
|
||||
@@ -198,7 +162,8 @@ describe("api/editor/sshkeys", function() {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
mockRuntime.storage.projects.ssh.getSSHKey.called.should.be.true();
|
||||
mockRuntime.settings.getUserKey.called.should.be.true();
|
||||
mockRuntime.settings.getUserKey.firstCall.args[0].should.eql({ user: undefined, id: 'test_key' });
|
||||
res.body.should.be.deepEqual({ publickey: fileContent });
|
||||
done();
|
||||
});
|
||||
@@ -210,16 +175,16 @@ describe("api/editor/sshkeys", function() {
|
||||
errInstance.code = "test_code";
|
||||
var p = Promise.reject(errInstance);
|
||||
p.catch(()=>{});
|
||||
mockRuntime.storage.projects.ssh.getSSHKey.returns(p);
|
||||
mockRuntime.settings.getUserKey.returns(p);
|
||||
request(app)
|
||||
.get("/settings/user/keys/" + key_file_name)
|
||||
.expect(400)
|
||||
.expect(500)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property('error');
|
||||
res.body.error.should.be.equal(errInstance.code);
|
||||
res.body.should.have.property('code');
|
||||
res.body.code.should.be.equal(errInstance.code);
|
||||
res.body.should.have.property('message');
|
||||
res.body.message.should.be.equal(errInstance.message);
|
||||
done();
|
||||
@@ -231,25 +196,25 @@ describe("api/editor/sshkeys", function() {
|
||||
var errInstance = new Error("Messages.....");
|
||||
var p = Promise.reject(errInstance);
|
||||
p.catch(()=>{});
|
||||
mockRuntime.storage.projects.ssh.getSSHKey.returns(p);
|
||||
mockRuntime.settings.getUserKey.returns(p);
|
||||
request(app)
|
||||
.get("/settings/user/keys/" + key_file_name)
|
||||
.expect(400)
|
||||
.expect(500)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property('error');
|
||||
res.body.error.should.be.equal("unexpected_error");
|
||||
res.body.should.have.property('code');
|
||||
res.body.code.should.be.equal("unexpected_error");
|
||||
res.body.should.have.property('message');
|
||||
res.body.message.should.be.equal(errInstance.toString());
|
||||
res.body.message.should.be.equal("Messages.....");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('POST /settings/user/keys --- success', function(done) {
|
||||
var key_file_name = "test_key";
|
||||
mockRuntime.storage.projects.ssh.generateSSHKey.returns(Promise.resolve(key_file_name));
|
||||
mockRuntime.settings.generateUserKey.returns(Promise.resolve(key_file_name));
|
||||
request(app)
|
||||
.post("/settings/user/keys")
|
||||
.send({ name: key_file_name })
|
||||
@@ -262,41 +227,23 @@ describe("api/editor/sshkeys", function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('POST /settings/user/keys --- return parameter error', function(done) {
|
||||
var key_file_name = "test_key";
|
||||
mockRuntime.storage.projects.ssh.generateSSHKey.returns(Promise.resolve(key_file_name));
|
||||
request(app)
|
||||
.post("/settings/user/keys")
|
||||
.expect(400)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property('error');
|
||||
res.body.error.should.be.equal("unexpected_error");
|
||||
res.body.should.have.property('message');
|
||||
res.body.message.should.be.equal("You need to have body or body.name");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('POST /settings/user/keys --- return Error', function(done) {
|
||||
var key_file_name = "test_key";
|
||||
var errInstance = new Error("Messages.....");
|
||||
errInstance.code = "test_code";
|
||||
var p = Promise.reject(errInstance);
|
||||
p.catch(()=>{});
|
||||
mockRuntime.storage.projects.ssh.generateSSHKey.returns(p);
|
||||
mockRuntime.settings.generateUserKey.returns(p);
|
||||
request(app)
|
||||
.post("/settings/user/keys")
|
||||
.send({ name: key_file_name })
|
||||
.expect(400)
|
||||
.expect(500)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property('error');
|
||||
res.body.error.should.be.equal("test_code");
|
||||
res.body.should.have.property('code');
|
||||
res.body.code.should.be.equal("test_code");
|
||||
res.body.should.have.property('message');
|
||||
res.body.message.should.be.equal(errInstance.message);
|
||||
done();
|
||||
@@ -308,26 +255,26 @@ describe("api/editor/sshkeys", function() {
|
||||
var errInstance = new Error("Messages.....");
|
||||
var p = Promise.reject(errInstance);
|
||||
p.catch(()=>{});
|
||||
mockRuntime.storage.projects.ssh.generateSSHKey.returns(p);
|
||||
mockRuntime.settings.generateUserKey.returns(p);
|
||||
request(app)
|
||||
.post("/settings/user/keys")
|
||||
.send({ name: key_file_name })
|
||||
.expect(400)
|
||||
.expect(500)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property('error');
|
||||
res.body.error.should.be.equal("unexpected_error");
|
||||
res.body.should.have.property('code');
|
||||
res.body.code.should.be.equal("unexpected_error");
|
||||
res.body.should.have.property('message');
|
||||
res.body.message.should.be.equal(errInstance.toString());
|
||||
res.body.message.should.be.equal("Messages.....");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('DELETE /settings/user/keys/<key_file_name> --- success', function(done) {
|
||||
var key_file_name = "test_key";
|
||||
mockRuntime.storage.projects.ssh.deleteSSHKey.returns(Promise.resolve(true));
|
||||
mockRuntime.settings.removeUserKey.returns(Promise.resolve(true));
|
||||
request(app)
|
||||
.delete("/settings/user/keys/" + key_file_name)
|
||||
.expect(204)
|
||||
@@ -346,16 +293,16 @@ describe("api/editor/sshkeys", function() {
|
||||
errInstance.code = "test_code";
|
||||
var p = Promise.reject(errInstance);
|
||||
p.catch(()=>{});
|
||||
mockRuntime.storage.projects.ssh.deleteSSHKey.returns(p);
|
||||
mockRuntime.settings.removeUserKey.returns(p);
|
||||
request(app)
|
||||
.delete("/settings/user/keys/" + key_file_name)
|
||||
.expect(400)
|
||||
.expect(500)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property('error');
|
||||
res.body.error.should.be.equal("test_code");
|
||||
res.body.should.have.property('code');
|
||||
res.body.code.should.be.equal("test_code");
|
||||
res.body.should.have.property('message');
|
||||
res.body.message.should.be.equal(errInstance.message);
|
||||
done();
|
||||
@@ -367,18 +314,18 @@ describe("api/editor/sshkeys", function() {
|
||||
var errInstance = new Error("Messages.....");
|
||||
var p = Promise.reject(errInstance);
|
||||
p.catch(()=>{});
|
||||
mockRuntime.storage.projects.ssh.deleteSSHKey.returns(p);
|
||||
mockRuntime.settings.removeUserKey.returns(p);
|
||||
request(app)
|
||||
.delete("/settings/user/keys/" + key_file_name)
|
||||
.expect(400)
|
||||
.expect(500)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property('error');
|
||||
res.body.error.should.be.equal("unexpected_error");
|
||||
res.body.should.have.property('code');
|
||||
res.body.code.should.be.equal("unexpected_error");
|
||||
res.body.should.have.property('message');
|
||||
res.body.message.should.be.equal(errInstance.toString());
|
||||
res.body.message.should.be.equal('Messages.....');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,7 +33,7 @@ describe("api/editor/theme", function() {
|
||||
fs.statSync.restore();
|
||||
});
|
||||
it("applies the default theme", function() {
|
||||
var result = theme.init({settings:{},version:function() { return '123.456'}});
|
||||
var result = theme.init({});
|
||||
should.not.exist(result);
|
||||
|
||||
var context = theme.context();
|
||||
@@ -43,13 +43,12 @@ describe("api/editor/theme", function() {
|
||||
context.should.have.a.property("header");
|
||||
context.header.should.have.a.property("title","Node-RED");
|
||||
context.header.should.have.a.property("image","red/images/node-red.png");
|
||||
context.should.have.a.property("version","123.456");
|
||||
|
||||
should.not.exist(theme.settings());
|
||||
});
|
||||
|
||||
it("picks up custom theme", function() {
|
||||
theme.init({settings:{
|
||||
theme.init({
|
||||
editorTheme: {
|
||||
page: {
|
||||
title: "Test Page Title",
|
||||
@@ -84,7 +83,7 @@ describe("api/editor/theme", function() {
|
||||
image: "/absolute/path/to/login/page/big/image" // a 256x256 image
|
||||
}
|
||||
}
|
||||
}});
|
||||
});
|
||||
|
||||
theme.app();
|
||||
|
||||
|
||||
@@ -20,8 +20,6 @@ var express = require("express");
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var events = new EventEmitter();
|
||||
var ui = require("../../../../red/api/editor/ui");
|
||||
|
||||
|
||||
@@ -30,10 +28,13 @@ describe("api/editor/ui", function() {
|
||||
|
||||
before(function() {
|
||||
ui.init({
|
||||
events:events,
|
||||
nodes: {
|
||||
getNodeIconPath: function(module,icon) {
|
||||
return path.resolve(__dirname+'/../../../../public/icons/arrow-in.png');
|
||||
getIcon: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
fs.readFile(path.resolve(__dirname+'/../../../../public/icons/arrow-in.png'), function(err,data) {
|
||||
resolve(data);
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -23,7 +23,6 @@ var fs = require("fs");
|
||||
var path = require("path");
|
||||
var api = require("../../../red/api");
|
||||
|
||||
var apiUtil = require("../../../red/api/util");
|
||||
var apiAuth = require("../../../red/api/auth");
|
||||
var apiEditor = require("../../../red/api/editor");
|
||||
var apiAdmin = require("../../../red/api/admin");
|
||||
@@ -31,7 +30,6 @@ var apiAdmin = require("../../../red/api/admin");
|
||||
|
||||
describe("api/index", function() {
|
||||
var beforeEach = function() {
|
||||
sinon.stub(apiUtil,"init",function(){});
|
||||
sinon.stub(apiAuth,"init",function(){});
|
||||
sinon.stub(apiEditor,"init",function(){
|
||||
var app = express();
|
||||
@@ -48,7 +46,6 @@ describe("api/index", function() {
|
||||
});
|
||||
};
|
||||
var afterEach = function() {
|
||||
apiUtil.init.restore();
|
||||
apiAuth.init.restore();
|
||||
apiAuth.login.restore();
|
||||
apiEditor.init.restore();
|
||||
@@ -59,18 +56,14 @@ describe("api/index", function() {
|
||||
afterEach(afterEach);
|
||||
|
||||
it("does not setup admin api if httpAdminRoot is false", function(done) {
|
||||
api.init({},{
|
||||
settings: { httpAdminRoot: false }
|
||||
});
|
||||
api.init({},{ httpAdminRoot: false },{},{});
|
||||
should.not.exist(api.adminApp);
|
||||
done();
|
||||
});
|
||||
describe('initalises admin api without adminAuth', function(done) {
|
||||
before(function() {
|
||||
beforeEach();
|
||||
api.init({},{
|
||||
settings: { }
|
||||
});
|
||||
api.init({},{},{},{});
|
||||
});
|
||||
after(afterEach);
|
||||
it('exposes the editor',function(done) {
|
||||
@@ -87,9 +80,7 @@ describe("api/index", function() {
|
||||
describe('initalises admin api without editor', function(done) {
|
||||
before(function() {
|
||||
beforeEach();
|
||||
api.init({},{
|
||||
settings: { disableEditor: true }
|
||||
});
|
||||
api.init({},{ disableEditor: true },{},{});
|
||||
});
|
||||
after(afterEach);
|
||||
it('does not expose the editor',function(done) {
|
||||
|
||||
@@ -15,11 +15,17 @@
|
||||
**/
|
||||
|
||||
var should = require("should");
|
||||
var sinon = require("sinon");
|
||||
var request = require('supertest');
|
||||
var express = require('express');
|
||||
|
||||
var apiUtil = require("../../../red/api/util");
|
||||
|
||||
var log = require("../../../red/util").log; // TODO: separate module
|
||||
var i18n = require("../../../red/util").i18n; // TODO: separate module
|
||||
|
||||
|
||||
|
||||
describe("api/util", function() {
|
||||
describe("errorHandler", function() {
|
||||
var loggedError = null;
|
||||
@@ -27,17 +33,8 @@ describe("api/util", function() {
|
||||
var app;
|
||||
before(function() {
|
||||
app = express();
|
||||
apiUtil.init({
|
||||
log:{
|
||||
error: function(msg) {
|
||||
loggedError = msg;
|
||||
},
|
||||
audit: function(event) {
|
||||
loggedEvent = event;
|
||||
}
|
||||
},
|
||||
i18n:{}
|
||||
})
|
||||
sinon.stub(log,'error',function(msg) {loggedError = msg;});
|
||||
sinon.stub(log,'audit',function(event) {loggedEvent = event;});
|
||||
app.get("/tooLarge", function(req,res) {
|
||||
var err = new Error();
|
||||
err.message = "request entity too large";
|
||||
@@ -49,6 +46,10 @@ describe("api/util", function() {
|
||||
throw err;
|
||||
},apiUtil.errorHandler)
|
||||
});
|
||||
after(function() {
|
||||
log.error.restore();
|
||||
log.audit.restore();
|
||||
})
|
||||
beforeEach(function() {
|
||||
loggedError = null;
|
||||
loggedEvent = null;
|
||||
@@ -91,11 +92,13 @@ describe("api/util", function() {
|
||||
})
|
||||
|
||||
describe('determineLangFromHeaders', function() {
|
||||
var oldDefaultLang;
|
||||
before(function() {
|
||||
apiUtil.init({
|
||||
log:{},
|
||||
i18n:{defaultLang:"en-US"}
|
||||
});
|
||||
oldDefaultLang = i18n.defaultLang;
|
||||
i18n.defaultLang = "en-US";
|
||||
})
|
||||
after(function() {
|
||||
i18n.defaultLang = oldDefaultLang;
|
||||
})
|
||||
it('returns the default lang if non provided', function() {
|
||||
apiUtil.determineLangFromHeaders(null).should.eql("en-US");
|
||||
|
||||
242
test/red/runtime-api/comms_spec.js
Normal file
242
test/red/runtime-api/comms_spec.js
Normal file
@@ -0,0 +1,242 @@
|
||||
/**
|
||||
* 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.
|
||||
**/
|
||||
|
||||
var should = require("should");
|
||||
var sinon = require("sinon");
|
||||
|
||||
var comms = require("../../../red/runtime-api/comms");
|
||||
|
||||
describe("runtime-api/comms", function() {
|
||||
describe("listens for events", function() {
|
||||
var messages = [];
|
||||
var clientConnection = {
|
||||
send: function(topic,data) {
|
||||
messages.push({topic,data})
|
||||
}
|
||||
}
|
||||
var eventHandlers = {};
|
||||
before(function(done) {
|
||||
comms.init({
|
||||
log: {
|
||||
trace: function(){}
|
||||
},
|
||||
events: {
|
||||
removeListener: function() {},
|
||||
on: function(evt,handler) {
|
||||
eventHandlers[evt] = handler;
|
||||
}
|
||||
}
|
||||
})
|
||||
comms.addConnection({client: clientConnection}).then(done);
|
||||
})
|
||||
after(function(done) {
|
||||
comms.removeConnection({client: clientConnection}).then(done);
|
||||
})
|
||||
afterEach(function() {
|
||||
messages = [];
|
||||
})
|
||||
|
||||
it('runtime events',function(){
|
||||
eventHandlers.should.have.property('runtime-event');
|
||||
eventHandlers['runtime-event']({
|
||||
id: "my-event",
|
||||
payload: "my-payload"
|
||||
})
|
||||
messages.should.have.length(1);
|
||||
messages[0].should.have.property("topic","notification/my-event");
|
||||
messages[0].should.have.property("data","my-payload")
|
||||
})
|
||||
it('status events',function(){
|
||||
eventHandlers.should.have.property('node-status');
|
||||
eventHandlers['node-status']({
|
||||
id: "my-event",
|
||||
status: "my-status"
|
||||
})
|
||||
messages.should.have.length(1);
|
||||
messages[0].should.have.property("topic","status/my-event");
|
||||
messages[0].should.have.property("data","my-status")
|
||||
})
|
||||
it('comms events',function(){
|
||||
eventHandlers.should.have.property('runtime-event');
|
||||
eventHandlers['comms']({
|
||||
topic: "my-topic",
|
||||
data: "my-payload"
|
||||
})
|
||||
messages.should.have.length(1);
|
||||
messages[0].should.have.property("topic","my-topic");
|
||||
messages[0].should.have.property("data","my-payload")
|
||||
})
|
||||
});
|
||||
describe("manages connections", function() {
|
||||
var eventHandlers = {};
|
||||
var messages = [];
|
||||
var clientConnection1 = {
|
||||
send: function(topic,data) {
|
||||
messages.push({topic,data})
|
||||
}
|
||||
}
|
||||
var clientConnection2 = {
|
||||
send: function(topic,data) {
|
||||
messages.push({topic,data})
|
||||
}
|
||||
}
|
||||
before(function() {
|
||||
comms.init({
|
||||
log: {
|
||||
trace: function(){}
|
||||
},
|
||||
events: {
|
||||
removeListener: function() {},
|
||||
on: function(evt,handler) {
|
||||
eventHandlers[evt] = handler;
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
afterEach(function(done) {
|
||||
comms.removeConnection({client: clientConnection1}).then(function() {
|
||||
comms.removeConnection({client: clientConnection2}).then(done);
|
||||
});
|
||||
messages = [];
|
||||
})
|
||||
it('adds new connections',function(done){
|
||||
eventHandlers['comms']({
|
||||
topic: "my-topic",
|
||||
data: "my-payload"
|
||||
})
|
||||
messages.should.have.length(0);
|
||||
comms.addConnection({client: clientConnection1}).then(function() {
|
||||
eventHandlers['comms']({
|
||||
topic: "my-topic",
|
||||
data: "my-payload"
|
||||
})
|
||||
messages.should.have.length(1);
|
||||
comms.addConnection({client: clientConnection2}).then(function() {
|
||||
eventHandlers['comms']({
|
||||
topic: "my-topic",
|
||||
data: "my-payload"
|
||||
})
|
||||
messages.should.have.length(3);
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
});
|
||||
it('removes connections',function(done){
|
||||
eventHandlers['comms']({
|
||||
topic: "my-topic",
|
||||
data: "my-payload"
|
||||
})
|
||||
messages.should.have.length(0);
|
||||
comms.addConnection({client: clientConnection1}).then(function() {
|
||||
comms.addConnection({client: clientConnection2}).then(function() {
|
||||
eventHandlers['comms']({
|
||||
topic: "my-topic",
|
||||
data: "my-payload"
|
||||
})
|
||||
messages.should.have.length(2);
|
||||
comms.removeConnection({client: clientConnection1}).then(function() {
|
||||
eventHandlers['comms']({
|
||||
topic: "my-topic",
|
||||
data: "my-payload"
|
||||
})
|
||||
messages.should.have.length(3);
|
||||
done();
|
||||
});
|
||||
}).catch(done);
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
describe("subscriptions", function() {
|
||||
var messages = [];
|
||||
var clientConnection = {
|
||||
send: function(topic,data) {
|
||||
messages.push({topic,data})
|
||||
}
|
||||
}
|
||||
var clientConnection2 = {
|
||||
send: function(topic,data) {
|
||||
messages.push({topic,data})
|
||||
}
|
||||
}
|
||||
var eventHandlers = {};
|
||||
before(function() {
|
||||
comms.init({
|
||||
log: {
|
||||
trace: function(){}
|
||||
},
|
||||
events: {
|
||||
removeListener: function() {},
|
||||
on: function(evt,handler) {
|
||||
eventHandlers[evt] = handler;
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
afterEach(function(done) {
|
||||
messages = [];
|
||||
comms.removeConnection({client: clientConnection}).then(done);
|
||||
})
|
||||
|
||||
it('subscribe triggers retained messages',function(done){
|
||||
eventHandlers['comms']({
|
||||
topic: "my-event",
|
||||
data: "my-payload",
|
||||
retain: true
|
||||
})
|
||||
messages.should.have.length(0);
|
||||
comms.addConnection({client: clientConnection}).then(function() {
|
||||
return comms.subscribe({client: clientConnection, topic: "my-event"}).then(function() {
|
||||
messages.should.have.length(1);
|
||||
messages[0].should.have.property("topic","my-event");
|
||||
messages[0].should.have.property("data","my-payload");
|
||||
done();
|
||||
});
|
||||
}).catch(done);
|
||||
})
|
||||
it('retained messages get cleared',function(done) {
|
||||
eventHandlers['comms']({
|
||||
topic: "my-event",
|
||||
data: "my-payload",
|
||||
retain: true
|
||||
})
|
||||
messages.should.have.length(0);
|
||||
comms.addConnection({client: clientConnection}).then(function() {
|
||||
return comms.subscribe({client: clientConnection, topic: "my-event"}).then(function() {
|
||||
messages.should.have.length(1);
|
||||
messages[0].should.have.property("topic","my-event");
|
||||
messages[0].should.have.property("data","my-payload");
|
||||
// Now we have a retained message, clear it
|
||||
eventHandlers['comms']({
|
||||
topic: "my-event",
|
||||
data: "my-payload-cleared"
|
||||
});
|
||||
messages.should.have.length(2);
|
||||
messages[1].should.have.property("topic","my-event");
|
||||
messages[1].should.have.property("data","my-payload-cleared");
|
||||
// Now add a second client and subscribe - no message should arrive
|
||||
return comms.addConnection({client: clientConnection2}).then(function() {
|
||||
return comms.subscribe({client: clientConnection2, topic: "my-event"}).then(function() {
|
||||
messages.should.have.length(2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
}).catch(done);
|
||||
});
|
||||
})
|
||||
|
||||
});
|
||||
19
test/red/runtime-api/context_spec.js
Normal file
19
test/red/runtime-api/context_spec.js
Normal 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.
|
||||
**/
|
||||
|
||||
describe("runtime-api/context", function() {
|
||||
it.skip("NEEDS TESTS WRITING",function() {});
|
||||
});
|
||||
413
test/red/runtime-api/flows_spec.js
Normal file
413
test/red/runtime-api/flows_spec.js
Normal file
@@ -0,0 +1,413 @@
|
||||
/**
|
||||
* 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.
|
||||
**/
|
||||
|
||||
|
||||
var should = require("should");
|
||||
var sinon = require("sinon");
|
||||
|
||||
var flows = require("../../../red/runtime-api/flows")
|
||||
|
||||
var mockLog = () => ({
|
||||
log: sinon.stub(),
|
||||
debug: sinon.stub(),
|
||||
trace: sinon.stub(),
|
||||
warn: sinon.stub(),
|
||||
info: sinon.stub(),
|
||||
metric: sinon.stub(),
|
||||
audit: sinon.stub(),
|
||||
_: function() { return "abc"}
|
||||
})
|
||||
|
||||
describe("runtime-api/flows", function() {
|
||||
describe("getFlows", function() {
|
||||
it("returns the current flow configuration", function(done) {
|
||||
flows.init({
|
||||
log: mockLog(),
|
||||
nodes: {
|
||||
getFlows: function() { return [1,2,3] }
|
||||
}
|
||||
});
|
||||
flows.getFlows({}).then(function(result) {
|
||||
result.should.eql([1,2,3]);
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe("setFlows", function() {
|
||||
var setFlows;
|
||||
var loadFlows;
|
||||
var reloadError = false;
|
||||
beforeEach(function() {
|
||||
setFlows = sinon.spy(function(flows,type) {
|
||||
if (flows[0] === "error") {
|
||||
var err = new Error("error");
|
||||
err.code = "error";
|
||||
var p = Promise.reject(err);
|
||||
p.catch(()=>{});
|
||||
return p;
|
||||
}
|
||||
return Promise.resolve("newRev");
|
||||
});
|
||||
loadFlows = sinon.spy(function() {
|
||||
if (!reloadError) {
|
||||
return Promise.resolve("newLoadRev");
|
||||
} else {
|
||||
var err = new Error("error");
|
||||
err.code = "error";
|
||||
var p = Promise.reject(err);
|
||||
p.catch(()=>{});
|
||||
return p;
|
||||
}
|
||||
})
|
||||
flows.init({
|
||||
log: mockLog(),
|
||||
nodes: {
|
||||
getFlows: function() { return {rev:"currentRev",flows:[]} },
|
||||
setFlows: setFlows,
|
||||
loadFlows: loadFlows
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
it("defaults to full deploy", function(done) {
|
||||
flows.setFlows({
|
||||
flows: {flows:[4,5,6]}
|
||||
}).then(function(result) {
|
||||
result.should.eql({rev:"newRev"});
|
||||
setFlows.called.should.be.true();
|
||||
setFlows.lastCall.args[0].should.eql([4,5,6]);
|
||||
setFlows.lastCall.args[1].should.eql("full");
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
it("passes through other deploy types", function(done) {
|
||||
flows.setFlows({
|
||||
deploymentType: "nodes",
|
||||
flows: {flows:[4,5,6]}
|
||||
}).then(function(result) {
|
||||
result.should.eql({rev:"newRev"});
|
||||
setFlows.called.should.be.true();
|
||||
setFlows.lastCall.args[0].should.eql([4,5,6]);
|
||||
setFlows.lastCall.args[1].should.eql("nodes");
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
it("triggers a flow reload", function(done) {
|
||||
flows.setFlows({
|
||||
deploymentType: "reload"
|
||||
}).then(function(result) {
|
||||
result.should.eql({rev:"newLoadRev"});
|
||||
setFlows.called.should.be.false();
|
||||
loadFlows.called.should.be.true();
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
it("allows update when revision matches", function(done) {
|
||||
flows.setFlows({
|
||||
deploymentType: "nodes",
|
||||
flows: {flows:[4,5,6],rev:"currentRev"}
|
||||
}).then(function(result) {
|
||||
result.should.eql({rev:"newRev"});
|
||||
setFlows.called.should.be.true();
|
||||
setFlows.lastCall.args[0].should.eql([4,5,6]);
|
||||
setFlows.lastCall.args[1].should.eql("nodes");
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
it("rejects update when revision does not match", function(done) {
|
||||
flows.setFlows({
|
||||
deploymentType: "nodes",
|
||||
flows: {flows:[4,5,6],rev:"notTheCurrentRev"}
|
||||
}).then(function(result) {
|
||||
done(new Error("Did not reject rev mismatch"));
|
||||
}).catch(function(err) {
|
||||
err.should.have.property('code','version_mismatch');
|
||||
err.should.have.property('status',409);
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
it("rejects when reload fails",function(done) {
|
||||
reloadError = true;
|
||||
flows.setFlows({
|
||||
deploymentType: "reload"
|
||||
}).then(function(result) {
|
||||
done(new Error("Did not return internal error"));
|
||||
}).catch(function(err) {
|
||||
err.should.have.property('code','error');
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
it("rejects when update fails",function(done) {
|
||||
flows.setFlows({
|
||||
deploymentType: "full",
|
||||
flows: {flows:["error",5,6]}
|
||||
}).then(function(result) {
|
||||
done(new Error("Did not return internal error"));
|
||||
}).catch(function(err) {
|
||||
err.should.have.property('code','error');
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe("addFlow", function() {
|
||||
var addFlow;
|
||||
beforeEach(function() {
|
||||
addFlow = sinon.spy(function(flow) {
|
||||
if (flow === "error") {
|
||||
var err = new Error("error");
|
||||
err.code = "error";
|
||||
var p = Promise.reject(err);
|
||||
p.catch(()=>{});
|
||||
return p;
|
||||
}
|
||||
return Promise.resolve("newId");
|
||||
});
|
||||
flows.init({
|
||||
log: mockLog(),
|
||||
nodes: {
|
||||
addFlow: addFlow
|
||||
}
|
||||
});
|
||||
})
|
||||
it("adds a flow", function(done) {
|
||||
flows.addFlow({flow:{a:"123"}}).then(function(id) {
|
||||
addFlow.called.should.be.true();
|
||||
addFlow.lastCall.args[0].should.eql({a:"123"});
|
||||
id.should.eql("newId");
|
||||
done()
|
||||
}).catch(done);
|
||||
});
|
||||
it("rejects when add fails", function(done) {
|
||||
flows.addFlow({flow:"error"}).then(function(id) {
|
||||
done(new Error("Did not return internal error"));
|
||||
}).catch(function(err) {
|
||||
err.should.have.property('code','error');
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
});
|
||||
describe("getFlow", function() {
|
||||
var getFlow;
|
||||
beforeEach(function() {
|
||||
getFlow = sinon.spy(function(flow) {
|
||||
if (flow === "unknown") {
|
||||
return null;
|
||||
}
|
||||
return [1,2,3];
|
||||
});
|
||||
flows.init({
|
||||
log: mockLog(),
|
||||
nodes: {
|
||||
getFlow: getFlow
|
||||
}
|
||||
});
|
||||
})
|
||||
it("gets a flow", function(done) {
|
||||
flows.getFlow({id:"123"}).then(function(flow) {
|
||||
flow.should.eql([1,2,3]);
|
||||
done()
|
||||
}).catch(done);
|
||||
});
|
||||
it("rejects when flow not found", function(done) {
|
||||
flows.getFlow({id:"unknown"}).then(function(flow) {
|
||||
done(new Error("Did not return internal error"));
|
||||
}).catch(function(err) {
|
||||
err.should.have.property('code','not_found');
|
||||
err.should.have.property('status',404);
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateFlow", function() {
|
||||
var updateFlow;
|
||||
beforeEach(function() {
|
||||
updateFlow = sinon.spy(function(id,flow) {
|
||||
if (id === "unknown") {
|
||||
var err = new Error();
|
||||
// TODO: quirk of internal api - uses .code for .status
|
||||
err.code = 404;
|
||||
throw err;
|
||||
} else if (id === "error") {
|
||||
var err = new Error();
|
||||
// TODO: quirk of internal api - uses .code for .status
|
||||
err.code = "error";
|
||||
var p = Promise.reject(err);
|
||||
p.catch(()=>{});
|
||||
return p;
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
flows.init({
|
||||
log: mockLog(),
|
||||
nodes: {
|
||||
updateFlow: updateFlow
|
||||
}
|
||||
});
|
||||
})
|
||||
it("updates a flow", function(done) {
|
||||
flows.updateFlow({id:"123",flow:[1,2,3]}).then(function(id) {
|
||||
id.should.eql("123");
|
||||
updateFlow.called.should.be.true();
|
||||
updateFlow.lastCall.args[0].should.eql("123");
|
||||
updateFlow.lastCall.args[1].should.eql([1,2,3]);
|
||||
done()
|
||||
}).catch(done);
|
||||
});
|
||||
it("rejects when flow not found", function(done) {
|
||||
flows.updateFlow({id:"unknown"}).then(function(flow) {
|
||||
done(new Error("Did not return internal error"));
|
||||
}).catch(function(err) {
|
||||
err.should.have.property('code','not_found');
|
||||
err.should.have.property('status',404);
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
it("rejects when update fails", function(done) {
|
||||
flows.updateFlow({id:"error"}).then(function(flow) {
|
||||
done(new Error("Did not return internal error"));
|
||||
}).catch(function(err) {
|
||||
err.should.have.property('code','error');
|
||||
err.should.have.property('status',400);
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe("deleteFlow", function() {
|
||||
var removeFlow;
|
||||
beforeEach(function() {
|
||||
removeFlow = sinon.spy(function(flow) {
|
||||
if (flow === "unknown") {
|
||||
var err = new Error();
|
||||
// TODO: quirk of internal api - uses .code for .status
|
||||
err.code = 404;
|
||||
throw err;
|
||||
} else if (flow === "error") {
|
||||
var err = new Error();
|
||||
// TODO: quirk of internal api - uses .code for .status
|
||||
err.code = "error";
|
||||
var p = Promise.reject(err);
|
||||
p.catch(()=>{});
|
||||
return p;
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
flows.init({
|
||||
log: mockLog(),
|
||||
nodes: {
|
||||
removeFlow: removeFlow
|
||||
}
|
||||
});
|
||||
})
|
||||
it("deletes a flow", function(done) {
|
||||
flows.deleteFlow({id:"123"}).then(function() {
|
||||
removeFlow.called.should.be.true();
|
||||
removeFlow.lastCall.args[0].should.eql("123");
|
||||
done()
|
||||
}).catch(done);
|
||||
});
|
||||
it("rejects when flow not found", function(done) {
|
||||
flows.deleteFlow({id:"unknown"}).then(function(flow) {
|
||||
done(new Error("Did not return internal error"));
|
||||
}).catch(function(err) {
|
||||
err.should.have.property('code','not_found');
|
||||
err.should.have.property('status',404);
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
it("rejects when delete fails", function(done) {
|
||||
flows.deleteFlow({id:"error"}).then(function(flow) {
|
||||
done(new Error("Did not return internal error"));
|
||||
}).catch(function(err) {
|
||||
err.should.have.property('code','error');
|
||||
err.should.have.property('status',400);
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getNodeCredentials", function() {
|
||||
beforeEach(function() {
|
||||
flows.init({
|
||||
log: mockLog(),
|
||||
nodes: {
|
||||
getCredentials: function(id) {
|
||||
if (id === "unknown") {
|
||||
return undefined;
|
||||
} else if (id === "known") {
|
||||
return {
|
||||
username: "abc",
|
||||
password: "123"
|
||||
}
|
||||
} else if (id === "known2") {
|
||||
return {
|
||||
username: "abc",
|
||||
password: ""
|
||||
}
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
getCredentialDefinition: function(type) {
|
||||
if (type === "node") {
|
||||
return {
|
||||
username: {type:"text"},
|
||||
password: {type:"password"}
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
it("returns an empty object for an unknown node", function(done) {
|
||||
flows.getNodeCredentials({id:"unknown", type:"node"}).then(function(result) {
|
||||
result.should.eql({});
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
it("gets the filtered credentials for a known node with password", function(done) {
|
||||
flows.getNodeCredentials({id:"known", type:"node"}).then(function(result) {
|
||||
result.should.eql({
|
||||
username: "abc",
|
||||
has_password: true
|
||||
});
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
it("gets the filtered credentials for a known node without password", function(done) {
|
||||
flows.getNodeCredentials({id:"known2", type:"node"}).then(function(result) {
|
||||
result.should.eql({
|
||||
username: "abc",
|
||||
has_password: false
|
||||
});
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
it("gets the empty credentials for a known node without a registered definition", function(done) {
|
||||
flows.getNodeCredentials({id:"known2", type:"unknown-type"}).then(function(result) {
|
||||
result.should.eql({});
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
54
test/red/runtime-api/index_spec.js
Normal file
54
test/red/runtime-api/index_spec.js
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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.
|
||||
**/
|
||||
|
||||
var should = require("should");
|
||||
var sinon = require("sinon");
|
||||
|
||||
var index = require("../../../red/runtime-api/index");
|
||||
|
||||
|
||||
describe("runtime-api/index", function() {
|
||||
before(function() {
|
||||
["comms","flows","nodes","settings","library","projects"].forEach(n => {
|
||||
sinon.stub(require(`../../../red/runtime-api/${n}`),"init",()=>{});
|
||||
})
|
||||
});
|
||||
after(function() {
|
||||
["comms","flows","nodes","settings","library","projects"].forEach(n => {
|
||||
require(`../../../red/runtime-api/${n}`).init.restore()
|
||||
})
|
||||
})
|
||||
it('isStarted', function(done) {
|
||||
index.init({
|
||||
isStarted: ()=>true
|
||||
});
|
||||
index.isStarted({}).then(function(started) {
|
||||
started.should.be.true();
|
||||
done();
|
||||
}).catch(done);
|
||||
})
|
||||
|
||||
it('isStarted', function(done) {
|
||||
index.init({
|
||||
version: ()=>"1.2.3.4"
|
||||
});
|
||||
index.version({}).then(function(version) {
|
||||
version.should.eql("1.2.3.4");
|
||||
done();
|
||||
}).catch(done);
|
||||
})
|
||||
|
||||
});
|
||||
537
test/red/runtime-api/library_spec.js
Normal file
537
test/red/runtime-api/library_spec.js
Normal file
@@ -0,0 +1,537 @@
|
||||
/**
|
||||
* 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.
|
||||
**/
|
||||
|
||||
|
||||
var should = require("should");
|
||||
var sinon = require("sinon");
|
||||
|
||||
var library = require("../../../red/runtime-api/library")
|
||||
|
||||
var mockLog = {
|
||||
log: sinon.stub(),
|
||||
debug: sinon.stub(),
|
||||
trace: sinon.stub(),
|
||||
warn: sinon.stub(),
|
||||
info: sinon.stub(),
|
||||
metric: sinon.stub(),
|
||||
audit: sinon.stub(),
|
||||
_: function() { return "abc"}
|
||||
}
|
||||
|
||||
describe("runtime-api/library", function() {
|
||||
describe("getEntry", function() {
|
||||
before(function() {
|
||||
library.init({
|
||||
log: mockLog,
|
||||
library: {
|
||||
getEntry: function(type,path) {
|
||||
if (type === "known") {
|
||||
return Promise.resolve("known");
|
||||
} else if (type === "forbidden") {
|
||||
var err = new Error("forbidden");
|
||||
err.code = "forbidden";
|
||||
var p = Promise.reject(err);
|
||||
p.catch(()=>{});
|
||||
return p;
|
||||
} else if (type === "not_found") {
|
||||
var err = new Error("forbidden");
|
||||
err.code = "not_found";
|
||||
var p = Promise.reject(err);
|
||||
p.catch(()=>{});
|
||||
return p;
|
||||
} else if (type === "error") {
|
||||
var err = new Error("error");
|
||||
err.code = "unknown_error";
|
||||
var p = Promise.reject(err);
|
||||
p.catch(()=>{});
|
||||
return p;
|
||||
} else if (type === "blank") {
|
||||
return Promise.reject();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
it("returns a known entry", function(done) {
|
||||
library.getEntry({type: "known", path: "/abc"}).then(function(result) {
|
||||
result.should.eql("known")
|
||||
done();
|
||||
}).catch(done)
|
||||
})
|
||||
it("rejects a forbidden entry", function(done) {
|
||||
library.getEntry({type: "forbidden", path: "/abc"}).then(function(result) {
|
||||
done(new Error("did not reject"));
|
||||
}).catch(function(err) {
|
||||
err.should.have.property("code","forbidden");
|
||||
err.should.have.property("status",403);
|
||||
done();
|
||||
}).catch(done)
|
||||
})
|
||||
it("rejects an unknown entry", function(done) {
|
||||
library.getEntry({type: "not_found", path: "/abc"}).then(function(result) {
|
||||
done(new Error("did not reject"));
|
||||
}).catch(function(err) {
|
||||
err.should.have.property("code","not_found");
|
||||
err.should.have.property("status",404);
|
||||
done();
|
||||
}).catch(done)
|
||||
})
|
||||
it("rejects a blank (unknown) entry", function(done) {
|
||||
library.getEntry({type: "blank", path: "/abc"}).then(function(result) {
|
||||
done(new Error("did not reject"));
|
||||
}).catch(function(err) {
|
||||
err.should.have.property("code","not_found");
|
||||
err.should.have.property("status",404);
|
||||
done();
|
||||
}).catch(done)
|
||||
})
|
||||
it("rejects unexpected error", function(done) {
|
||||
library.getEntry({type: "error", path: "/abc"}).then(function(result) {
|
||||
done(new Error("did not reject"));
|
||||
}).catch(function(err) {
|
||||
err.should.have.property("status",400);
|
||||
done();
|
||||
}).catch(done)
|
||||
})
|
||||
})
|
||||
describe("saveEntry", function() {
|
||||
var opts;
|
||||
before(function() {
|
||||
library.init({
|
||||
log: mockLog,
|
||||
library: {
|
||||
saveEntry: function(type,path,meta,body) {
|
||||
opts = {type,path,meta,body};
|
||||
if (type === "known") {
|
||||
return Promise.resolve();
|
||||
} else if (type === "forbidden") {
|
||||
var err = new Error("forbidden");
|
||||
err.code = "forbidden";
|
||||
var p = Promise.reject(err);
|
||||
p.catch(()=>{});
|
||||
return p;
|
||||
} else if (type === "not_found") {
|
||||
var err = new Error("forbidden");
|
||||
err.code = "not_found";
|
||||
var p = Promise.reject(err);
|
||||
p.catch(()=>{});
|
||||
return p;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it("saves an entry", function(done) {
|
||||
library.saveEntry({type: "known", path: "/abc", meta: {a:1}, body:"123"}).then(function() {
|
||||
opts.should.have.property("type","known");
|
||||
opts.should.have.property("path","/abc");
|
||||
opts.should.have.property("meta",{a:1});
|
||||
opts.should.have.property("body","123");
|
||||
done();
|
||||
}).catch(done)
|
||||
})
|
||||
it("rejects a forbidden entry", function(done) {
|
||||
library.saveEntry({type: "forbidden", path: "/abc", meta: {a:1}, body:"123"}).then(function() {
|
||||
done(new Error("did not reject"));
|
||||
}).catch(function(err) {
|
||||
err.should.have.property("code","forbidden");
|
||||
err.should.have.property("status",403);
|
||||
done();
|
||||
}).catch(done)
|
||||
})
|
||||
it("rejects an unknown entry", function(done) {
|
||||
library.saveEntry({type: "not_found", path: "/abc", meta: {a:1}, body:"123"}).then(function() {
|
||||
done(new Error("did not reject"));
|
||||
}).catch(function(err) {
|
||||
err.should.have.property("status",400);
|
||||
done();
|
||||
}).catch(done)
|
||||
})
|
||||
})
|
||||
describe("getEntries", function() {
|
||||
var opts;
|
||||
before(function() {
|
||||
library.init({
|
||||
log: mockLog,
|
||||
storage: {
|
||||
getAllFlows: function() {
|
||||
return Promise.resolve({a:1});
|
||||
}
|
||||
},
|
||||
nodes: {
|
||||
getNodeExampleFlows: function() {
|
||||
return {b:2};
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
it("returns all flows", function(done) {
|
||||
library.getEntries({type:"flows"}).then(function(result) {
|
||||
result.should.eql({a:1,d:{_examples_:{b:2}}});
|
||||
done();
|
||||
}).catch(done)
|
||||
});
|
||||
it("fails for non-flows (currently)", function(done) {
|
||||
library.getEntries({type:"functions"}).then(function(result) {
|
||||
done(new Error("did not reject"));
|
||||
}).catch(function(err) {
|
||||
done();
|
||||
}).catch(done)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
|
||||
var should = require("should");
|
||||
var sinon = require("sinon");
|
||||
var fs = require("fs");
|
||||
var fspath = require('path');
|
||||
var request = require('supertest');
|
||||
var express = require('express');
|
||||
var bodyParser = require('body-parser');
|
||||
|
||||
var when = require('when');
|
||||
|
||||
var app;
|
||||
var library = require("../../../../red/api/editor/library");
|
||||
var auth = require("../../../../red/api/auth");
|
||||
|
||||
describe("api/editor/library", function() {
|
||||
|
||||
function initLibrary(_flows,_libraryEntries,_examples,_exampleFlowPathFunction) {
|
||||
var flows = _flows;
|
||||
var libraryEntries = _libraryEntries;
|
||||
library.init(app,{
|
||||
log:{audit:function(){},_:function(){},warn:function(){}},
|
||||
storage: {
|
||||
init: function() {
|
||||
return when.resolve();
|
||||
},
|
||||
getAllFlows: function() {
|
||||
return when.resolve(flows);
|
||||
},
|
||||
getFlow: function(fn) {
|
||||
if (flows[fn]) {
|
||||
return when.resolve(flows[fn]);
|
||||
} else if (fn.indexOf("..")!==-1) {
|
||||
var err = new Error();
|
||||
err.code = 'forbidden';
|
||||
return when.reject(err);
|
||||
} else {
|
||||
return when.reject();
|
||||
}
|
||||
},
|
||||
saveFlow: function(fn,data) {
|
||||
if (fn.indexOf("..")!==-1) {
|
||||
var err = new Error();
|
||||
err.code = 'forbidden';
|
||||
return when.reject(err);
|
||||
}
|
||||
flows[fn] = data;
|
||||
return when.resolve();
|
||||
},
|
||||
getLibraryEntry: function(type,path) {
|
||||
if (path.indexOf("..")!==-1) {
|
||||
var err = new Error();
|
||||
err.code = 'forbidden';
|
||||
return when.reject(err);
|
||||
}
|
||||
if (libraryEntries[type] && libraryEntries[type][path]) {
|
||||
return when.resolve(libraryEntries[type][path]);
|
||||
} else {
|
||||
return when.reject();
|
||||
}
|
||||
},
|
||||
saveLibraryEntry: function(type,path,meta,body) {
|
||||
if (path.indexOf("..")!==-1) {
|
||||
var err = new Error();
|
||||
err.code = 'forbidden';
|
||||
return when.reject(err);
|
||||
}
|
||||
libraryEntries[type][path] = body;
|
||||
return when.resolve();
|
||||
}
|
||||
},
|
||||
events: {
|
||||
on: function(){},
|
||||
removeListener: function(){}
|
||||
},
|
||||
nodes: {
|
||||
getNodeExampleFlows: function() {
|
||||
return _examples;
|
||||
},
|
||||
getNodeExampleFlowPath: _exampleFlowPathFunction
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
describe("flows", function() {
|
||||
before(function() {
|
||||
app = express();
|
||||
app.use(bodyParser.json());
|
||||
app.get("/library/flows",library.getAll);
|
||||
app.post(new RegExp("/library/flows\/(.*)"),library.post);
|
||||
app.get(new RegExp("/library/flows\/(.*)"),library.get);
|
||||
app.response.sendFile = function (path) {
|
||||
app.response.json.call(this, {sendFile: path});
|
||||
};
|
||||
sinon.stub(fs,"statSync",function() { return true; });
|
||||
});
|
||||
after(function() {
|
||||
fs.statSync.restore();
|
||||
});
|
||||
it('returns empty result', function(done) {
|
||||
initLibrary({},{flows:{}});
|
||||
request(app)
|
||||
.get('/library/flows')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.not.have.property('f');
|
||||
res.body.should.not.have.property('d');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns 404 for non-existent entry', function(done) {
|
||||
initLibrary({},{flows:{}});
|
||||
request(app)
|
||||
.get('/library/flows/foo')
|
||||
.expect(404)
|
||||
.end(done);
|
||||
});
|
||||
|
||||
|
||||
it('can store and retrieve item', function(done) {
|
||||
initLibrary({},{flows:{}});
|
||||
var flow = '[]';
|
||||
request(app)
|
||||
.post('/library/flows/foo')
|
||||
.set('Content-Type', 'application/json')
|
||||
.send(flow)
|
||||
.expect(204).end(function (err, res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
request(app)
|
||||
.get('/library/flows/foo')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.text.should.equal(flow);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('lists a stored item', function(done) {
|
||||
initLibrary({f:["bar"]});
|
||||
request(app)
|
||||
.get('/library/flows')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.have.property('f');
|
||||
should.deepEqual(res.body.f,['bar']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns 403 for malicious get attempt', function(done) {
|
||||
initLibrary({});
|
||||
// without the userDir override the malicious url would be
|
||||
// http://127.0.0.1:1880/library/flows/../../package to
|
||||
// obtain package.json from the node-red root.
|
||||
request(app)
|
||||
.get('/library/flows/../../../../../package')
|
||||
.expect(403)
|
||||
.end(done);
|
||||
});
|
||||
it('returns 403 for malicious post attempt', function(done) {
|
||||
initLibrary({});
|
||||
// without the userDir override the malicious url would be
|
||||
// http://127.0.0.1:1880/library/flows/../../package to
|
||||
// obtain package.json from the node-red root.
|
||||
request(app)
|
||||
.post('/library/flows/../../../../../package')
|
||||
.expect(403)
|
||||
.end(done);
|
||||
});
|
||||
it('includes examples flows if set', function(done) {
|
||||
var examples = {"d":{"node-module":{"f":["example-one"]}}};
|
||||
initLibrary({},{},examples);
|
||||
request(app)
|
||||
.get('/library/flows')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.have.property('d');
|
||||
res.body.d.should.have.property('_examples_');
|
||||
should.deepEqual(res.body.d._examples_,examples);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can retrieve an example flow', function(done) {
|
||||
var examples = {"d":{"node-module":{"f":["example-one"]}}};
|
||||
initLibrary({},{},examples,function(module,path) {
|
||||
return module + ':' + path
|
||||
});
|
||||
request(app)
|
||||
.get('/library/flows/_examples_/node-module/example-one')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.have.property('sendFile',
|
||||
'node-module:example-one');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can retrieve an example flow in an org scoped package', function(done) {
|
||||
var examples = {"d":{"@org_scope/node_package":{"f":["example-one"]}}};
|
||||
initLibrary({},{},examples,function(module,path) {
|
||||
return module + ':' + path
|
||||
});
|
||||
request(app)
|
||||
.get('/library/flows/_examples_/@org_scope/node_package/example-one')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.have.property('sendFile',
|
||||
'@org_scope/node_package:example-one');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("type", function() {
|
||||
before(function() {
|
||||
|
||||
app = express();
|
||||
app.use(bodyParser.json());
|
||||
initLibrary({},{});
|
||||
auth.init({settings:{}});
|
||||
library.register("test");
|
||||
});
|
||||
|
||||
it('returns empty result', function(done) {
|
||||
initLibrary({},{'test':{"":[]}});
|
||||
request(app)
|
||||
.get('/library/test')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.body.should.not.have.property('f');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns 404 for non-existent entry', function(done) {
|
||||
initLibrary({},{});
|
||||
request(app)
|
||||
.get('/library/test/foo')
|
||||
.expect(404)
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('can store and retrieve item', function(done) {
|
||||
initLibrary({},{'test':{}});
|
||||
var flow = {text:"test content"};
|
||||
request(app)
|
||||
.post('/library/test/foo')
|
||||
.set('Content-Type', 'application/json')
|
||||
.send(flow)
|
||||
.expect(204).end(function (err, res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
request(app)
|
||||
.get('/library/test/foo')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
res.text.should.equal(flow.text);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('lists a stored item', function(done) {
|
||||
initLibrary({},{'test':{'a':['abc','def']}});
|
||||
request(app)
|
||||
.get('/library/test/a')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
// This response isn't strictly accurate - but it
|
||||
// verifies the api returns what storage gave it
|
||||
should.deepEqual(res.body,['abc','def']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('returns 403 for malicious access attempt', function(done) {
|
||||
request(app)
|
||||
.get('/library/test/../../../../../../../../../../etc/passwd')
|
||||
.expect(403)
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('returns 403 for malicious access attempt', function(done) {
|
||||
request(app)
|
||||
.get('/library/test/..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\etc\\passwd')
|
||||
.expect(403)
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('returns 403 for malicious access attempt', function(done) {
|
||||
request(app)
|
||||
.post('/library/test/../../../../../../../../../../etc/passwd')
|
||||
.set('Content-Type', 'text/plain')
|
||||
.send('root:x:0:0:root:/root:/usr/bin/tclsh')
|
||||
.expect(403)
|
||||
.end(done);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
*/
|
||||
1005
test/red/runtime-api/nodes_spec.js
Normal file
1005
test/red/runtime-api/nodes_spec.js
Normal file
File diff suppressed because it is too large
Load Diff
1109
test/red/runtime-api/projects_spec.js
Normal file
1109
test/red/runtime-api/projects_spec.js
Normal file
File diff suppressed because it is too large
Load Diff
670
test/red/runtime-api/settings_spec.js
Normal file
670
test/red/runtime-api/settings_spec.js
Normal file
@@ -0,0 +1,670 @@
|
||||
/**
|
||||
* 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.
|
||||
**/
|
||||
|
||||
|
||||
var should = require("should");
|
||||
var sinon = require("sinon");
|
||||
|
||||
var settings = require("../../../red/runtime-api/settings")
|
||||
|
||||
var mockLog = () => ({
|
||||
log: sinon.stub(),
|
||||
debug: sinon.stub(),
|
||||
trace: sinon.stub(),
|
||||
warn: sinon.stub(),
|
||||
info: sinon.stub(),
|
||||
metric: sinon.stub(),
|
||||
audit: sinon.stub(),
|
||||
_: function() { return "abc"}
|
||||
})
|
||||
|
||||
describe("runtime-api/settings", function() {
|
||||
describe.skip("getRuntimeSettings", function() {});
|
||||
describe.skip("getUserSettings", function() {});
|
||||
describe.skip("updateUserSettings", function() {});
|
||||
describe.skip("getUserKeys", function() {});
|
||||
describe.skip("getUserKey", function() {});
|
||||
describe.skip("generateUserKey", function() {});
|
||||
describe.skip("removeUserKey", function() {});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
/*
|
||||
|
||||
before(function() {
|
||||
sinon.stub(theme,"settings",function() { return { test: 456 };});
|
||||
app = express();
|
||||
app.get("/settings",info.runtimeSettings);
|
||||
app.get("/settingsWithUser",function(req,res,next) {
|
||||
req.user = {
|
||||
username: "nick",
|
||||
permissions: "*",
|
||||
image: "http://example.com",
|
||||
anonymous: false,
|
||||
private: "secret"
|
||||
}
|
||||
next();
|
||||
},info.runtimeSettings);
|
||||
});
|
||||
after(function() {
|
||||
theme.settings.restore();
|
||||
});
|
||||
|
||||
|
||||
|
||||
it('returns the filtered settings', function(done) {
|
||||
info.init({
|
||||
settings: {
|
||||
foo: 123,
|
||||
httpNodeRoot: "testHttpNodeRoot",
|
||||
version: "testVersion",
|
||||
paletteCategories :["red","blue","green"],
|
||||
exportNodeSettings: function(obj) {
|
||||
obj.testNodeSetting = "helloWorld";
|
||||
}
|
||||
},
|
||||
nodes: {
|
||||
paletteEditorEnabled: function() { return true; },
|
||||
getCredentialKeyType: function() { return "test-key-type"}
|
||||
},
|
||||
log: { error: console.error },
|
||||
storage: {}
|
||||
});
|
||||
request(app)
|
||||
.get("/settings")
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property("httpNodeRoot","testHttpNodeRoot");
|
||||
res.body.should.have.property("version","testVersion");
|
||||
res.body.should.have.property("paletteCategories",["red","blue","green"]);
|
||||
res.body.should.have.property("editorTheme",{test:456});
|
||||
res.body.should.have.property("testNodeSetting","helloWorld");
|
||||
res.body.should.not.have.property("foo",123);
|
||||
res.body.should.have.property("flowEncryptionType","test-key-type");
|
||||
res.body.should.not.have.property("user");
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('returns the filtered user in settings', function(done) {
|
||||
info.init({
|
||||
settings: {
|
||||
foo: 123,
|
||||
httpNodeRoot: "testHttpNodeRoot",
|
||||
version: "testVersion",
|
||||
paletteCategories :["red","blue","green"],
|
||||
exportNodeSettings: function(obj) {
|
||||
obj.testNodeSetting = "helloWorld";
|
||||
}
|
||||
},
|
||||
nodes: {
|
||||
paletteEditorEnabled: function() { return true; },
|
||||
getCredentialKeyType: function() { return "test-key-type"}
|
||||
},
|
||||
log: { error: console.error },
|
||||
storage: {}
|
||||
});
|
||||
request(app)
|
||||
.get("/settingsWithUser")
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property("user");
|
||||
res.body.user.should.have.property("username","nick");
|
||||
res.body.user.should.have.property("permissions","*");
|
||||
res.body.user.should.have.property("image","http://example.com");
|
||||
res.body.user.should.have.property("anonymous",false);
|
||||
res.body.user.should.not.have.property("private");
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('includes project settings if projects available', function(done) {
|
||||
info.init({
|
||||
settings: {
|
||||
foo: 123,
|
||||
httpNodeRoot: "testHttpNodeRoot",
|
||||
version: "testVersion",
|
||||
paletteCategories :["red","blue","green"],
|
||||
exportNodeSettings: function(obj) {
|
||||
obj.testNodeSetting = "helloWorld";
|
||||
}
|
||||
},
|
||||
nodes: {
|
||||
paletteEditorEnabled: function() { return true; },
|
||||
getCredentialKeyType: function() { return "test-key-type"}
|
||||
},
|
||||
log: { error: console.error },
|
||||
storage: {
|
||||
projects: {
|
||||
getActiveProject: () => 'test-active-project',
|
||||
getFlowFilename: () => 'test-flow-file',
|
||||
getCredentialsFilename: () => 'test-creds-file',
|
||||
getGlobalGitUser: () => {return {name:'foo',email:'foo@example.com'}}
|
||||
}
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.get("/settings")
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property("project","test-active-project");
|
||||
res.body.should.not.have.property("files");
|
||||
res.body.should.have.property("git");
|
||||
res.body.git.should.have.property("globalUser",{name:'foo',email:'foo@example.com'});
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('includes existing files details if projects enabled but no active project and files exist', function(done) {
|
||||
info.init({
|
||||
settings: {
|
||||
foo: 123,
|
||||
httpNodeRoot: "testHttpNodeRoot",
|
||||
version: "testVersion",
|
||||
paletteCategories :["red","blue","green"],
|
||||
exportNodeSettings: function(obj) {
|
||||
obj.testNodeSetting = "helloWorld";
|
||||
}
|
||||
},
|
||||
nodes: {
|
||||
paletteEditorEnabled: function() { return true; },
|
||||
getCredentialKeyType: function() { return "test-key-type"}
|
||||
},
|
||||
log: { error: console.error },
|
||||
storage: {
|
||||
projects: {
|
||||
flowFileExists: () => true,
|
||||
getActiveProject: () => false,
|
||||
getFlowFilename: () => 'test-flow-file',
|
||||
getCredentialsFilename: () => 'test-creds-file',
|
||||
getGlobalGitUser: () => {return {name:'foo',email:'foo@example.com'}}
|
||||
}
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.get("/settings")
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.not.have.property("project");
|
||||
res.body.should.have.property("files");
|
||||
res.body.files.should.have.property("flow",'test-flow-file');
|
||||
res.body.files.should.have.property("credentials",'test-creds-file');
|
||||
res.body.should.have.property("git");
|
||||
res.body.git.should.have.property("globalUser",{name:'foo',email:'foo@example.com'});
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('does not include file details if projects enabled but no active project and files do not exist', function(done) {
|
||||
info.init({
|
||||
settings: {
|
||||
foo: 123,
|
||||
httpNodeRoot: "testHttpNodeRoot",
|
||||
version: "testVersion",
|
||||
paletteCategories :["red","blue","green"],
|
||||
exportNodeSettings: function(obj) {
|
||||
obj.testNodeSetting = "helloWorld";
|
||||
}
|
||||
},
|
||||
nodes: {
|
||||
paletteEditorEnabled: function() { return true; },
|
||||
getCredentialKeyType: function() { return "test-key-type"}
|
||||
},
|
||||
log: { error: console.error },
|
||||
storage: {
|
||||
projects: {
|
||||
flowFileExists: () => false,
|
||||
getActiveProject: () => false,
|
||||
getFlowFilename: () => 'test-flow-file',
|
||||
getCredentialsFilename: () => 'test-creds-file',
|
||||
getGlobalGitUser: () => {return {name:'foo',email:'foo@example.com'}}
|
||||
}
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.get("/settings")
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.not.have.property("project");
|
||||
res.body.should.not.have.property("files");
|
||||
res.body.should.have.property("git");
|
||||
res.body.git.should.have.property("globalUser",{name:'foo',email:'foo@example.com'});
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('overrides palette editable if runtime says it is disabled', function(done) {
|
||||
info.init({
|
||||
settings: {
|
||||
httpNodeRoot: "testHttpNodeRoot",
|
||||
version: "testVersion",
|
||||
paletteCategories :["red","blue","green"],
|
||||
exportNodeSettings: function() {}
|
||||
},
|
||||
nodes: {
|
||||
paletteEditorEnabled: function() { return false; },
|
||||
getCredentialKeyType: function() { return "test-key-type"}
|
||||
|
||||
},
|
||||
log: { error: console.error },
|
||||
storage: {}
|
||||
|
||||
});
|
||||
request(app)
|
||||
.get("/settings")
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property("httpNodeRoot","testHttpNodeRoot");
|
||||
res.body.should.have.property("version","testVersion");
|
||||
res.body.should.have.property("paletteCategories",["red","blue","green"]);
|
||||
res.body.should.have.property("editorTheme");
|
||||
res.body.editorTheme.should.have.property("test",456);
|
||||
|
||||
res.body.editorTheme.should.have.property("palette",{editable:false});
|
||||
done();
|
||||
});
|
||||
})
|
||||
|
||||
*/
|
||||
/*
|
||||
|
||||
|
||||
var should = require("should");
|
||||
var sinon = require("sinon");
|
||||
var request = require("supertest");
|
||||
var express = require("express");
|
||||
var editorApi = require("../../../../red/api/editor");
|
||||
var comms = require("../../../../red/api/editor/comms");
|
||||
var info = require("../../../../red/api/editor/settings");
|
||||
var auth = require("../../../../red/api/auth");
|
||||
var sshkeys = require("../../../../red/api/editor/sshkeys");
|
||||
var when = require("when");
|
||||
var bodyParser = require("body-parser");
|
||||
var fs = require("fs-extra");
|
||||
var fspath = require("path");
|
||||
|
||||
|
||||
describe("api/editor/sshkeys", function() {
|
||||
var app;
|
||||
var mockList = [
|
||||
'library','theme','locales','credentials','comms'
|
||||
]
|
||||
var isStarted = true;
|
||||
var errors = [];
|
||||
var session_data = {};
|
||||
|
||||
var mockRuntime = {
|
||||
settings:{
|
||||
httpNodeRoot: true,
|
||||
httpAdminRoot: true,
|
||||
disableEditor: false,
|
||||
exportNodeSettings:function(){},
|
||||
storage: {
|
||||
getSessions: function(){
|
||||
return when.resolve(session_data);
|
||||
},
|
||||
setSessions: function(_session) {
|
||||
session_data = _session;
|
||||
return when.resolve();
|
||||
}
|
||||
}
|
||||
},
|
||||
log:{audit:function(){},error:function(msg){errors.push(msg)},trace:function(){}},
|
||||
storage: {
|
||||
projects: {
|
||||
ssh: {
|
||||
init: function(){},
|
||||
listSSHKeys: function(){},
|
||||
getSSHKey: function(){},
|
||||
generateSSHKey: function(){},
|
||||
deleteSSHKey: function(){}
|
||||
}
|
||||
}
|
||||
},
|
||||
events:{on:function(){},removeListener:function(){}},
|
||||
isStarted: function() { return isStarted; },
|
||||
nodes: {paletteEditorEnabled: function() { return false }}
|
||||
};
|
||||
|
||||
before(function() {
|
||||
auth.init(mockRuntime);
|
||||
app = express();
|
||||
app.use(bodyParser.json());
|
||||
app.use(editorApi.init({},mockRuntime));
|
||||
});
|
||||
after(function() {
|
||||
})
|
||||
|
||||
beforeEach(function() {
|
||||
sinon.stub(mockRuntime.storage.projects.ssh, "listSSHKeys");
|
||||
sinon.stub(mockRuntime.storage.projects.ssh, "getSSHKey");
|
||||
sinon.stub(mockRuntime.storage.projects.ssh, "generateSSHKey");
|
||||
sinon.stub(mockRuntime.storage.projects.ssh, "deleteSSHKey");
|
||||
})
|
||||
afterEach(function() {
|
||||
mockRuntime.storage.projects.ssh.listSSHKeys.restore();
|
||||
mockRuntime.storage.projects.ssh.getSSHKey.restore();
|
||||
mockRuntime.storage.projects.ssh.generateSSHKey.restore();
|
||||
mockRuntime.storage.projects.ssh.deleteSSHKey.restore();
|
||||
})
|
||||
|
||||
it('GET /settings/user/keys --- return empty list', function(done) {
|
||||
mockRuntime.storage.projects.ssh.listSSHKeys.returns(Promise.resolve([]));
|
||||
request(app)
|
||||
.get("/settings/user/keys")
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property('keys');
|
||||
res.body.keys.should.be.empty();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('GET /settings/user/keys --- return normal list', function(done) {
|
||||
var fileList = [
|
||||
'test_key01',
|
||||
'test_key02'
|
||||
];
|
||||
var retList = fileList.map(function(elem) {
|
||||
return {
|
||||
name: elem
|
||||
};
|
||||
});
|
||||
mockRuntime.storage.projects.ssh.listSSHKeys.returns(Promise.resolve(retList));
|
||||
request(app)
|
||||
.get("/settings/user/keys")
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property('keys');
|
||||
for (var item of retList) {
|
||||
res.body.keys.should.containEql(item);
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('GET /settings/user/keys --- return Error', function(done) {
|
||||
var errInstance = new Error("Messages here.....");
|
||||
errInstance.code = "test_code";
|
||||
var p = Promise.reject(errInstance);
|
||||
p.catch(()=>{});
|
||||
mockRuntime.storage.projects.ssh.listSSHKeys.returns(p);
|
||||
request(app)
|
||||
.get("/settings/user/keys")
|
||||
.expect(400)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property('error');
|
||||
res.body.error.should.be.equal(errInstance.code);
|
||||
res.body.should.have.property('message');
|
||||
res.body.message.should.be.equal(errInstance.message);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('GET /settings/user/keys/<key_file_name> --- return 404', function(done) {
|
||||
mockRuntime.storage.projects.ssh.getSSHKey.returns(Promise.resolve(null));
|
||||
request(app)
|
||||
.get("/settings/user/keys/NOT_REAL")
|
||||
.expect(404)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('GET /settings/user/keys --- return Unexpected Error', function(done) {
|
||||
var errInstance = new Error("Messages.....");
|
||||
var p = Promise.reject(errInstance);
|
||||
p.catch(()=>{});
|
||||
mockRuntime.storage.projects.ssh.listSSHKeys.returns(p);
|
||||
request(app)
|
||||
.get("/settings/user/keys")
|
||||
.expect(400)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property('error');
|
||||
res.body.error.should.be.equal("unexpected_error");
|
||||
res.body.should.have.property('message');
|
||||
res.body.message.should.be.equal(errInstance.toString());
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('GET /settings/user/keys/<key_file_name> --- return content', function(done) {
|
||||
var key_file_name = "test_key";
|
||||
var fileContent = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQD3a+sgtgzSbbliWxmOq5p6+H/mE+0gjWfLWrkIVmHENd1mifV4uCmIHAR2NfuadUYMQ3+bQ90kpmmEKTMYPsyentsKpHQZxTzG7wOCAIpJnbPTHDMxEJhVTaAwEjbVyMSIzTTPfnhoavWIBu0+uMgKDDlBm+RjlgkFlyhXyCN6UwFrIUUMH6Gw+eQHLiooKIl8ce7uDxIlt+9b7hFCU+sQ3kvuse239DZluu6+8buMWqJvrEHgzS9adRFKku8nSPAEPYn85vDi7OgVAcLQufknNgs47KHBAx9h04LeSrFJ/P5J1b//ItRpMOIme+O9d1BR46puzhvUaCHLdvO9czj+OmW+dIm+QIk6lZIOOMnppG72kZxtLfeKT16ur+2FbwAdL9ItBp4BI/YTlBPoa5mLMxpuWfmX1qHntvtGc9wEwS1P7YFfmF3XiK5apxalzrn0Qlr5UmDNbVIqJb1OlbC0w03Z0oktti1xT+R2DGOLWM4lBbpXDHV1BhQ7oYOvbUD8Cnof55lTP0WHHsOHlQc/BGDti1XA9aBX/OzVyzBUYEf0pkimsD0RYo6aqt7QwehJYdlz9x1NBguBffT0s4NhNb9IWr+ASnFPvNl2sw4XH/8U0J0q8ZkMpKkbLM1Zdp1Fv00GF0f5UNRokai6uM3w/ccantJ3WvZ6GtctqytWrw== \n";
|
||||
mockRuntime.storage.projects.ssh.getSSHKey.returns(Promise.resolve(fileContent));
|
||||
request(app)
|
||||
.get("/settings/user/keys/" + key_file_name)
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
mockRuntime.storage.projects.ssh.getSSHKey.called.should.be.true();
|
||||
res.body.should.be.deepEqual({ publickey: fileContent });
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('GET /settings/user/keys/<key_file_name> --- return Error', function(done) {
|
||||
var key_file_name = "test_key";
|
||||
var errInstance = new Error("Messages.....");
|
||||
errInstance.code = "test_code";
|
||||
var p = Promise.reject(errInstance);
|
||||
p.catch(()=>{});
|
||||
mockRuntime.storage.projects.ssh.getSSHKey.returns(p);
|
||||
request(app)
|
||||
.get("/settings/user/keys/" + key_file_name)
|
||||
.expect(400)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property('error');
|
||||
res.body.error.should.be.equal(errInstance.code);
|
||||
res.body.should.have.property('message');
|
||||
res.body.message.should.be.equal(errInstance.message);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('GET /settings/user/keys/<key_file_name> --- return Unexpected Error', function(done) {
|
||||
var key_file_name = "test_key";
|
||||
var errInstance = new Error("Messages.....");
|
||||
var p = Promise.reject(errInstance);
|
||||
p.catch(()=>{});
|
||||
mockRuntime.storage.projects.ssh.getSSHKey.returns(p);
|
||||
request(app)
|
||||
.get("/settings/user/keys/" + key_file_name)
|
||||
.expect(400)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property('error');
|
||||
res.body.error.should.be.equal("unexpected_error");
|
||||
res.body.should.have.property('message');
|
||||
res.body.message.should.be.equal(errInstance.toString());
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('POST /settings/user/keys --- success', function(done) {
|
||||
var key_file_name = "test_key";
|
||||
mockRuntime.storage.projects.ssh.generateSSHKey.returns(Promise.resolve(key_file_name));
|
||||
request(app)
|
||||
.post("/settings/user/keys")
|
||||
.send({ name: key_file_name })
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('POST /settings/user/keys --- return parameter error', function(done) {
|
||||
var key_file_name = "test_key";
|
||||
mockRuntime.storage.projects.ssh.generateSSHKey.returns(Promise.resolve(key_file_name));
|
||||
request(app)
|
||||
.post("/settings/user/keys")
|
||||
.expect(400)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property('error');
|
||||
res.body.error.should.be.equal("unexpected_error");
|
||||
res.body.should.have.property('message');
|
||||
res.body.message.should.be.equal("You need to have body or body.name");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('POST /settings/user/keys --- return Error', function(done) {
|
||||
var key_file_name = "test_key";
|
||||
var errInstance = new Error("Messages.....");
|
||||
errInstance.code = "test_code";
|
||||
var p = Promise.reject(errInstance);
|
||||
p.catch(()=>{});
|
||||
mockRuntime.storage.projects.ssh.generateSSHKey.returns(p);
|
||||
request(app)
|
||||
.post("/settings/user/keys")
|
||||
.send({ name: key_file_name })
|
||||
.expect(400)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property('error');
|
||||
res.body.error.should.be.equal("test_code");
|
||||
res.body.should.have.property('message');
|
||||
res.body.message.should.be.equal(errInstance.message);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('POST /settings/user/keys --- return Unexpected error', function(done) {
|
||||
var key_file_name = "test_key";
|
||||
var errInstance = new Error("Messages.....");
|
||||
var p = Promise.reject(errInstance);
|
||||
p.catch(()=>{});
|
||||
mockRuntime.storage.projects.ssh.generateSSHKey.returns(p);
|
||||
request(app)
|
||||
.post("/settings/user/keys")
|
||||
.send({ name: key_file_name })
|
||||
.expect(400)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property('error');
|
||||
res.body.error.should.be.equal("unexpected_error");
|
||||
res.body.should.have.property('message');
|
||||
res.body.message.should.be.equal(errInstance.toString());
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('DELETE /settings/user/keys/<key_file_name> --- success', function(done) {
|
||||
var key_file_name = "test_key";
|
||||
mockRuntime.storage.projects.ssh.deleteSSHKey.returns(Promise.resolve(true));
|
||||
request(app)
|
||||
.delete("/settings/user/keys/" + key_file_name)
|
||||
.expect(204)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.be.deepEqual({});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('DELETE /settings/user/keys/<key_file_name> --- return Error', function(done) {
|
||||
var key_file_name = "test_key";
|
||||
var errInstance = new Error("Messages.....");
|
||||
errInstance.code = "test_code";
|
||||
var p = Promise.reject(errInstance);
|
||||
p.catch(()=>{});
|
||||
mockRuntime.storage.projects.ssh.deleteSSHKey.returns(p);
|
||||
request(app)
|
||||
.delete("/settings/user/keys/" + key_file_name)
|
||||
.expect(400)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property('error');
|
||||
res.body.error.should.be.equal("test_code");
|
||||
res.body.should.have.property('message');
|
||||
res.body.message.should.be.equal(errInstance.message);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('DELETE /settings/user/keys/<key_file_name> --- return Unexpected Error', function(done) {
|
||||
var key_file_name = "test_key";
|
||||
var errInstance = new Error("Messages.....");
|
||||
var p = Promise.reject(errInstance);
|
||||
p.catch(()=>{});
|
||||
mockRuntime.storage.projects.ssh.deleteSSHKey.returns(p);
|
||||
request(app)
|
||||
.delete("/settings/user/keys/" + key_file_name)
|
||||
.expect(400)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property('error');
|
||||
res.body.error.should.be.equal("unexpected_error");
|
||||
res.body.should.have.property('message');
|
||||
res.body.message.should.be.equal(errInstance.toString());
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
*/
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
var should = require("should");
|
||||
|
||||
var deprecated = require("../../../../../red/runtime/nodes/registry/deprecated.js");
|
||||
var deprecated = require("../../../red/runtime-registry/deprecated.js");
|
||||
|
||||
describe('deprecated', function() {
|
||||
it('should return info on a node',function() {
|
||||
@@ -20,16 +20,13 @@ var path = require("path");
|
||||
var when = require("when");
|
||||
var fs = require("fs");
|
||||
|
||||
var registry = require("../../../../../red/runtime/nodes/registry");
|
||||
var registry = require("../../../red/runtime-registry");
|
||||
|
||||
var installer = require("../../../../../red/runtime/nodes/registry/installer");
|
||||
var loader = require("../../../../../red/runtime/nodes/registry/loader");
|
||||
var typeRegistry = require("../../../../../red/runtime/nodes/registry/registry");
|
||||
var installer = require("../../../red/runtime-registry/installer");
|
||||
var loader = require("../../../red/runtime-registry/loader");
|
||||
var typeRegistry = require("../../../red/runtime-registry/registry");
|
||||
|
||||
|
||||
|
||||
|
||||
describe('red/nodes/registry/index', function() {
|
||||
describe('red/registry/index', function() {
|
||||
var stubs = [];
|
||||
afterEach(function() {
|
||||
while(stubs.length) {
|
||||
@@ -60,7 +57,7 @@ describe('red/nodes/registry/index', function() {
|
||||
registry.addModule("foo").then(function(info) {
|
||||
info.should.eql("info");
|
||||
done();
|
||||
}).otherwise(function(err) { done(err); });
|
||||
}).catch(function(err) { done(err); });
|
||||
});
|
||||
it('rejects if loader rejects', function(done) {
|
||||
stubs.push(sinon.stub(loader,"addModule",function(module) {
|
||||
@@ -71,7 +68,7 @@ describe('red/nodes/registry/index', function() {
|
||||
}));
|
||||
registry.addModule("foo").then(function(info) {
|
||||
done(new Error("unexpected resolve"));
|
||||
}).otherwise(function(err) {
|
||||
}).catch(function(err) {
|
||||
err.should.eql("error");
|
||||
done();
|
||||
})
|
||||
@@ -90,7 +87,7 @@ describe('red/nodes/registry/index', function() {
|
||||
typeRegistry.enableNodeSet.called.should.be.true();
|
||||
ns.should.have.a.property('id','node-set');
|
||||
done();
|
||||
}).otherwise(function(err) { done(err); });
|
||||
}).catch(function(err) { done(err); });
|
||||
});
|
||||
|
||||
it('rejects if node unknown',function() {
|
||||
@@ -121,7 +118,7 @@ describe('red/nodes/registry/index', function() {
|
||||
ns.should.have.a.property('id','node-set');
|
||||
ns.should.have.a.property('loaded',true);
|
||||
done();
|
||||
}).otherwise(function(err) { done(err); });
|
||||
}).catch(function(err) { done(err); });
|
||||
});
|
||||
|
||||
});
|
||||
@@ -22,14 +22,24 @@ var fs = require('fs');
|
||||
var EventEmitter = require('events');
|
||||
|
||||
var child_process = require('child_process');
|
||||
var installer = require("../../../../../red/runtime/nodes/registry/installer");
|
||||
var registry = require("../../../../../red/runtime/nodes/registry/index");
|
||||
var typeRegistry = require("../../../../../red/runtime/nodes/registry/registry");
|
||||
var installer = require("../../../red/runtime-registry/installer");
|
||||
var registry = require("../../../red/runtime-registry/index");
|
||||
var typeRegistry = require("../../../red/runtime-registry/registry");
|
||||
|
||||
describe('nodes/registry/installer', function() {
|
||||
|
||||
var mockLog = {
|
||||
log: sinon.stub(),
|
||||
debug: sinon.stub(),
|
||||
trace: sinon.stub(),
|
||||
warn: sinon.stub(),
|
||||
info: sinon.stub(),
|
||||
metric: sinon.stub(),
|
||||
_: function() { return "abc"}
|
||||
}
|
||||
|
||||
before(function() {
|
||||
installer.init({});
|
||||
installer.init({log:mockLog, settings:{}, events: new EventEmitter()});
|
||||
});
|
||||
afterEach(function() {
|
||||
if (child_process.spawn.restore) {
|
||||
@@ -74,7 +84,7 @@ describe('nodes/registry/installer', function() {
|
||||
});
|
||||
|
||||
installer.installModule("this_wont_exist").catch(function(err) {
|
||||
err.code.should.be.eql(404);
|
||||
err.should.have.property("code",404);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -156,7 +166,7 @@ describe('nodes/registry/installer', function() {
|
||||
});
|
||||
it("rejects when non-existant path is provided", function(done) {
|
||||
this.timeout(20000);
|
||||
var resourcesDir = path.resolve(path.join(__dirname,"..","resources","local","TestNodeModule","node_modules","NonExistant"));
|
||||
var resourcesDir = path.resolve(path.join(__dirname,"resources","local","TestNodeModule","node_modules","NonExistant"));
|
||||
installer.installModule(resourcesDir).then(function() {
|
||||
done(new Error("Unexpected success"));
|
||||
}).catch(function(err) {
|
||||
@@ -176,7 +186,7 @@ describe('nodes/registry/installer', function() {
|
||||
var addModule = sinon.stub(registry,"addModule",function(md) {
|
||||
return when.resolve(nodeInfo);
|
||||
});
|
||||
var resourcesDir = path.resolve(path.join(__dirname,"..","resources","local","TestNodeModule","node_modules","TestNodeModule"));
|
||||
var resourcesDir = path.resolve(path.join(__dirname,"resources","local","TestNodeModule","node_modules","TestNodeModule"));
|
||||
sinon.stub(child_process,"spawn",function(cmd,args,opt) {
|
||||
var ee = new EventEmitter();
|
||||
ee.stdout = new EventEmitter();
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user