diff --git a/Gruntfile.js b/Gruntfile.js
index 2f81da923..4f4b3b027 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -169,6 +169,7 @@ module.exports = function(grunt) {
"packages/node_modules/@node-red/editor-client/src/js/ui/diagnostics.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/diff.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js",
+ "packages/node_modules/@node-red/editor-client/src/js/ui/env-var.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/statusBar.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/view.js",
diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json
index 8de24c3c9..9ffde4df0 100755
--- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json
+++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json
@@ -1209,5 +1209,10 @@
"node": "Node",
"junction": "Junction",
"linkNodes": "Link Nodes"
+ },
+ "env-var": {
+ "environment": "Environment",
+ "header": "Global Environment Variables",
+ "revert": "Revert"
}
}
diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json
index d515eb557..5bd7de5a4 100644
--- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json
+++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json
@@ -1358,5 +1358,10 @@
"copy-item-edit-url": "要素の編集URLをコピー",
"move-flow-to-start": "フローを先頭に移動",
"move-flow-to-end": "フローを末尾に移動"
+ },
+ "env-var": {
+ "environment": "環境変数",
+ "header": "大域環境変数",
+ "revert": "破棄"
}
}
diff --git a/packages/node_modules/@node-red/editor-client/src/js/red.js b/packages/node_modules/@node-red/editor-client/src/js/red.js
index 129c46799..6c0adf21b 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/red.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/red.js
@@ -775,6 +775,7 @@ var RED = (function() {
RED.deploy.init(RED.settings.theme("deployButton",null));
RED.keyboard.init(buildMainMenu);
+ RED.envVar.init();
RED.nodes.init();
RED.runtime.init()
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/env-var.js b/packages/node_modules/@node-red/editor-client/src/js/ui/env-var.js
new file mode 100644
index 000000000..ab071a1d4
--- /dev/null
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/env-var.js
@@ -0,0 +1,175 @@
+RED.envVar = (function() {
+ function saveEnvList(list) {
+ const items = list.editableList("items")
+ const new_env = [];
+ items.each(function (i,el) {
+ var data = el.data('data');
+ var item;
+ if (data.nameField && data.valueField) {
+ item = {
+ name: data.nameField.val(),
+ value: data.valueField.typedInput("value"),
+ type: data.valueField.typedInput("type")
+ };
+ new_env.push(item);
+ }
+ });
+ return new_env;
+ }
+
+ function getGlobalConf(create) {
+ var gconf = null;
+ RED.nodes.eachConfig(function (conf) {
+ if (conf.type === "global-config") {
+ gconf = conf;
+ }
+ });
+ if ((gconf === null) && create) {
+ var cred = {
+ _ : {},
+ map: {}
+ };
+ gconf = {
+ id: RED.nodes.id(),
+ type: "global-config",
+ env: [],
+ name: "global-config",
+ label: "",
+ hasUsers: false,
+ users: [],
+ credentials: cred,
+ _def: RED.nodes.getType("global-config"),
+ };
+ RED.nodes.add(gconf);
+ }
+ return gconf;
+ }
+
+ function applyChanges(list) {
+ var gconf = getGlobalConf(false);
+ var new_env = [];
+ var items = list.editableList('items');
+ var credentials = gconf ? gconf.credentials : null;
+
+ if (!credentials) {
+ credentials = {
+ _ : {},
+ map: {}
+ };
+ }
+ items.each(function (i,el) {
+ var data = el.data('data');
+ if (data.nameField && data.valueField) {
+ var item = {
+ name: data.nameField.val(),
+ value: data.valueField.typedInput("value"),
+ type: data.valueField.typedInput("type")
+ };
+ if (item.name.trim() !== "") {
+ new_env.push(item);
+ if ((item.type === "cred") && (item.value !== "__PWRD__")) {
+ credentials.map[item.name] = item.value;
+ credentials.map["has_"+item.name] = (item.value !== "");
+ item.value = "__PWRD__";
+ }
+ }
+ }
+ });
+ if (gconf === null) {
+ gconf = getGlobalConf(true);
+ }
+ if ((JSON.stringify(new_env) !== JSON.stringify(gconf.env)) ||
+ (JSON.stringify(credentials) !== JSON.stringify(gconf.credentials))) {
+ gconf.env = new_env;
+ gconf.credentials = credentials;
+ RED.nodes.dirty(true);
+ }
+ }
+
+ function getSettingsPane() {
+ var gconf = getGlobalConf(false);
+ var env = gconf ? gconf.env : [];
+ var cred = gconf ? gconf.credentials : null;
+ if (!cred) {
+ cred = {
+ _ : {},
+ map: {}
+ };
+ }
+
+ var pane = $("
", {
+ id: "red-ui-settings-tab-envvar",
+ class: "form-horizontal"
+ });
+ var content = $("", {
+ class: "form-row node-input-env-container-row"
+ }).css({
+ "margin": "10px"
+ }).appendTo(pane);
+
+ var label = $("").css({
+ width: "100%"
+ }).appendTo(content);
+ $("", {
+ class: "fa fa-list"
+ }).appendTo(label);
+ $("").text(" "+RED._("env-var.header")).appendTo(label);
+
+ var list = $("
", {
+ id: "node-input-env-container"
+ }).appendTo(content);
+ var node = {
+ type: "",
+ env: env,
+ credentials: cred.map,
+ };
+ RED.editor.envVarList.create(list, node);
+
+ var buttons = $("").css({
+ "text-align": "right",
+ }).appendTo(content);
+ var revertButton = $("", {
+ class: "red-ui-button"
+ }).css({
+ }).text(RED._("env-var.revert")).appendTo(buttons);
+
+ var items = saveEnvList(list);
+ revertButton.on("click", function (ev) {
+ list.editableList("empty");
+ list.editableList("addItems", items);
+ });
+
+ return pane;
+ }
+
+ function init(done) {
+ if (!RED.user.hasPermission("settings.write")) {
+ RED.notify(RED._("user.errors.settings"),"error");
+ return;
+ }
+ RED.userSettings.add({
+ id:'envvar',
+ title: RED._("env-var.environment"),
+ get: getSettingsPane,
+ focus: function() {
+ var height = $("#red-ui-settings-tab-envvar").parent().height();
+ $("#node-input-env-container").editableList("height", (height -100));
+ },
+ close: function() {
+ var list = $("#node-input-env-container");
+ try {
+ applyChanges(list);
+ }
+ catch (e) {
+ console.log(e);
+ console.log(e.stack);
+ }
+ }
+ });
+ }
+
+ return {
+ init: init,
+ };
+
+})();
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js
index 83215afe4..dafcddda7 100755
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js
@@ -5604,7 +5604,24 @@ RED.view = (function() {
if (activeSubflow) {
activeSubflowChanged = activeSubflow.changed;
}
- var result = RED.nodes.import(nodesToImport,{generateIds:options.generateIds, addFlow: addNewFlow, importMap: options.importMap});
+ var filteredNodesToImport = nodesToImport;
+ var globalConfig = null;
+ var gconf = null;
+
+ RED.nodes.eachConfig(function (conf) {
+ if (conf.type === "global-config") {
+ gconf = conf;
+ }
+ });
+ if (gconf) {
+ filteredNodesToImport = nodesToImport.filter(function (n) {
+ return (n.type !== "global-config");
+ });
+ globalConfig = nodesToImport.find(function (n) {
+ return (n.type === "global-config");
+ });
+ }
+ var result = RED.nodes.import(filteredNodesToImport,{generateIds:options.generateIds, addFlow: addNewFlow, importMap: options.importMap});
if (result) {
var new_nodes = result.nodes;
var new_links = result.links;
@@ -5736,6 +5753,50 @@ RED.view = (function() {
}
}
+ if (globalConfig) {
+ // merge global env to existing global-config
+ var env0 = gconf.env;
+ var env1 = globalConfig.env;
+ var newEnv = Array.from(env0);
+ var changed = false;
+
+ env1.forEach(function (item1) {
+ var index = newEnv.findIndex(function (item0) {
+ return (item0.name === item1.name);
+ });
+ if (index >= 0) {
+ var item0 = newEnv[index];
+ if ((item0.type !== item1.type) ||
+ (item0.value !== item1.value)) {
+ newEnv[index] = item1;
+ changed = true;
+ }
+ }
+ else {
+ newEnv.push(item1);
+ changed = true;
+ }
+ });
+ if(changed) {
+ gconf.env = newEnv;
+ var replaceEvent = {
+ t: "edit",
+ node: gconf,
+ changed: true,
+ changes: {
+ env: env0
+ }
+ };
+ historyEvent = {
+ t:"multi",
+ events: [
+ replaceEvent,
+ historyEvent,
+ ]
+ };
+ }
+ }
+
RED.history.push(historyEvent);
updateActiveNodes();
diff --git a/packages/node_modules/@node-red/nodes/core/common/91-global-config.html b/packages/node_modules/@node-red/nodes/core/common/91-global-config.html
new file mode 100644
index 000000000..3240d315d
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/core/common/91-global-config.html
@@ -0,0 +1,27 @@
+
+
+
diff --git a/packages/node_modules/@node-red/nodes/core/common/91-global-config.js b/packages/node_modules/@node-red/nodes/core/common/91-global-config.js
new file mode 100644
index 000000000..08aa23207
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/core/common/91-global-config.js
@@ -0,0 +1,7 @@
+module.exports = function(RED) {
+ "use strict";
+ function GlobalConfigNode(n) {
+ RED.nodes.createNode(this,n);
+ }
+ RED.nodes.registerType("global-config", GlobalConfigNode);
+}
diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/common/91-global-config.html b/packages/node_modules/@node-red/nodes/locales/en-US/common/91-global-config.html
new file mode 100644
index 000000000..0a3021762
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/en-US/common/91-global-config.html
@@ -0,0 +1,3 @@
+
diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json
index 8b66bf5e9..3ba344ae0 100644
--- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json
+++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json
@@ -1124,5 +1124,10 @@
"warn": {
"nonumber": "no number found in payload"
}
+ },
+ "global-config": {
+ "label": {
+ "open-conf": "Open Configuration"
+ }
}
}
diff --git a/packages/node_modules/@node-red/nodes/locales/ja/common/91-global-config.html b/packages/node_modules/@node-red/nodes/locales/ja/common/91-global-config.html
new file mode 100644
index 000000000..2eda840ec
--- /dev/null
+++ b/packages/node_modules/@node-red/nodes/locales/ja/common/91-global-config.html
@@ -0,0 +1,3 @@
+p
diff --git a/packages/node_modules/@node-red/nodes/locales/ja/messages.json b/packages/node_modules/@node-red/nodes/locales/ja/messages.json
index cc4a07b4e..2365f7f7a 100644
--- a/packages/node_modules/@node-red/nodes/locales/ja/messages.json
+++ b/packages/node_modules/@node-red/nodes/locales/ja/messages.json
@@ -1124,5 +1124,10 @@
"warn": {
"nonumber": "ペイロードに数値が含まれていません"
}
+ },
+ "global-config": {
+ "label": {
+ "open-conf": "設定を開く"
+ }
}
}
diff --git a/packages/node_modules/@node-red/runtime/lib/flows/index.js b/packages/node_modules/@node-red/runtime/lib/flows/index.js
index 1b5476a3f..a583540f1 100644
--- a/packages/node_modules/@node-red/runtime/lib/flows/index.js
+++ b/packages/node_modules/@node-red/runtime/lib/flows/index.js
@@ -777,6 +777,16 @@ const flowAPI = {
}
+function getGlobalConfig() {
+ let gconf = null;
+ eachNode((n) => {
+ if (n.type === "global-config") {
+ gconf = n;
+ }
+ });
+ return gconf;
+}
+
module.exports = {
init: init,
@@ -790,6 +800,9 @@ module.exports = {
get:getNode,
eachNode: eachNode,
+
+ getGlobalConfig: getGlobalConfig,
+
/**
* Gets the current flow configuration
*/
diff --git a/packages/node_modules/@node-red/runtime/lib/flows/util.js b/packages/node_modules/@node-red/runtime/lib/flows/util.js
index 0f3435b77..e350cc28c 100644
--- a/packages/node_modules/@node-red/runtime/lib/flows/util.js
+++ b/packages/node_modules/@node-red/runtime/lib/flows/util.js
@@ -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 = {};
@@ -263,15 +265,55 @@ 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
+ if (!envVarExcludes[k]) {
+ const item = getGlobalEnv(k);
+ if (item) {
+ const val = redUtil.evaluateNodeProperty(item.value, item.type, null, null, null);
+ return val;
+ }
+ return process.env[k];
+ }
+ return undefined;
},
diffNodes: diffNodes,
mapEnvVarProperties: mapEnvVarProperties,
diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js b/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js
index 305594c85..73567f7c4 100644
--- a/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js
+++ b/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js
@@ -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];
diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/index.js b/packages/node_modules/@node-red/runtime/lib/nodes/index.js
index 5b859a5f8..fd01ebdb0 100644
--- a/packages/node_modules/@node-red/runtime/lib/nodes/index.js
+++ b/packages/node_modules/@node-red/runtime/lib/nodes/index.js
@@ -205,6 +205,7 @@ module.exports = {
getNode: flows.get,
eachNode: flows.eachNode,
getContext: context.get,
+ getGlobalConfig: flows.getGlobalConfig,
clearContext: context.clear,
diff --git a/test/nodes/core/common/91-global-config_spec.js b/test/nodes/core/common/91-global-config_spec.js
new file mode 100644
index 000000000..8cc5658cf
--- /dev/null
+++ b/test/nodes/core/common/91-global-config_spec.js
@@ -0,0 +1,47 @@
+var should = require("should");
+var config = require("nr-test-utils").require("@node-red/nodes/core/common/91-global-config.js");
+var inject = require("nr-test-utils").require("@node-red/nodes/core/common/20-inject.js");
+var helper = require("node-red-node-test-helper");
+
+describe('unknown Node', function() {
+
+ afterEach(function() {
+ helper.unload();
+ });
+
+ it('should be loaded', function(done) {
+ var flow = [{id:"n1", type:"global-config", name: "XYZ" }];
+ helper.load(config, flow, function() {
+ var n1 = helper.getNode("n1");
+ n1.should.have.property("name", "XYZ");
+ done();
+ });
+ });
+
+ it('should access global environment variable', function(done) {
+ var flow = [{id:"n1", type:"global-config", name: "XYZ",
+ env: [ {
+ name: "X",
+ type: "string",
+ value: "foo"
+ }]
+ },
+ {id: "n2", type: "inject", topic: "t1", payload: "X", payloadType: "env", wires: [["n3"]], z: "flow"},
+ {id: "n3", type: "helper"}
+ ];
+ helper.load([config, inject], flow, function() {
+ var n2 = helper.getNode("n2");
+ var n3 = helper.getNode("n3");
+ n3.on("input", (msg) => {
+ try {
+ msg.should.have.property("payload", "foo");
+ done();
+ } catch (err) {
+ done(err);
+ }
+ });
+ n2.receive({});
+ });
+ });
+
+});