node-red
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
* 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.
|
||||
**/
|
||||
|
||||
/**
|
||||
* This is the comms subsystem of the runtime.
|
||||
* @mixin @node-red/runtime_comms
|
||||
*/
|
||||
|
||||
/**
|
||||
* A WebSocket connection between the runtime and the editor.
|
||||
* @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 = [];
|
||||
|
||||
const events = require("@node-red/util").events;
|
||||
|
||||
function handleCommsEvent(event) {
|
||||
publish(event.topic,event.data,event.retain,event.session,event.excludeSession);
|
||||
}
|
||||
function handleStatusEvent(event) {
|
||||
if (!event.status) {
|
||||
delete retained["status/"+event.id]
|
||||
} else if (!event.status.text && !event.status.fill && !event.status.shape) {
|
||||
if (retained["status/"+event.id]) {
|
||||
publish("status/"+event.id,{},false);
|
||||
}
|
||||
} else {
|
||||
var status = {
|
||||
text: event.status.text,
|
||||
fill: event.status.fill,
|
||||
shape: event.status.shape
|
||||
};
|
||||
publish("status/"+event.id,status,true);
|
||||
}
|
||||
}
|
||||
function handleRuntimeEvent(event) {
|
||||
runtime.log.trace("runtime event: "+JSON.stringify(event));
|
||||
publish("notification/"+event.id,event.payload||{},event.retain);
|
||||
}
|
||||
function handleEventLog(event) {
|
||||
var type = event.payload.type;
|
||||
var id = event.id;
|
||||
if (event.payload.data) {
|
||||
var data = event.payload.data;
|
||||
if (data.endsWith('\n')) {
|
||||
data = data.substring(0,data.length-1);
|
||||
}
|
||||
var lines = data.split(/\n/);
|
||||
lines.forEach(line => {
|
||||
runtime.log.debug((type?("["+type+"] "):"")+line)
|
||||
})
|
||||
}
|
||||
publish("event-log/"+event.id,event.payload||{});
|
||||
}
|
||||
|
||||
function publish(topic, data, retain, session, excludeSession) {
|
||||
if (retain) {
|
||||
retained[topic] = data;
|
||||
} else {
|
||||
delete retained[topic];
|
||||
}
|
||||
connections.forEach(connection => {
|
||||
if ((!session || connection.session === session) && (!excludeSession || connection.session !== excludeSession)) {
|
||||
connection.send(topic,data)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
var api = module.exports = {
|
||||
init: function(_runtime) {
|
||||
runtime = _runtime;
|
||||
connections = [];
|
||||
retained = {};
|
||||
events.removeListener("node-status",handleStatusEvent);
|
||||
events.on("node-status",handleStatusEvent);
|
||||
events.removeListener("runtime-event",handleRuntimeEvent);
|
||||
events.on("runtime-event",handleRuntimeEvent);
|
||||
events.removeListener("comms",handleCommsEvent);
|
||||
events.on("comms",handleCommsEvent);
|
||||
events.removeListener("event-log",handleEventLog);
|
||||
events.on("event-log",handleEventLog);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 @node-red/runtime_comms
|
||||
*/
|
||||
addConnection: async function(opts) {
|
||||
connections.push(opts.client);
|
||||
events.emit('comms:connection-added', {
|
||||
session: opts.client.session,
|
||||
user: opts.client.user
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 @node-red/runtime_comms
|
||||
*/
|
||||
removeConnection: async function(opts) {
|
||||
for (var i=0;i<connections.length;i++) {
|
||||
if (connections[i] === opts.client) {
|
||||
connections.splice(i,1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
events.emit('comms:connection-removed', {
|
||||
session: opts.client.session
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 @node-red/runtime_comms
|
||||
*/
|
||||
subscribe: async 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]);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 @node-red/runtime_comms
|
||||
*/
|
||||
unsubscribe: async function(opts) {},
|
||||
|
||||
/**
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {CommsConnection} opts.client - the client connection
|
||||
* @param {String} opts.topic - the message topic
|
||||
* @param {String} opts.data - the message data
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
*/
|
||||
receive: async function (opts) {
|
||||
if (opts.topic) {
|
||||
events.emit('comms:message:' + opts.topic, {
|
||||
session: opts.client.session,
|
||||
user: opts.user,
|
||||
data: opts.data
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,310 @@
|
||||
/**
|
||||
* 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.
|
||||
**/
|
||||
|
||||
/**
|
||||
* @mixin @node-red/runtime_context
|
||||
*/
|
||||
|
||||
var runtime;
|
||||
|
||||
var util = require("@node-red/util").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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @param {Boolean} opts.keysOnly - whether to return keys only
|
||||
* @return {Promise} - the node information
|
||||
* @memberof @node-red/runtime_context
|
||||
*/
|
||||
getValue: async 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"}, opts.req);
|
||||
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) {
|
||||
if (/^subflow:/.test(node.type)) {
|
||||
ctx = runtime.nodes.getContext(node.id);
|
||||
} else {
|
||||
ctx = node.context();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ctx) {
|
||||
if (key) {
|
||||
store = store || availableStores.default;
|
||||
ctx.get(key,store,function(err, v) {
|
||||
if (opts.keysOnly) {
|
||||
const result = {}
|
||||
if (Array.isArray(v)) {
|
||||
result.format = `array[${v.length}]`
|
||||
} else if (typeof v === 'object') {
|
||||
result.keys = Object.keys(v).map(k => {
|
||||
if (Array.isArray(v[k])) {
|
||||
return { key: k, format: `array[${v[k].length}]`, length: v[k].length }
|
||||
} else if (typeof v[k] === 'object') {
|
||||
return { key: k, format: 'object' }
|
||||
} else {
|
||||
return { key: k }
|
||||
}
|
||||
})
|
||||
result.format = 'object'
|
||||
} else {
|
||||
result.keys = []
|
||||
}
|
||||
resolve({ [store]: result })
|
||||
return
|
||||
}
|
||||
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}, opts.req);
|
||||
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) {
|
||||
if (opts.keysOnly) {
|
||||
ctx.keys(store,function(err, keys) {
|
||||
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"}, opts.req);
|
||||
var err = new Error();
|
||||
err.code = "unexpected_error";
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
}
|
||||
return
|
||||
}
|
||||
result[store] = { keys: keys.map(key => { return { key }}) }
|
||||
c--;
|
||||
if (c === 0) {
|
||||
if (!errorReported) {
|
||||
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key},opts.req);
|
||||
resolve(result);
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
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"}, opts.req);
|
||||
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},opts.req);
|
||||
resolve(result);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key},opts.req);
|
||||
resolve({});
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise} - the node information
|
||||
* @memberof @node-red/runtime_context
|
||||
*/
|
||||
delete: async 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"},opts.req);
|
||||
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) {
|
||||
if (/^subflow:/.test(node.type)) {
|
||||
ctx = runtime.nodes.getContext(node.id);
|
||||
} else {
|
||||
ctx = node.context();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ctx) {
|
||||
if (key) {
|
||||
store = store || availableStores.default;
|
||||
ctx.set(key,undefined,store,function(err) {
|
||||
runtime.log.audit({event: "context.delete",scope:scope,id:id,store:store,key:key},opts.req);
|
||||
resolve();
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
// TODO: support deleting whole context
|
||||
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key,error:"not_found"},opts.req);
|
||||
var err = new Error();
|
||||
err.code = "not_found";
|
||||
err.status = 404;
|
||||
return reject(err);
|
||||
// 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.delete",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.delete",scope:scope,id:id,store:store,key:key},opts.req);
|
||||
resolve();
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
|
||||
const os = require('os');
|
||||
const fs = require('fs');
|
||||
|
||||
let runtime;
|
||||
let isContainerCached;
|
||||
let isWSLCached;
|
||||
|
||||
const isInWsl = () => {
|
||||
if (isWSLCached === undefined) {
|
||||
isWSLCached = getIsInWSL();
|
||||
}
|
||||
return isWSLCached;
|
||||
function getIsInWSL() {
|
||||
if (process.platform !== 'linux') {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
if (os.release().toLowerCase().includes('microsoft')) {
|
||||
if (isInContainer()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return fs.readFileSync('/proc/version', 'utf8').toLowerCase().includes('microsoft') ? !isInContainer() : false;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const isInContainer = () => {
|
||||
if (isContainerCached === undefined) {
|
||||
isContainerCached = hasDockerEnv() || hasDockerCGroup();
|
||||
}
|
||||
return isContainerCached;
|
||||
function hasDockerEnv() {
|
||||
try {
|
||||
fs.statSync('/.dockerenv');
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function hasDockerCGroup() {
|
||||
try {
|
||||
const s = fs.readFileSync('/proc/self/cgroup', 'utf8');
|
||||
if (s.includes('docker')) {
|
||||
return "docker"
|
||||
} else if (s.includes('kubepod')) {
|
||||
return "kubepod"
|
||||
} else if (s.includes('lxc')) {
|
||||
return "lxc"
|
||||
}
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildDiagnosticReport(scope, callback) {
|
||||
const modules = {};
|
||||
const nl = runtime.nodes.getNodeList();
|
||||
for (let i = 0; i < nl.length; i++) {
|
||||
if (modules[nl[i].module]) {
|
||||
continue;
|
||||
}
|
||||
modules[nl[i].module] = nl[i].version
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const {locale, timeZone} = Intl.DateTimeFormat().resolvedOptions();
|
||||
const report = {
|
||||
report: "diagnostics",
|
||||
scope: scope,
|
||||
time: {
|
||||
utc: now.toUTCString(),
|
||||
local: now.toLocaleString(),
|
||||
},
|
||||
intl: {
|
||||
locale, timeZone
|
||||
},
|
||||
nodejs: {
|
||||
version: process.version,
|
||||
arch: process.arch,
|
||||
platform: process.platform,
|
||||
memoryUsage: process.memoryUsage(),
|
||||
},
|
||||
os: {
|
||||
containerised: isInContainer(),
|
||||
wsl: isInWsl(),
|
||||
totalmem: os.totalmem(),
|
||||
freemem: os.freemem(),
|
||||
arch: os.arch(),
|
||||
loadavg: os.loadavg(),
|
||||
platform: os.platform(),
|
||||
release: os.release(),
|
||||
type: os.type(),
|
||||
uptime: os.uptime(),
|
||||
version: os.version(),
|
||||
},
|
||||
runtime: {
|
||||
version: runtime.settings.version,
|
||||
isStarted: runtime.isStarted(),
|
||||
flows: {
|
||||
state: runtime.flows && runtime.flows.state(),
|
||||
started: runtime.flows && runtime.flows.started,
|
||||
},
|
||||
modules: modules,
|
||||
settings: {
|
||||
available: runtime.settings.available(),
|
||||
apiMaxLength: runtime.settings.apiMaxLength || "UNSET",
|
||||
//coreNodesDir: runtime.settings.coreNodesDir,
|
||||
disableEditor: runtime.settings.disableEditor,
|
||||
contextStorage: listContextModules(),
|
||||
debugMaxLength: runtime.settings.debugMaxLength || "UNSET",
|
||||
editorTheme: runtime.settings.editorTheme || "UNSET",
|
||||
flowFile: runtime.settings.flowFile || "UNSET",
|
||||
mqttReconnectTime: runtime.settings.mqttReconnectTime || "UNSET",
|
||||
serialReconnectTime: runtime.settings.serialReconnectTime || "UNSET",
|
||||
socketReconnectTime: runtime.settings.socketReconnectTime || "UNSET",
|
||||
socketTimeout: runtime.settings.socketTimeout || "UNSET",
|
||||
tcpMsgQueueSize: runtime.settings.tcpMsgQueueSize || "UNSET",
|
||||
inboundWebSocketTimeout: runtime.settings.inboundWebSocketTimeout || "UNSET",
|
||||
runtimeState: runtime.settings.runtimeState || "UNSET",
|
||||
|
||||
adminAuth: runtime.settings.adminAuth ? "SET" : "UNSET",
|
||||
|
||||
httpAdminRoot: runtime.settings.httpAdminRoot || "UNSET",
|
||||
httpAdminCors: runtime.settings.httpAdminCors ? "SET" : "UNSET",
|
||||
httpNodeAuth: runtime.settings.httpNodeAuth ? "SET" : "UNSET",
|
||||
|
||||
httpNodeRoot: runtime.settings.httpNodeRoot || "UNSET",
|
||||
httpNodeCors: runtime.settings.httpNodeCors ? "SET" : "UNSET",
|
||||
|
||||
httpStatic: runtime.settings.httpStatic ? "SET" : "UNSET",
|
||||
httpStaticRoot: runtime.settings.httpStaticRoot || "UNSET",
|
||||
httpStaticCors: runtime.settings.httpStaticCors ? "SET" : "UNSET",
|
||||
|
||||
uiHost: runtime.settings.uiHost ? "SET" : "UNSET",
|
||||
uiPort: runtime.settings.uiPort ? "SET" : "UNSET",
|
||||
userDir: runtime.settings.userDir ? "SET" : "UNSET",
|
||||
nodesDir: runtime.settings.nodesDir && runtime.settings.nodesDir.length ? "SET" : "UNSET",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if (scope == "admin") {
|
||||
// const moreSettings = {
|
||||
// adminAuth_type: (runtime.settings.adminAuth && runtime.settings.adminAuth.type) ? runtime.settings.adminAuth.type : "UNSET",
|
||||
// httpAdminCors: runtime.settings.httpAdminCors ? runtime.settings.httpAdminCors : "UNSET",
|
||||
// httpNodeCors: runtime.settings.httpNodeCors ? runtime.settings.httpNodeCors : "UNSET",
|
||||
// httpStaticCors: runtime.settings.httpStaticCors ? "SET" : "UNSET",
|
||||
// settingsFile: runtime.settings.settingsFile ? runtime.settings.settingsFile : "UNSET",
|
||||
// uiHost: runtime.settings.uiHost ? runtime.settings.uiHost : "UNSET",
|
||||
// uiPort: runtime.settings.uiPort ? runtime.settings.uiPort : "UNSET",
|
||||
// userDir: runtime.settings.userDir ? runtime.settings.userDir : "UNSET",
|
||||
// }
|
||||
// const moreNodejs = {
|
||||
// execPath: process.execPath,
|
||||
// pid: process.pid,
|
||||
// }
|
||||
// const moreOs = {
|
||||
// cpus: os.cpus(),
|
||||
// homedir: os.homedir(),
|
||||
// hostname: os.hostname(),
|
||||
// networkInterfaces: os.networkInterfaces(),
|
||||
// }
|
||||
// report.runtime.settings = Object.assign({}, report.runtime.settings, moreSettings);
|
||||
// report.nodejs = Object.assign({}, report.nodejs, moreNodejs);
|
||||
// report.os = Object.assign({}, report.os, moreOs);
|
||||
// }
|
||||
|
||||
callback(report);
|
||||
|
||||
/** gets a sanitised list containing only the module name */
|
||||
function listContextModules() {
|
||||
const keys = Object.keys(runtime.settings.contextStorage || {});
|
||||
const result = {};
|
||||
keys.forEach(e => {
|
||||
result[e] = {
|
||||
module: String(runtime.settings.contextStorage[e].module)
|
||||
}
|
||||
})
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
init: function (_runtime) {
|
||||
runtime = _runtime;
|
||||
},
|
||||
/**
|
||||
* Gets the node-red diagnostics report
|
||||
* @param {{scope: string}} opts - settings
|
||||
* @return {Promise} the diagnostics information
|
||||
* @memberof @node-red/diagnostics
|
||||
*/
|
||||
get: async function (opts) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
opts = opts || {}
|
||||
try {
|
||||
runtime.log.audit({ event: "diagnostics.get", scope: opts.scope }, opts.req);
|
||||
buildDiagnosticReport(opts.scope, (report) => resolve(report));
|
||||
} catch (error) {
|
||||
error.status = 500;
|
||||
reject(error);
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
/**
|
||||
* 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.
|
||||
**/
|
||||
|
||||
/**
|
||||
* @mixin @node-red/runtime_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 Mutex = require('async-mutex').Mutex;
|
||||
const mutex = new Mutex();
|
||||
|
||||
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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Flows>} - the active flow configuration
|
||||
* @memberof @node-red/runtime_flows
|
||||
*/
|
||||
getFlows: async function(opts) {
|
||||
runtime.log.audit({event: "flows.get"}, opts.req);
|
||||
return runtime.flows.getFlows();
|
||||
},
|
||||
/**
|
||||
* Sets the current flow configuration
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {Object} opts.flows - the flow configuration: `{flows: [..], credentials: {}}`
|
||||
* @param {Object} opts.deploymentType - the type of deployment - "full", "nodes", "flows", "reload"
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Flows>} - the active flow configuration
|
||||
* @memberof @node-red/runtime_flows
|
||||
*/
|
||||
setFlows: async function(opts) {
|
||||
return mutex.runExclusive(async function() {
|
||||
var flows = opts.flows;
|
||||
var deploymentType = opts.deploymentType||"full";
|
||||
runtime.log.audit({event: "flows.set",type:deploymentType}, opts.req);
|
||||
|
||||
var apiPromise;
|
||||
if (deploymentType === 'reload') {
|
||||
apiPromise = runtime.flows.loadFlows(true);
|
||||
} else {
|
||||
if (flows.hasOwnProperty('rev')) {
|
||||
var currentVersion = runtime.flows.getFlows().rev;
|
||||
if (currentVersion !== flows.rev) {
|
||||
var err;
|
||||
err = new Error();
|
||||
err.code = "version_mismatch";
|
||||
err.status = 409;
|
||||
//TODO: log warning
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
apiPromise = runtime.flows.setFlows(flows.flows,flows.credentials,deploymentType,null,null,opts.user);
|
||||
}
|
||||
return apiPromise.then(function(flowId) {
|
||||
return {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);
|
||||
throw 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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<String>} - the id of the added flow
|
||||
* @memberof @node-red/runtime_flows
|
||||
*/
|
||||
addFlow: async function(opts) {
|
||||
return mutex.runExclusive(async function() {
|
||||
var flow = opts.flow;
|
||||
return runtime.flows.addFlow(flow, opts.user).then(function (id) {
|
||||
runtime.log.audit({event: "flow.add", id: id}, opts.req);
|
||||
return id;
|
||||
}).catch(function (err) {
|
||||
runtime.log.audit({
|
||||
event: "flow.add",
|
||||
error: err.code || "unexpected_error",
|
||||
message: err.toString()
|
||||
}, opts.req);
|
||||
err.status = 400;
|
||||
throw 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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Flow>} - the active flow configuration
|
||||
* @memberof @node-red/runtime_flows
|
||||
*/
|
||||
getFlow: async function(opts) {
|
||||
var flow = runtime.flows.getFlow(opts.id);
|
||||
if (flow) {
|
||||
runtime.log.audit({event: "flow.get",id:opts.id}, opts.req);
|
||||
return flow;
|
||||
} else {
|
||||
runtime.log.audit({event: "flow.get",id:opts.id,error:"not_found"}, opts.req);
|
||||
var err = new Error();
|
||||
err.code = "not_found";
|
||||
err.status = 404;
|
||||
throw 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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<String>} - the id of the updated flow
|
||||
* @memberof @node-red/runtime_flows
|
||||
*/
|
||||
updateFlow: async function(opts) {
|
||||
return mutex.runExclusive(async function() {
|
||||
var flow = opts.flow;
|
||||
var id = opts.id;
|
||||
return runtime.flows.updateFlow(id, flow, opts.user).then(function () {
|
||||
runtime.log.audit({event: "flow.update", id: id}, opts.req);
|
||||
return id;
|
||||
}).catch(function (err) {
|
||||
if (err.code === 404) {
|
||||
runtime.log.audit({event: "flow.update", id: id, error: "not_found"}, opts.req);
|
||||
// TODO: this swap around of .code and .status isn't ideal
|
||||
err.status = 404;
|
||||
err.code = "not_found";
|
||||
} else {
|
||||
runtime.log.audit({
|
||||
event: "flow.update",
|
||||
error: err.code || "unexpected_error",
|
||||
message: err.toString()
|
||||
}, opts.req);
|
||||
err.status = 400;
|
||||
}
|
||||
throw 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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise} - resolves if successful
|
||||
* @memberof @node-red/runtime_flows
|
||||
*/
|
||||
deleteFlow: async function(opts) {
|
||||
return mutex.runExclusive(function() {
|
||||
var id = opts.id;
|
||||
return runtime.flows.removeFlow(id, opts.user).then(function () {
|
||||
runtime.log.audit({event: "flow.remove", id: id}, opts.req);
|
||||
return;
|
||||
}).catch(function (err) {
|
||||
if (err.code === 404) {
|
||||
runtime.log.audit({event: "flow.remove", id: id, error: "not_found"}, opts.req);
|
||||
// TODO: this swap around of .code and .status isn't ideal
|
||||
err.status = 404;
|
||||
err.code = "not_found";
|
||||
} else {
|
||||
runtime.log.audit({
|
||||
event: "flow.remove",
|
||||
id: id,
|
||||
error: err.code || "unexpected_error",
|
||||
message: err.toString()
|
||||
}, opts.req);
|
||||
err.status = 400;
|
||||
}
|
||||
throw 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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - the safe credentials
|
||||
* @memberof @node-red/runtime_flows
|
||||
*/
|
||||
getNodeCredentials: async function(opts) {
|
||||
runtime.log.audit({event: "credentials.get",type:opts.type,id:opts.id}, opts.req);
|
||||
var credentials = runtime.nodes.getCredentials(opts.id);
|
||||
if (!credentials) {
|
||||
return {};
|
||||
}
|
||||
var sendCredentials = {};
|
||||
var cred;
|
||||
if (/^subflow(:|$)/.test(opts.type) ||
|
||||
(opts.type === "tab") ||
|
||||
(opts.type === "group")) {
|
||||
for (cred in credentials) {
|
||||
if (credentials.hasOwnProperty(cred)) {
|
||||
sendCredentials['has_'+cred] = credentials[cred] != null && credentials[cred] !== '';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var definition = runtime.nodes.getCredentialDefinition(opts.type) || {};
|
||||
for (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] || '';
|
||||
}
|
||||
}
|
||||
}
|
||||
return sendCredentials;
|
||||
},
|
||||
/**
|
||||
* Gets running state of runtime flows
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {{state:string, started:boolean}} - the current run state of the flows
|
||||
* @memberof @node-red/runtime_flows
|
||||
*/
|
||||
getState: async function(opts) {
|
||||
runtime.log.audit({event: "flows.getState"}, opts.req);
|
||||
const result = {
|
||||
state: runtime.flows.state()
|
||||
}
|
||||
return result;
|
||||
},
|
||||
/**
|
||||
* Sets running state of runtime flows
|
||||
* @param {Object} opts
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {string} opts.state - the requested state. Valid values are "start" and "stop".
|
||||
* @return {Promise<Flow>} - the active flow configuration
|
||||
* @memberof @node-red/runtime_flows
|
||||
*/
|
||||
setState: async function(opts) {
|
||||
opts = opts || {};
|
||||
const makeError = (error, errcode, statusCode) => {
|
||||
const message = typeof error == "object" ? error.message : error
|
||||
const err = typeof error == "object" ? error : new Error(message||"Unexpected Error")
|
||||
err.status = err.status || statusCode || 400;
|
||||
err.code = err.code || errcode || "unexpected_error"
|
||||
runtime.log.audit({
|
||||
event: "flows.setState",
|
||||
state: opts.state || "",
|
||||
error: errcode || "unexpected_error",
|
||||
message: err.code
|
||||
}, opts.req);
|
||||
return err
|
||||
}
|
||||
|
||||
const getState = () => {
|
||||
return {
|
||||
state: runtime.flows.state()
|
||||
}
|
||||
}
|
||||
|
||||
if(!runtime.settings.runtimeState || runtime.settings.runtimeState.enabled !== true) {
|
||||
throw (makeError("Method Not Allowed", "not_allowed", 405))
|
||||
}
|
||||
switch (opts.state) {
|
||||
case "start":
|
||||
try {
|
||||
try {
|
||||
runtime.settings.set('runtimeFlowState', opts.state);
|
||||
} catch(err) {}
|
||||
if (runtime.settings.safeMode) {
|
||||
delete runtime.settings.safeMode
|
||||
}
|
||||
await runtime.flows.startFlows("full")
|
||||
return getState()
|
||||
} catch (err) {
|
||||
throw (makeError(err, err.code, 500))
|
||||
}
|
||||
case "stop":
|
||||
try {
|
||||
try {
|
||||
runtime.settings.set('runtimeFlowState', opts.state);
|
||||
} catch(err) {}
|
||||
await runtime.flows.stopFlows("full")
|
||||
return getState()
|
||||
} catch (err) {
|
||||
throw (makeError(err, err.code, 500))
|
||||
}
|
||||
default:
|
||||
throw (makeError(`Cannot change flows runtime state to '${opts.state}'}`, "invalid_run_state", 400))
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*!
|
||||
* 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 runtime;
|
||||
|
||||
var api = module.exports = {
|
||||
init: function(_runtime) {
|
||||
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);
|
||||
api.plugins.init(runtime);
|
||||
api.diagnostics.init(runtime);
|
||||
},
|
||||
|
||||
comms: require("./comms"),
|
||||
flows: require("./flows"),
|
||||
library: require("./library"),
|
||||
nodes: require("./nodes"),
|
||||
settings: require("./settings"),
|
||||
projects: require("./projects"),
|
||||
context: require("./context"),
|
||||
plugins: require("./plugins"),
|
||||
diagnostics: require("./diagnostics"),
|
||||
|
||||
isStarted: async function(opts) {
|
||||
return runtime.isStarted();
|
||||
},
|
||||
version: async function(opts) {
|
||||
return runtime.version();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* 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.
|
||||
**/
|
||||
|
||||
/**
|
||||
* @mixin @node-red/runtime_library
|
||||
*/
|
||||
|
||||
var runtime;
|
||||
|
||||
var api = module.exports = {
|
||||
init: function(_runtime) {
|
||||
runtime = _runtime;
|
||||
},
|
||||
|
||||
// /* *
|
||||
// * Gets the configuration of a library source.
|
||||
// * @param {Object} opts
|
||||
// * @param {User} opts.user - the user calling the api
|
||||
// * @param {String} opts.library - the library
|
||||
// * @param {Object} opts.req - the request to log (optional)
|
||||
// * @return {Promise<String|Object>} - resolves when complete
|
||||
// * @memberof @node-red/runtime_library
|
||||
// */
|
||||
// getConfig: async function(opts) {
|
||||
// runtime.log.audit({event: "library.getConfig",library:opts.library}, opts.req);
|
||||
// try {
|
||||
// return runtime.library.getConfig(opts.library)
|
||||
// } catch(err) {
|
||||
// var error = new Error();
|
||||
// error.code = "not_found";
|
||||
// error.status = 404;
|
||||
// throw error;
|
||||
// }
|
||||
// },
|
||||
|
||||
/**
|
||||
* Gets an entry from the library.
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.library - the library
|
||||
* @param {String} opts.type - the type of entry
|
||||
* @param {String} opts.path - the path of the entry
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<String|Object>} - resolves when complete
|
||||
* @memberof @node-red/runtime_library
|
||||
*/
|
||||
getEntry: async function(opts) {
|
||||
return runtime.library.getEntry(opts.library,opts.type,opts.path).then(function(result) {
|
||||
runtime.log.audit({event: "library.get",library:opts.library,type:opts.type,path:opts.path}, opts.req);
|
||||
return result;
|
||||
}).catch(function(err) {
|
||||
if (err) {
|
||||
runtime.log.warn(runtime.log._("api.library.error-load-entry",{library:opts.library,type:opts.type,path:opts.path,message:err.toString()}));
|
||||
if (err.code === 'forbidden') {
|
||||
err.status = 403;
|
||||
throw err;
|
||||
} else if (err.code === "not_found") {
|
||||
err.status = 404;
|
||||
} else {
|
||||
err.status = 400;
|
||||
}
|
||||
runtime.log.audit({event: "library.get",library:opts.library,type:opts.type,path:opts.path,error:err.code}, opts.req);
|
||||
throw err;
|
||||
}
|
||||
runtime.log.audit({event: "library.get",library:opts.library,type:opts.type,error:"not_found"}, opts.req);
|
||||
var error = new Error();
|
||||
error.code = "not_found";
|
||||
error.status = 404;
|
||||
throw error;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Saves an entry to the library
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.library - the library
|
||||
* @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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise} - resolves when complete
|
||||
* @memberof @node-red/runtime_library
|
||||
*/
|
||||
saveEntry: async function(opts) {
|
||||
return runtime.library.saveEntry(opts.library,opts.type,opts.path,opts.meta,opts.body).then(function() {
|
||||
runtime.log.audit({event: "library.set",type:opts.type,path:opts.path}, opts.req);
|
||||
}).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"}, opts.req);
|
||||
err.status = 403;
|
||||
throw err;
|
||||
}
|
||||
runtime.log.audit({event: "library.set",type:opts.type,path:opts.path,error:"unexpected_error",message:err.toString()}, opts.req);
|
||||
var error = new Error();
|
||||
error.status = 400;
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,492 @@
|
||||
/**
|
||||
* 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.
|
||||
**/
|
||||
|
||||
/**
|
||||
* @mixin @node-red/runtime_nodes
|
||||
*/
|
||||
|
||||
var fs = require("fs-extra");
|
||||
|
||||
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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<NodeInfo>} - the node information
|
||||
* @memberof @node-red/runtime_nodes
|
||||
*/
|
||||
getNodeInfo: async function(opts) {
|
||||
var id = opts.id;
|
||||
var result = runtime.nodes.getNodeInfo(id);
|
||||
if (result) {
|
||||
runtime.log.audit({event: "nodes.info.get",id:id}, opts.req);
|
||||
delete result.loaded;
|
||||
return result;
|
||||
} else {
|
||||
runtime.log.audit({event: "nodes.info.get",id:id,error:"not_found"}, opts.req);
|
||||
var err = new Error("Node not found");
|
||||
err.code = "not_found";
|
||||
err.status = 404;
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the list of node modules installed in the runtime
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<NodeList>} - the list of node modules
|
||||
* @memberof @node-red/runtime_nodes
|
||||
*/
|
||||
getNodeList: async function(opts) {
|
||||
runtime.log.audit({event: "nodes.list.get"}, opts.req);
|
||||
return 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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<String>} - the node html content
|
||||
* @memberof @node-red/runtime_nodes
|
||||
*/
|
||||
getNodeConfig: async function(opts) {
|
||||
var id = opts.id;
|
||||
var lang = opts.lang;
|
||||
if (/[^0-9a-z=\-\*]/i.test(opts.lang)) {
|
||||
reject(new Error("Invalid language: "+opts.lang));
|
||||
return
|
||||
}
|
||||
var result = runtime.nodes.getNodeConfig(id,lang);
|
||||
if (result) {
|
||||
runtime.log.audit({event: "nodes.config.get",id:id}, opts.req);
|
||||
return result;
|
||||
} else {
|
||||
runtime.log.audit({event: "nodes.config.get",id:id,error:"not_found"}, opts.req);
|
||||
var err = new Error("Node not found");
|
||||
err.code = "not_found";
|
||||
err.status = 404;
|
||||
throw 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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<String>} - the node html content
|
||||
* @memberof @node-red/runtime_nodes
|
||||
*/
|
||||
getNodeConfigs: async function(opts) {
|
||||
runtime.log.audit({event: "nodes.configs.get"}, opts.req);
|
||||
if (/[^0-9a-z=\-\*]/i.test(opts.lang)) {
|
||||
reject(new Error("Invalid language: "+opts.lang));
|
||||
return
|
||||
}
|
||||
return 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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<ModuleInfo>} - the node module info
|
||||
* @memberof @node-red/runtime_nodes
|
||||
*/
|
||||
getModuleInfo: async function(opts) {
|
||||
var result = runtime.nodes.getModuleInfo(opts.module);
|
||||
if (result) {
|
||||
runtime.log.audit({event: "nodes.module.get",id:opts.module}, opts.req);
|
||||
return result;
|
||||
} else {
|
||||
runtime.log.audit({event: "nodes.module.get",id:opts.module,error:"not_found"}, opts.req);
|
||||
var err = new Error("Module not found");
|
||||
err.code = "not_found";
|
||||
err.status = 404;
|
||||
throw 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
|
||||
* @param {Object} opts.tarball - (optional) a tarball file to install. Object has properties `name`, `size` and `buffer`.
|
||||
* @param {String} opts.url - (optional) url to install
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<ModuleInfo>} - the node module info
|
||||
* @memberof @node-red/runtime_nodes
|
||||
*/
|
||||
addModule: async function(opts) {
|
||||
if (!runtime.settings.available()) {
|
||||
runtime.log.audit({event: "nodes.install",error:"settings_unavailable"}, opts.req);
|
||||
var err = new Error("Settings unavailable");
|
||||
err.code = "settings_unavailable";
|
||||
err.status = 400;
|
||||
throw err;
|
||||
}
|
||||
if (opts.tarball) {
|
||||
if (runtime.settings.externalModules && runtime.settings.externalModules.palette && runtime.settings.externalModules.palette.upload === false) {
|
||||
runtime.log.audit({event: "nodes.install",tarball:opts.tarball.file,error:"invalid_request"}, opts.req);
|
||||
var err = new Error("Invalid request");
|
||||
err.code = "invalid_request";
|
||||
err.status = 400;
|
||||
throw err;
|
||||
}
|
||||
if (opts.module || opts.version || opts.url) {
|
||||
runtime.log.audit({event: "nodes.install",tarball:opts.tarball.file,module:opts.module,error:"invalid_request"}, opts.req);
|
||||
var err = new Error("Invalid request");
|
||||
err.code = "invalid_request";
|
||||
err.status = 400;
|
||||
throw err;
|
||||
}
|
||||
return runtime.nodes.installModule(opts.tarball.buffer).then(function(info) {
|
||||
runtime.log.audit({event: "nodes.install",tarball:opts.tarball.file,module:info.id}, opts.req);
|
||||
return info;
|
||||
}).catch(function(err) {
|
||||
if (err.code) {
|
||||
err.status = 400;
|
||||
runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url,error:err.code}, opts.req);
|
||||
} else {
|
||||
err.status = 400;
|
||||
runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url,error:err.code||"unexpected_error",message:err.toString()}, opts.req);
|
||||
}
|
||||
throw err;
|
||||
})
|
||||
}
|
||||
if (opts.module) {
|
||||
var existingModule = runtime.nodes.getModuleInfo(opts.module);
|
||||
if (existingModule && existingModule.user) {
|
||||
if (!opts.version || existingModule.version === opts.version) {
|
||||
runtime.log.audit({event: "nodes.install",module:opts.module, version:opts.version, error:"module_already_loaded"}, opts.req);
|
||||
var err = new Error("Module already loaded");
|
||||
err.code = "module_already_loaded";
|
||||
err.status = 400;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
return runtime.nodes.installModule(opts.module,opts.version,opts.url).then(function(info) {
|
||||
runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url}, opts.req);
|
||||
return info;
|
||||
}).catch(function(err) {
|
||||
if (err.code === 404) {
|
||||
runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url,error:"not_found"}, opts.req);
|
||||
// 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,url:opts.url,error:err.code}, opts.req);
|
||||
} else {
|
||||
err.status = 400;
|
||||
runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url,error:err.code||"unexpected_error",message:err.toString()}, opts.req);
|
||||
}
|
||||
throw err;
|
||||
})
|
||||
} else {
|
||||
runtime.log.audit({event: "nodes.install",module:opts.module,error:"invalid_request"}, opts.req);
|
||||
var err = new Error("Invalid request");
|
||||
err.code = "invalid_request";
|
||||
err.status = 400;
|
||||
throw 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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise} - resolves when complete
|
||||
* @memberof @node-red/runtime_nodes
|
||||
*/
|
||||
removeModule: async function(opts) {
|
||||
if (!runtime.settings.available()) {
|
||||
runtime.log.audit({event: "nodes.install",error:"settings_unavailable"}, opts.req);
|
||||
var err = new Error("Settings unavailable");
|
||||
err.code = "settings_unavailable";
|
||||
err.status = 400;
|
||||
throw err;
|
||||
}
|
||||
var module = runtime.nodes.getModuleInfo(opts.module);
|
||||
if (!module) {
|
||||
runtime.log.audit({event: "nodes.remove",module:opts.module,error:"not_found"}, opts.req);
|
||||
var err = new Error("Module not found");
|
||||
err.code = "not_found";
|
||||
err.status = 404;
|
||||
throw err;
|
||||
}
|
||||
try {
|
||||
return runtime.nodes.uninstallModule(opts.module).then(function() {
|
||||
runtime.log.audit({event: "nodes.remove",module:opts.module}, opts.req);
|
||||
}).catch(function(err) {
|
||||
err.status = 400;
|
||||
runtime.log.audit({event: "nodes.remove",module:opts.module,error:err.code||"unexpected_error",message:err.toString()}, opts.req);
|
||||
throw err;
|
||||
})
|
||||
} catch(error) {
|
||||
runtime.log.audit({event: "nodes.remove",module:opts.module,error:error.code||"unexpected_error",message:error.toString()}, opts.req);
|
||||
error.status = 400;
|
||||
throw 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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<ModuleInfo>} - the module info object
|
||||
* @memberof @node-red/runtime_nodes
|
||||
*/
|
||||
setModuleState: async function(opts) {
|
||||
var mod = opts.module;
|
||||
if (!runtime.settings.available()) {
|
||||
runtime.log.audit({event: "nodes.module.set",error:"settings_unavailable"}, opts.req);
|
||||
var err = new Error("Settings unavailable");
|
||||
err.code = "settings_unavailable";
|
||||
err.status = 400;
|
||||
throw err;
|
||||
}
|
||||
try {
|
||||
var module = runtime.nodes.getModuleInfo(mod);
|
||||
if (!module) {
|
||||
runtime.log.audit({event: "nodes.module.set",module:mod,error:"not_found"}, opts.req);
|
||||
var err = new Error("Module not found");
|
||||
err.code = "not_found";
|
||||
err.status = 404;
|
||||
throw err;
|
||||
}
|
||||
|
||||
var nodes = module.nodes;
|
||||
var promises = [];
|
||||
for (var i = 0; i < nodes.length; ++i) {
|
||||
promises.push(putNode(nodes[i],opts.enabled));
|
||||
}
|
||||
return Promise.all(promises).then(function() {
|
||||
return runtime.nodes.getModuleInfo(mod);
|
||||
}).catch(function(err) {
|
||||
err.status = 400;
|
||||
throw err;
|
||||
});
|
||||
} catch(error) {
|
||||
runtime.log.audit({event: "nodes.module.set",module:mod,enabled:opts.enabled,error:error.code||"unexpected_error",message:error.toString()}, opts.req);
|
||||
error.status = 400;
|
||||
throw 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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<ModuleInfo>} - the module info object
|
||||
* @memberof @node-red/runtime_nodes
|
||||
*/
|
||||
setNodeSetState: async function(opts) {
|
||||
if (!runtime.settings.available()) {
|
||||
runtime.log.audit({event: "nodes.info.set",error:"settings_unavailable"}, opts.req);
|
||||
var err = new Error("Settings unavailable");
|
||||
err.code = "settings_unavailable";
|
||||
err.status = 400;
|
||||
throw 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"}, opts.req);
|
||||
var err = new Error("Node not found");
|
||||
err.code = "not_found";
|
||||
err.status = 404;
|
||||
throw err;
|
||||
} else {
|
||||
delete node.loaded;
|
||||
return putNode(node,enabled).then(function(result) {
|
||||
runtime.log.audit({event: "nodes.info.set",id:id,enabled:enabled}, opts.req);
|
||||
return result;
|
||||
}).catch(function(err) {
|
||||
runtime.log.audit({event: "nodes.info.set",id:id,enabled:enabled,error:err.code||"unexpected_error",message:err.toString()}, opts.req);
|
||||
err.status = 400;
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
} catch(error) {
|
||||
runtime.log.audit({event: "nodes.info.set",id:id,enabled:enabled,error:error.code||"unexpected_error",message:error.toString()}, opts.req);
|
||||
error.status = 400;
|
||||
throw 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)
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - the message catalogs
|
||||
* @memberof @node-red/runtime_nodes
|
||||
*/
|
||||
getModuleCatalogs: async function(opts) {
|
||||
var namespace = opts.module;
|
||||
var lang = opts.lang;
|
||||
if (/[^0-9a-z=\-\*]/i.test(lang)) {
|
||||
reject(new Error("Invalid language: "+lang));
|
||||
return
|
||||
}
|
||||
var prevLang = runtime.i18n.i.language;
|
||||
// Trigger a load from disk of the language if it is not the default
|
||||
return new Promise( (resolve,reject) => {
|
||||
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)||{};
|
||||
}
|
||||
});
|
||||
runtime.i18n.i.changeLanguage(prevLang);
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 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)
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - the message catalog
|
||||
* @memberof @node-red/runtime_nodes
|
||||
*/
|
||||
getModuleCatalog: async function(opts) {
|
||||
var namespace = opts.module;
|
||||
var lang = opts.lang;
|
||||
if (/[^0-9a-z=\-\*]/i.test(lang)) {
|
||||
reject(new Error("Invalid language: "+lang));
|
||||
return
|
||||
}
|
||||
var prevLang = runtime.i18n.i.language;
|
||||
// Trigger a load from disk of the language if it is not the default
|
||||
return new Promise(resolve => {
|
||||
runtime.i18n.i.changeLanguage(lang, function() {
|
||||
var catalog = runtime.i18n.i.getResourceBundle(lang, namespace);
|
||||
runtime.i18n.i.changeLanguage(prevLang);
|
||||
resolve(catalog||{});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<IconList>} - the list of all icons
|
||||
* @memberof @node-red/runtime_nodes
|
||||
*/
|
||||
getIconList: async function(opts) {
|
||||
runtime.log.audit({event: "nodes.icons.get"}, opts.req);
|
||||
return 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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Buffer>} - the icon file as a Buffer or null if no icon available
|
||||
* @memberof @node-red/runtime_nodes
|
||||
*/
|
||||
getIcon: async function(opts) {
|
||||
var iconPath = runtime.nodes.getNodeIconPath(opts.module,opts.icon);
|
||||
if (iconPath) {
|
||||
return fs.readFile(iconPath).catch(err => {
|
||||
err.status = 400;
|
||||
throw err;
|
||||
});
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a resource from a module
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.module - the id of the module requesting the resource
|
||||
* @param {String} opts.path - the path of the resource
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Buffer>} - the resource file as a Buffer or null if not found
|
||||
* @memberof @node-red/runtime_nodes
|
||||
*/
|
||||
getModuleResource: async function(opts) {
|
||||
var resourcePath = runtime.nodes.getModuleResource(opts.module, opts.path);
|
||||
if (resourcePath) {
|
||||
return fs.readFile(resourcePath).catch(err => {
|
||||
if (err.code === 'EISDIR') {
|
||||
return null;
|
||||
}
|
||||
err.status = 400;
|
||||
throw err;
|
||||
});
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* @mixin @node-red/runtime_plugins
|
||||
*/
|
||||
|
||||
var runtime;
|
||||
|
||||
var api = module.exports = {
|
||||
init: function(_runtime) {
|
||||
runtime = _runtime;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a plugin definition from the registry
|
||||
* @param {Object} opts
|
||||
* @param {String} opts.id - the id of the plugin to get
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<PluginDefinition>} - the plugin definition
|
||||
* @memberof @node-red/runtime_plugins
|
||||
*/
|
||||
getPlugin: async function(opts) {
|
||||
return runtime.plugins.getPlugin(opts.id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets all plugin definitions of a given type
|
||||
* @param {Object} opts
|
||||
* @param {String} opts.type - the type of the plugins to get
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Array>} - the plugin definitions
|
||||
* @memberof @node-red/runtime_plugins
|
||||
*/
|
||||
getPluginsByType: async function(opts) {
|
||||
return runtime.plugins.getPluginsByType(opts.type);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the editor content for an individual plugin
|
||||
* @param {String} opts.lang - the locale language to return
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<NodeInfo>} - the node information
|
||||
* @memberof @node-red/runtime_plugins
|
||||
*/
|
||||
getPluginList: async function(opts) {
|
||||
runtime.log.audit({event: "plugins.list.get"}, opts.req);
|
||||
return runtime.plugins.getPluginList();
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the editor content for all registered plugins
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<NodeInfo>} - the node information
|
||||
* @memberof @node-red/runtime_plugins
|
||||
*/
|
||||
getPluginConfigs: async function(opts) {
|
||||
if (/[^0-9a-z=\-\*]/i.test(opts.lang)) {
|
||||
throw new Error("Invalid language: "+opts.lang)
|
||||
return;
|
||||
}
|
||||
runtime.log.audit({event: "plugins.configs.get"}, opts.req);
|
||||
return runtime.plugins.getPluginConfigs(opts.lang);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the editor content for one registered plugin
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<NodeInfo>} - the plugin information
|
||||
* @memberof @node-red/runtime_plugins
|
||||
*/
|
||||
getPluginConfig: async function(opts) {
|
||||
if (/[^0-9a-z=\-\*]/i.test(opts.lang)) {
|
||||
throw new Error("Invalid language: "+opts.lang)
|
||||
return;
|
||||
}
|
||||
runtime.log.audit({event: "plugins.configs.get"}, opts.req);
|
||||
return runtime.plugins.getPluginConfig(opts.module, opts.lang);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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)
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - the message catalogs
|
||||
* @memberof @node-red/runtime_plugins
|
||||
*/
|
||||
getPluginCatalogs: async function(opts) {
|
||||
var lang = opts.lang;
|
||||
var prevLang = runtime.i18n.i.language;
|
||||
// Trigger a load from disk of the language if it is not the default
|
||||
return new Promise( (resolve,reject) => {
|
||||
runtime.i18n.i.changeLanguage(lang, function(){
|
||||
var nodeList = runtime.plugins.getPluginList();
|
||||
var result = {};
|
||||
nodeList.forEach(function(n) {
|
||||
if (n.module !== "node-red") {
|
||||
result[n.id] = runtime.i18n.i.getResourceBundle(lang, n.id)||{};
|
||||
}
|
||||
});
|
||||
runtime.i18n.i.changeLanguage(prevLang);
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,490 @@
|
||||
/**
|
||||
* 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.
|
||||
**/
|
||||
|
||||
/**
|
||||
* @mixin @node-red/runtime_projects
|
||||
*/
|
||||
|
||||
var runtime;
|
||||
|
||||
var api = module.exports = {
|
||||
init: function(_runtime) {
|
||||
runtime = _runtime;
|
||||
},
|
||||
available: async function(opts) {
|
||||
return !!runtime.storage.projects;
|
||||
},
|
||||
/**
|
||||
* List projects known to the runtime
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
listProjects: async 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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
createProject: async function(opts) {
|
||||
runtime.log.audit({event: "projects.create",name:opts.project?opts.project.name:"missing-name"}, opts.req);
|
||||
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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
initialiseProject: async function(opts) {
|
||||
// Initialised set when creating default files for an empty repo
|
||||
runtime.log.audit({event: "projects.initialise",id:opts.id}, opts.req);
|
||||
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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - the active project
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
getActiveProject: async function(opts) {
|
||||
return 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
|
||||
* @param {boolean} opts.clearContext - whether to clear context
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
setActiveProject: async function(opts) {
|
||||
var currentProject = runtime.storage.projects.getActiveProject(opts.user);
|
||||
runtime.log.audit({event: "projects.set",id:opts.id}, opts.req);
|
||||
if (!currentProject || opts.id !== currentProject.name) {
|
||||
return runtime.storage.projects.setActiveProject(opts.user, opts.id, opts.clearContext);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - the project metadata
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
getProject: async 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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
updateProject: async function(opts) {
|
||||
runtime.log.audit({event: "projects.update",id:opts.id}, opts.req);
|
||||
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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
deleteProject: async function(opts) {
|
||||
runtime.log.audit({event: "projects.delete",id:opts.id}, opts.req);
|
||||
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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - the project status
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
getStatus: async 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)
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - a list of the local branches
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
getBranches: async 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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - the status of the branch
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
getBranchStatus: async 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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
setBranch: async function(opts) {
|
||||
runtime.log.audit({event: "projects.branch.set",id:opts.id, branch: opts.branch, create:opts.create}, opts.req);
|
||||
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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
deleteBranch: async function(opts) {
|
||||
runtime.log.audit({event: "projects.branch.delete",id:opts.id, branch: opts.branch, force:opts.force}, opts.req);
|
||||
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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
commit: async function(opts) {
|
||||
runtime.log.audit({event: "projects.commit",id:opts.id}, opts.req);
|
||||
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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - the commit details
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
getCommit: async 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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Array>} - an array of commits
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
getCommits: async 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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
abortMerge: async function(opts) {
|
||||
runtime.log.audit({event: "projects.merge.abort",id:opts.id}, opts.req);
|
||||
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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
resolveMerge: async function(opts) {
|
||||
runtime.log.audit({event: "projects.merge.resolve",id:opts.id, file:opts.path}, opts.req);
|
||||
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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - the file listing
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
getFiles: async 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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<String>} - the content of the file
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
getFile: async 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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
stageFile: async function(opts) {
|
||||
runtime.log.audit({event: "projects.file.stage",id:opts.id, file:opts.path}, opts.req);
|
||||
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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
unstageFile: async function(opts) {
|
||||
runtime.log.audit({event: "projects.file.unstage",id:opts.id, file:opts.path}, opts.req);
|
||||
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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
revertFile: async function(opts) {
|
||||
runtime.log.audit({event: "projects.file.revert",id:opts.id, file:opts.path}, opts.req);
|
||||
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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - the requested diff
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
getFileDiff: async 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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - a list of project remotes
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
getRemotes: async 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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
addRemote: async function(opts) {
|
||||
runtime.log.audit({event: "projects.remote.add",id:opts.id, remote:opts.remote.name}, opts.req);
|
||||
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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
removeRemote: async function(opts) {
|
||||
runtime.log.audit({event: "projects.remote.delete",id:opts.id, remote:opts.remote}, opts.req);
|
||||
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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
updateRemote: async function(opts) {
|
||||
runtime.log.audit({event: "projects.remote.update",id:opts.id, remote:opts.remote.name}, opts.req);
|
||||
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
|
||||
* @param {String} opts.remote - the remote to pull
|
||||
* @param {Boolean} opts.track - whether to track this remote
|
||||
* @param {Boolean} opts.allowUnrelatedHistories -
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
pull: async function(opts) {
|
||||
runtime.log.audit({event: "projects.pull",id:opts.id, remote: opts.remote, track:opts.track}, opts.req);
|
||||
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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof @node-red/runtime_projects
|
||||
*/
|
||||
push: async function(opts) {
|
||||
runtime.log.audit({event: "projects.push",id:opts.id, remote: opts.remote, track:opts.track}, opts.req);
|
||||
return runtime.storage.projects.push(opts.user, opts.id, opts.remote, opts.track);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
/**
|
||||
* 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.
|
||||
**/
|
||||
|
||||
/**
|
||||
* @mixin @node-red/runtime_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 property exists. Need to handle the case
|
||||
// where the existing property is a string/number/boolean
|
||||
// and is being replaced wholesale.
|
||||
var targetType = typeof target[keys[i]];
|
||||
if (targetType === 'string' || targetType === 'number' || targetType === 'boolean') {
|
||||
target[keys[i]] = value;
|
||||
} else {
|
||||
target[keys[i]] = extend(target[keys[i]],value);
|
||||
}
|
||||
} else {
|
||||
target[keys[i]] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
function getSSHKeyUsername(userObj) {
|
||||
var username = '__default';
|
||||
if ( userObj && userObj.username ) {
|
||||
username = userObj.username;
|
||||
}
|
||||
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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - the runtime settings
|
||||
* @memberof @node-red/runtime_settings
|
||||
*/
|
||||
getRuntimeSettings: async function(opts) {
|
||||
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];
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (!runtime.settings.disableEditor) {
|
||||
safeSettings.context = runtime.nodes.listContextStores();
|
||||
if (runtime.settings.editorTheme) {
|
||||
if (runtime.settings.editorTheme.codeEditor) {
|
||||
safeSettings.codeEditor = runtime.settings.editorTheme.codeEditor || {};
|
||||
safeSettings.codeEditor.lib = safeSettings.codeEditor.lib || "monaco";
|
||||
safeSettings.codeEditor.options = safeSettings.codeEditor.options || {};
|
||||
}
|
||||
if (runtime.settings.editorTheme.markdownEditor) {
|
||||
safeSettings.markdownEditor = runtime.settings.editorTheme.markdownEditor || {};
|
||||
safeSettings.markdownEditor.mermaid = safeSettings.markdownEditor.mermaid || { enabled: true };
|
||||
}
|
||||
if (runtime.settings.editorTheme.mermaid) {
|
||||
safeSettings.mermaid = runtime.settings.editorTheme.mermaid
|
||||
}
|
||||
}
|
||||
safeSettings.libraries = runtime.library.getLibraries();
|
||||
if (Array.isArray(runtime.settings.paletteCategories)) {
|
||||
safeSettings.paletteCategories = runtime.settings.paletteCategories;
|
||||
}
|
||||
|
||||
if (runtime.settings.flowFilePretty) {
|
||||
safeSettings.flowFilePretty = runtime.settings.flowFilePretty;
|
||||
}
|
||||
|
||||
if (runtime.settings.editorTheme && runtime.settings.editorTheme.palette) {
|
||||
if (runtime.settings.editorTheme.palette.upload === false || runtime.settings.editorTheme.palette.editable === false) {
|
||||
safeSettings.externalModules = {palette: { } }
|
||||
}
|
||||
if (runtime.settings.editorTheme.palette.upload === false) {
|
||||
safeSettings.externalModules.palette.allowUpload = false;
|
||||
}
|
||||
if (runtime.settings.editorTheme.palette.editable === false) {
|
||||
safeSettings.externalModules.palette.allowInstall = false;
|
||||
safeSettings.externalModules.palette.allowUpload = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (runtime.settings.externalModules) {
|
||||
safeSettings.externalModules = extend(safeSettings.externalModules||{},runtime.settings.externalModules);
|
||||
}
|
||||
|
||||
if (!runtime.nodes.installerEnabled()) {
|
||||
safeSettings.externalModules = safeSettings.externalModules || {};
|
||||
safeSettings.externalModules.palette = safeSettings.externalModules.palette || {};
|
||||
safeSettings.externalModules.palette.allowInstall = false;
|
||||
safeSettings.externalModules.palette.allowUpload = 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();
|
||||
|
||||
safeSettings.diagnostics = {
|
||||
//unless diagnostics.ui and diagnostics.enabled are explicitly false, they will default to true.
|
||||
enabled: (runtime.settings.diagnostics && runtime.settings.diagnostics.enabled === false) ? false : true,
|
||||
ui: (runtime.settings.diagnostics && runtime.settings.diagnostics.ui === false) ? false : true
|
||||
}
|
||||
if(safeSettings.diagnostics.enabled === false) {
|
||||
safeSettings.diagnostics.ui = false; // cannot have UI without endpoint
|
||||
}
|
||||
|
||||
safeSettings.telemetryEnabled = runtime.telemetry.isEnabled()
|
||||
|
||||
safeSettings.runtimeState = {
|
||||
//unless runtimeState.ui and runtimeState.enabled are explicitly true, they will default to false.
|
||||
enabled: !!runtime.settings.runtimeState && runtime.settings.runtimeState.enabled === true,
|
||||
ui: !!runtime.settings.runtimeState && runtime.settings.runtimeState.ui === true
|
||||
}
|
||||
if(safeSettings.runtimeState.enabled !== true) {
|
||||
safeSettings.runtimeState.ui = false; // cannot have UI without endpoint
|
||||
}
|
||||
|
||||
runtime.settings.exportNodeSettings(safeSettings);
|
||||
runtime.plugins.exportPluginSettings(safeSettings);
|
||||
}
|
||||
|
||||
return safeSettings;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets an individual user's settings object
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - the user settings
|
||||
* @memberof @node-red/runtime_settings
|
||||
*/
|
||||
getUserSettings: async function(opts) {
|
||||
var username;
|
||||
if (!opts.user || opts.user.anonymous) {
|
||||
username = '_';
|
||||
} else {
|
||||
username = opts.user.username;
|
||||
}
|
||||
return 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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - the user settings
|
||||
* @memberof @node-red/runtime_settings
|
||||
*/
|
||||
updateUserSettings: async function(opts) {
|
||||
var username;
|
||||
if (!opts.user || opts.user.anonymous) {
|
||||
username = '_';
|
||||
} else {
|
||||
username = opts.user.username;
|
||||
}
|
||||
var currentSettings = runtime.settings.getUserSettings(username)||{};
|
||||
currentSettings = extend(currentSettings, opts.settings);
|
||||
|
||||
try {
|
||||
if (currentSettings.hasOwnProperty("telemetryEnabled")) {
|
||||
// This is a global setting that is being set by the user. It should
|
||||
// not be stored per-user as it applies to the whole runtime.
|
||||
const telemetryEnabled = currentSettings.telemetryEnabled;
|
||||
delete currentSettings.telemetryEnabled;
|
||||
if (telemetryEnabled) {
|
||||
runtime.telemetry.enable()
|
||||
} else {
|
||||
runtime.telemetry.disable()
|
||||
}
|
||||
}
|
||||
return runtime.settings.setUserSettings(username, currentSettings).then(function() {
|
||||
runtime.log.audit({event: "settings.update",username:username}, opts.req);
|
||||
return;
|
||||
}).catch(function(err) {
|
||||
runtime.log.audit({event: "settings.update",username:username,error:err.code||"unexpected_error",message:err.toString()}, opts.req);
|
||||
err.status = 400;
|
||||
throw err;
|
||||
});
|
||||
} catch(err) {
|
||||
runtime.log.warn(runtime.log._("settings.user-not-available",{message:runtime.log._("settings.not-available")}));
|
||||
runtime.log.audit({event: "settings.update",username:username,error:err.code||"unexpected_error",message:err.toString()}, opts.req);
|
||||
err.status = 400;
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a list of a user's ssh keys
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - the user's ssh keys
|
||||
* @memberof @node-red/runtime_settings
|
||||
*/
|
||||
getUserKeys: async function(opts) {
|
||||
var username = getSSHKeyUsername(opts.user);
|
||||
return runtime.storage.projects.ssh.listSSHKeys(username).catch(function(err) {
|
||||
err.status = 400;
|
||||
throw err;
|
||||
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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<String>} - the user's ssh public key
|
||||
* @memberof @node-red/runtime_settings
|
||||
*/
|
||||
getUserKey: async function(opts) {
|
||||
var username = getSSHKeyUsername(opts.user);
|
||||
// console.log('username:', username);
|
||||
return runtime.storage.projects.ssh.getSSHKey(username, opts.id).then(function(data) {
|
||||
if (data) {
|
||||
return data;
|
||||
} else {
|
||||
var err = new Error("Key not found");
|
||||
err.code = "not_found";
|
||||
err.status = 404;
|
||||
throw err;
|
||||
}
|
||||
}).catch(function(err) {
|
||||
if (!err.status) {
|
||||
err.status = 400;
|
||||
}
|
||||
throw 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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<String>} - the id of the generated key
|
||||
* @memberof @node-red/runtime_settings
|
||||
*/
|
||||
generateUserKey: async function(opts) {
|
||||
var username = getSSHKeyUsername(opts.user);
|
||||
return runtime.storage.projects.ssh.generateSSHKey(username, opts).catch(function(err) {
|
||||
err.status = 400;
|
||||
throw 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
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise} - resolves when deleted
|
||||
* @memberof @node-red/runtime_settings
|
||||
*/
|
||||
removeUserKey: async function(opts) {
|
||||
var username = getSSHKeyUsername(opts.user);
|
||||
return runtime.storage.projects.ssh.deleteSSHKey(username, opts.id).catch(function(err) {
|
||||
err.status = 400;
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user