This commit is contained in:
gongwenxin
2025-08-28 16:20:24 +08:00
parent 61e533cddf
commit 73e887ebf4
1539 changed files with 360926 additions and 0 deletions
+190
View File
@@ -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);
}
})
},
}
+336
View File
@@ -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;
});
}
}
+492
View File
@@ -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;
});
}
}