mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Merge branch 'dev' into 4219-missing-error-logging-for-config-nodes
This commit is contained in:
@@ -89,10 +89,16 @@ var api = module.exports = {
|
||||
|
||||
if (!runtime.settings.disableEditor) {
|
||||
safeSettings.context = runtime.nodes.listContextStores();
|
||||
if (runtime.settings.editorTheme && 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) {
|
||||
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 };
|
||||
}
|
||||
}
|
||||
safeSettings.libraries = runtime.library.getLibraries();
|
||||
if (util.isArray(runtime.settings.paletteCategories)) {
|
||||
|
@@ -416,23 +416,50 @@ class Flow {
|
||||
return this.activeNodes;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get value of environment variable defined in group node.
|
||||
* @param {String} group - group node
|
||||
* @param {String} name - name of variable
|
||||
* @return {Object} object containing the value in val property or null if not defined
|
||||
|
||||
/**
|
||||
* Group callback signature
|
||||
*
|
||||
* @callback GroupEnvCallback
|
||||
* @param {Error} err The error object (or null)
|
||||
* @param {[result: {val:Any}, name: String]} result The result of the callback
|
||||
* @returns {void}
|
||||
*/
|
||||
getGroupEnvSetting(node, group, name) {
|
||||
|
||||
/**
|
||||
* @function getGroupEnvSetting
|
||||
* Get a group setting value synchronously.
|
||||
* This currently automatically defers to the parent
|
||||
* @overload
|
||||
* @param {Object} node
|
||||
* @param {Object} group
|
||||
* @param {String} name
|
||||
* @returns {Any}
|
||||
*
|
||||
* Get a group setting value asynchronously.
|
||||
* @overload
|
||||
* @param {Object} node
|
||||
* @param {Object} group
|
||||
* @param {String} name
|
||||
* @param {GroupEnvCallback} callback
|
||||
* @returns {void}
|
||||
*/
|
||||
|
||||
getGroupEnvSetting(node, group, name, callback) {
|
||||
/** @type {GroupEnvCallback} */
|
||||
const returnOrCallback = (err, [result, newName]) => {
|
||||
if (callback) {
|
||||
callback(err, [result, newName]);
|
||||
return
|
||||
}
|
||||
return [result, newName];
|
||||
}
|
||||
if (group) {
|
||||
if (name === "NR_GROUP_NAME") {
|
||||
return [{
|
||||
val: group.name
|
||||
}, null];
|
||||
return returnOrCallback(null, [{ val: group.name }, null]);
|
||||
}
|
||||
if (name === "NR_GROUP_ID") {
|
||||
return [{
|
||||
val: group.id
|
||||
}, null];
|
||||
return returnOrCallback(null, [{ val: group.id }, null]);
|
||||
}
|
||||
|
||||
if (group.credentials === undefined) {
|
||||
@@ -457,33 +484,32 @@ class Flow {
|
||||
if (env) {
|
||||
let value = env.value;
|
||||
const type = env.type;
|
||||
if ((type !== "env") ||
|
||||
(value !== name)) {
|
||||
if ((type !== "env") || (value !== name)) {
|
||||
if (type === "env") {
|
||||
value = value.replace(new RegExp("\\${"+name+"}","g"),"${$parent."+name+"}");
|
||||
}
|
||||
if (type === "bool") {
|
||||
const val
|
||||
= ((value === "true") ||
|
||||
(value === true));
|
||||
return [{
|
||||
val: val
|
||||
}, null];
|
||||
} else if (type === "bool") {
|
||||
const val = ((value === "true") || (value === true));
|
||||
return returnOrCallback(null, [{ val: val }, null])
|
||||
}
|
||||
if (type === "cred") {
|
||||
return [{
|
||||
val: value
|
||||
}, null];
|
||||
return returnOrCallback(null, [{ val: value }, null])
|
||||
}
|
||||
try {
|
||||
var val = redUtil.evaluateNodeProperty(value, type, node, null, null);
|
||||
return [{
|
||||
val: val
|
||||
}, null];
|
||||
if (!callback) {
|
||||
var val = redUtil.evaluateNodeProperty(value, type, node, null, null);
|
||||
return [{ val: val }, null];
|
||||
} else {
|
||||
redUtil.evaluateNodeProperty(value, type, node, null, (err, value) => {
|
||||
return returnOrCallback(err, [{ val: value }, null])
|
||||
});
|
||||
return
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
this.error(e);
|
||||
return [null, null];
|
||||
if (!callback) {
|
||||
this.error(e);
|
||||
}
|
||||
return returnOrCallback(e, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -494,27 +520,47 @@ class Flow {
|
||||
}
|
||||
if (group.g) {
|
||||
const parent = this.getGroupNode(group.g);
|
||||
return this.getGroupEnvSetting(node, parent, name);
|
||||
const gVal = this.getGroupEnvSetting(node, parent, name, callback);
|
||||
if (callback) {
|
||||
return;
|
||||
}
|
||||
return gVal;
|
||||
}
|
||||
}
|
||||
return [null, name];
|
||||
return returnOrCallback(null, [null, name]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Settings callback signature
|
||||
*
|
||||
* @callback SettingsCallback
|
||||
* @param {Error} err The error object (or null)
|
||||
* @param {Any} result The result of the callback
|
||||
* @returns {void}
|
||||
*/
|
||||
/**
|
||||
* Get a flow setting value. This currently automatically defers to the parent
|
||||
* flow which, as defined in ./index.js returns `process.env[key]`.
|
||||
* This lays the groundwork for Subflow to have instance-specific settings
|
||||
* @param {[type]} key [description]
|
||||
* @return {[type]} [description]
|
||||
* @param {String} key The settings key
|
||||
* @param {SettingsCallback} callback Optional callback function
|
||||
* @return {Any}
|
||||
*/
|
||||
getSetting(key) {
|
||||
getSetting(key, callback) {
|
||||
/** @type {SettingsCallback} */
|
||||
const returnOrCallback = (err, result) => {
|
||||
if (callback) {
|
||||
callback(err, result);
|
||||
return
|
||||
}
|
||||
return result;
|
||||
}
|
||||
const flow = this.flow;
|
||||
if (key === "NR_FLOW_NAME") {
|
||||
return flow.label;
|
||||
return returnOrCallback(null, flow.label);
|
||||
}
|
||||
if (key === "NR_FLOW_ID") {
|
||||
return flow.id;
|
||||
return returnOrCallback(null, flow.id);
|
||||
}
|
||||
if (flow.credentials === undefined) {
|
||||
flow.credentials = credentials.get(flow.id) || {};
|
||||
@@ -544,15 +590,14 @@ class Flow {
|
||||
}
|
||||
try {
|
||||
if (type === "bool") {
|
||||
const val = ((value === "true") ||
|
||||
(value === true));
|
||||
return val;
|
||||
const val = ((value === "true") || (value === true));
|
||||
return returnOrCallback(null, val);
|
||||
}
|
||||
if (type === "cred") {
|
||||
return value;
|
||||
return returnOrCallback(null, value);
|
||||
}
|
||||
var val = redUtil.evaluateNodeProperty(value, type, null, null, null);
|
||||
return val;
|
||||
return returnOrCallback(null, val);
|
||||
}
|
||||
catch (e) {
|
||||
this.error(e);
|
||||
@@ -564,7 +609,11 @@ class Flow {
|
||||
key = key.substring(8);
|
||||
}
|
||||
}
|
||||
return this.parent.getSetting(key);
|
||||
const pVal = this.parent.getSetting(key, callback);
|
||||
if (callback) {
|
||||
return;
|
||||
}
|
||||
return pVal;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -605,10 +654,36 @@ class Flow {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.statusNodes.forEach(function(targetStatusNode) {
|
||||
if (targetStatusNode.scope && targetStatusNode.scope.indexOf(reportingNode.id) === -1) {
|
||||
const candidateNodes = [];
|
||||
this.statusNodes.forEach(targetStatusNode => {
|
||||
if (targetStatusNode.g && targetStatusNode.scope === 'group' && !reportingNode.g) {
|
||||
// Status node inside a group, reporting node not in a group - skip it
|
||||
return
|
||||
}
|
||||
if (Array.isArray(targetStatusNode.scope) && targetStatusNode.scope.indexOf(reportingNode.id) === -1) {
|
||||
return;
|
||||
}
|
||||
let distance = 0
|
||||
if (reportingNode.g) {
|
||||
// Reporting node inside a group. Calculate the distance between it and the status node
|
||||
let containingGroup = this.global.groups[reportingNode.g]
|
||||
while (containingGroup && containingGroup.id !== targetStatusNode.g) {
|
||||
distance++
|
||||
containingGroup = this.global.groups[containingGroup.g]
|
||||
}
|
||||
if (!containingGroup && targetStatusNode.g && targetStatusNode.scope === 'group') {
|
||||
// This status node is in a group, but not in the same hierachy
|
||||
// the reporting node is in
|
||||
return
|
||||
}
|
||||
}
|
||||
candidateNodes.push({ d: distance, n: targetStatusNode })
|
||||
})
|
||||
candidateNodes.sort((A,B) => {
|
||||
return A.d - B.d
|
||||
})
|
||||
candidateNodes.forEach(candidate => {
|
||||
const targetStatusNode = candidate.n
|
||||
var message = {
|
||||
status: clone(statusMessage)
|
||||
}
|
||||
@@ -665,21 +740,46 @@ class Flow {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var handledByUncaught = false;
|
||||
|
||||
this.catchNodes.forEach(function(targetCatchNode) {
|
||||
if (targetCatchNode.scope && targetCatchNode.scope.indexOf(reportingNode.id) === -1) {
|
||||
const candidateNodes = [];
|
||||
this.catchNodes.forEach(targetCatchNode => {
|
||||
if (targetCatchNode.g && targetCatchNode.scope === 'group' && !reportingNode.g) {
|
||||
// Catch node inside a group, reporting node not in a group - skip it
|
||||
return
|
||||
}
|
||||
if (Array.isArray(targetCatchNode.scope) && targetCatchNode.scope.indexOf(reportingNode.id) === -1) {
|
||||
// Catch node has a scope set and it doesn't include the reporting node
|
||||
return;
|
||||
}
|
||||
if (!targetCatchNode.scope && targetCatchNode.uncaught && !handledByUncaught) {
|
||||
if (handled) {
|
||||
// This has been handled by a !uncaught catch node
|
||||
return;
|
||||
let distance = 0
|
||||
if (reportingNode.g) {
|
||||
// Reporting node inside a group. Calculate the distance between it and the catch node
|
||||
let containingGroup = this.global.groups[reportingNode.g]
|
||||
while (containingGroup && containingGroup.id !== targetCatchNode.g) {
|
||||
distance++
|
||||
containingGroup = this.global.groups[containingGroup.g]
|
||||
}
|
||||
if (!containingGroup && targetCatchNode.g && targetCatchNode.scope === 'group') {
|
||||
// This catch node is in a group, but not in the same hierachy
|
||||
// the reporting node is in
|
||||
return
|
||||
}
|
||||
// This is an uncaught error
|
||||
handledByUncaught = true;
|
||||
}
|
||||
var errorMessage;
|
||||
candidateNodes.push({ d: distance, n: targetCatchNode })
|
||||
})
|
||||
candidateNodes.sort((A,B) => {
|
||||
return A.d - B.d
|
||||
})
|
||||
let handledByUncaught = false
|
||||
candidateNodes.forEach(candidate => {
|
||||
const targetCatchNode = candidate.n
|
||||
if (targetCatchNode.uncaught && !handledByUncaught) {
|
||||
// This node only wants errors that haven't already been handled
|
||||
if (handled) {
|
||||
return
|
||||
}
|
||||
handledByUncaught = true
|
||||
}
|
||||
let errorMessage;
|
||||
if (msg) {
|
||||
errorMessage = redUtil.cloneMessage(msg);
|
||||
} else {
|
||||
|
@@ -474,7 +474,7 @@ class Subflow extends Flow {
|
||||
*/
|
||||
function createNodeInSubflow(subflowInstanceId, def) {
|
||||
let node = clone(def);
|
||||
let nid = redUtil.generateId();
|
||||
let nid = `${subflowInstanceId}-${node.id}` //redUtil.generateId();
|
||||
// console.log("Create Node In subflow",node._alias, "--->",nid, "(",node.type,")")
|
||||
// node_map[node.id] = node;
|
||||
node._alias = node.id;
|
||||
|
@@ -780,11 +780,21 @@ const flowAPI = {
|
||||
getNode: getNode,
|
||||
handleError: () => false,
|
||||
handleStatus: () => false,
|
||||
getSetting: k => flowUtil.getEnvVar(k),
|
||||
getSetting: (k, callback) => flowUtil.getEnvVar(k, callback),
|
||||
log: m => log.log(m)
|
||||
}
|
||||
|
||||
|
||||
function getGlobalConfig() {
|
||||
let gconf = null;
|
||||
eachNode((n) => {
|
||||
if (n.type === "global-config") {
|
||||
gconf = n;
|
||||
}
|
||||
});
|
||||
return gconf;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: init,
|
||||
|
||||
@@ -798,6 +808,9 @@ module.exports = {
|
||||
get:getNode,
|
||||
eachNode: eachNode,
|
||||
|
||||
|
||||
getGlobalConfig: getGlobalConfig,
|
||||
|
||||
/**
|
||||
* Gets the current flow configuration
|
||||
*/
|
||||
|
@@ -18,7 +18,9 @@ var redUtil = require("@node-red/util").util;
|
||||
var Log = require("@node-red/util").log;
|
||||
var subflowInstanceRE = /^subflow:(.+)$/;
|
||||
var typeRegistry = require("@node-red/registry");
|
||||
const credentials = require("../nodes/credentials");
|
||||
|
||||
let _runtime = null;
|
||||
|
||||
var envVarExcludes = {};
|
||||
|
||||
@@ -267,15 +269,73 @@ function parseConfig(config) {
|
||||
return flow;
|
||||
}
|
||||
|
||||
function getGlobalEnv(name) {
|
||||
const nodes = _runtime.nodes;
|
||||
if (!nodes) {
|
||||
return null;
|
||||
}
|
||||
const gconf = nodes.getGlobalConfig();
|
||||
const env = gconf ? gconf.env : null;
|
||||
|
||||
if (env) {
|
||||
const cred = (gconf ? credentials.get(gconf.id) : null) || {
|
||||
map: {}
|
||||
};
|
||||
const map = cred.map;
|
||||
|
||||
for (let i = 0; i < env.length; i++) {
|
||||
const item = env[i];
|
||||
if (item.name === name) {
|
||||
if (item.type === "cred") {
|
||||
return {
|
||||
name: name,
|
||||
value: map[name],
|
||||
type: "cred"
|
||||
};
|
||||
}
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function(runtime) {
|
||||
_runtime = runtime;
|
||||
envVarExcludes = {};
|
||||
if (runtime.settings.hasOwnProperty('envVarExcludes') && Array.isArray(runtime.settings.envVarExcludes)) {
|
||||
runtime.settings.envVarExcludes.forEach(v => envVarExcludes[v] = true);
|
||||
}
|
||||
},
|
||||
getEnvVar: function(k) {
|
||||
return !envVarExcludes[k]?process.env[k]:undefined
|
||||
/**
|
||||
* Get the value of an environment variable
|
||||
* Call with a callback to get the value asynchronously
|
||||
* or without to get the value synchronously
|
||||
* @param {String} key The name of the environment variable
|
||||
* @param {(err: Error, val: Any)} [callback] Optional callback for asynchronous call
|
||||
* @returns {Any | void} The value of the environment variable or undefined if not found
|
||||
*/
|
||||
getEnvVar: function(key, callback) {
|
||||
const returnOrCallback = function(err, val) {
|
||||
if (callback) {
|
||||
callback(err, val);
|
||||
return;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
if (!envVarExcludes[key]) {
|
||||
const item = getGlobalEnv(key);
|
||||
if (item) {
|
||||
const val = redUtil.evaluateNodeProperty(item.value, item.type, null, null, callback);
|
||||
if (callback) {
|
||||
return;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
return returnOrCallback(null, process.env[key]);
|
||||
}
|
||||
return returnOrCallback(undefined);
|
||||
},
|
||||
diffNodes: diffNodes,
|
||||
mapEnvVarProperties: mapEnvVarProperties,
|
||||
|
@@ -89,6 +89,15 @@ function init(userSettings,httpServer,_adminApi) {
|
||||
|
||||
nodeApp = express();
|
||||
adminApp = express();
|
||||
const defaultServerSettings = {
|
||||
"x-powered-by": false
|
||||
}
|
||||
const serverSettings = Object.assign({},defaultServerSettings,userSettings.httpServerOptions||{});
|
||||
for (let eOption in serverSettings) {
|
||||
nodeApp.set(eOption, serverSettings[eOption]);
|
||||
adminApp.set(eOption, serverSettings[eOption]);
|
||||
}
|
||||
|
||||
|
||||
if (_adminApi) {
|
||||
adminApi = _adminApi;
|
||||
|
@@ -589,17 +589,28 @@ function deleteContext(id,flowId) {
|
||||
* If flowConfig is undefined, all flow/node contexts will be removed
|
||||
**/
|
||||
function clean(flowConfig) {
|
||||
flowConfig = flowConfig || { allNodes: {} };
|
||||
var promises = [];
|
||||
for(var plugin in stores){
|
||||
if(stores.hasOwnProperty(plugin)){
|
||||
promises.push(stores[plugin].clean(Object.keys(flowConfig.allNodes)));
|
||||
}
|
||||
flowConfig = flowConfig || { allNodes: {}, subflows: {} };
|
||||
const knownNodes = new Set(Object.keys(flowConfig.allNodes))
|
||||
|
||||
// We need to alias all of the subflow instance contents
|
||||
for (const subflow of Object.values(flowConfig.subflows || {})) {
|
||||
subflow.instances.forEach(instance => {
|
||||
for (const nodeId of Object.keys(subflow.nodes || {})) {
|
||||
knownNodes.add(`${instance.id}-${nodeId}`)
|
||||
}
|
||||
for (const nodeId of Object.keys(subflow.configs || {})) {
|
||||
knownNodes.add(`${instance.id}-${nodeId}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
for (var id in contexts) {
|
||||
if (contexts.hasOwnProperty(id) && id !== "global") {
|
||||
var promises = [];
|
||||
for (const store of Object.values(stores)){
|
||||
promises.push(store.clean(Array.from(knownNodes)));
|
||||
}
|
||||
for (const id of Object.keys(contexts)) {
|
||||
if (id !== "global") {
|
||||
var idParts = id.split(":");
|
||||
if (!flowConfig.allNodes.hasOwnProperty(idParts[0])) {
|
||||
if (!knownNodes.has(idParts[0])) {
|
||||
delete contexts[id];
|
||||
}
|
||||
}
|
||||
|
@@ -383,6 +383,11 @@ var api = module.exports = {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (nodeType === "global-config") {
|
||||
if (JSON.stringify(savedCredentials.map) !== JSON.stringify(newCreds.map)) {
|
||||
savedCredentials.map = newCreds.map;
|
||||
dirty = true;
|
||||
}
|
||||
} else {
|
||||
var dashedType = nodeType.replace(/\s+/g, '-');
|
||||
var definition = credentialsDef[dashedType];
|
||||
|
@@ -205,6 +205,7 @@ module.exports = {
|
||||
getNode: flows.get,
|
||||
eachNode: flows.eachNode,
|
||||
getContext: context.get,
|
||||
getGlobalConfig: flows.getGlobalConfig,
|
||||
|
||||
clearContext: context.clear,
|
||||
|
||||
|
Reference in New Issue
Block a user