mirror of
https://github.com/node-red/node-red
synced 2024-11-21 23:48:30 +00:00
Merge pull request #3941 from node-red-hitachi/global-env-var
add global environment variable feature
This commit is contained in:
commit
928131cf08
@ -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",
|
||||
|
@ -1209,5 +1209,10 @@
|
||||
"node": "Node",
|
||||
"junction": "Junction",
|
||||
"linkNodes": "Link Nodes"
|
||||
},
|
||||
"env-var": {
|
||||
"environment": "Environment",
|
||||
"header": "Global Environment Variables",
|
||||
"revert": "Revert"
|
||||
}
|
||||
}
|
||||
|
@ -1356,5 +1356,10 @@
|
||||
"stop-flows": "フローを停止",
|
||||
"copy-item-url": "要素のURLをコピー",
|
||||
"copy-item-edit-url": "要素の編集URLをコピー"
|
||||
},
|
||||
"env-var": {
|
||||
"environment": "環境変数",
|
||||
"header": "大域環境変数",
|
||||
"revert": "破棄"
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
175
packages/node_modules/@node-red/editor-client/src/js/ui/env-var.js
vendored
Normal file
175
packages/node_modules/@node-red/editor-client/src/js/ui/env-var.js
vendored
Normal file
@ -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 = $("<div/>", {
|
||||
id: "red-ui-settings-tab-envvar",
|
||||
class: "form-horizontal"
|
||||
});
|
||||
var content = $("<div/>", {
|
||||
class: "form-row node-input-env-container-row"
|
||||
}).css({
|
||||
"margin": "10px"
|
||||
}).appendTo(pane);
|
||||
|
||||
var label = $("<label></label>").css({
|
||||
width: "100%"
|
||||
}).appendTo(content);
|
||||
$("<i/>", {
|
||||
class: "fa fa-list"
|
||||
}).appendTo(label);
|
||||
$("<span/>").text(" "+RED._("env-var.header")).appendTo(label);
|
||||
|
||||
var list = $("<ol/>", {
|
||||
id: "node-input-env-container"
|
||||
}).appendTo(content);
|
||||
var node = {
|
||||
type: "",
|
||||
env: env,
|
||||
credentials: cred.map,
|
||||
};
|
||||
RED.editor.envVarList.create(list, node);
|
||||
|
||||
var buttons = $("<div/>").css({
|
||||
"text-align": "right",
|
||||
}).appendTo(content);
|
||||
var revertButton = $("<button/>", {
|
||||
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,
|
||||
};
|
||||
|
||||
})();
|
@ -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();
|
||||
|
27
packages/node_modules/@node-red/nodes/core/common/91-global-config.html
vendored
Normal file
27
packages/node_modules/@node-red/nodes/core/common/91-global-config.html
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
<script type="text/html" data-template-name="global-config">
|
||||
<div class="form-row">
|
||||
<label style="width: 100%"><span data-i18n="global-config.label.open-conf"></span>:</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<button class="red-ui-button" type="button" id="node-input-edit-env-var" data-i18n="editor:env-var.header" style="margin-left: 20px"></button>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('global-config',{
|
||||
category: 'config',
|
||||
defaults: {
|
||||
name: { value: "" },
|
||||
env: { value: [] },
|
||||
},
|
||||
credentials: {
|
||||
map: { type: "map" }
|
||||
},
|
||||
oneditprepare: function() {
|
||||
$('#node-input-edit-env-var').on('click', function(evt) {
|
||||
RED.actions.invoke('core:show-user-settings', 'envvar')
|
||||
});
|
||||
},
|
||||
hasUsers: false
|
||||
});
|
||||
</script>
|
7
packages/node_modules/@node-red/nodes/core/common/91-global-config.js
vendored
Normal file
7
packages/node_modules/@node-red/nodes/core/common/91-global-config.js
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
function GlobalConfigNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
}
|
||||
RED.nodes.registerType("global-config", GlobalConfigNode);
|
||||
}
|
3
packages/node_modules/@node-red/nodes/locales/en-US/common/91-global-config.html
vendored
Normal file
3
packages/node_modules/@node-red/nodes/locales/en-US/common/91-global-config.html
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
<script type="text/html" data-help-name="global-config">
|
||||
<p>A node for holding global configuration of flows.</p>
|
||||
</script>
|
@ -1124,5 +1124,10 @@
|
||||
"warn": {
|
||||
"nonumber": "no number found in payload"
|
||||
}
|
||||
},
|
||||
"global-config": {
|
||||
"label": {
|
||||
"open-conf": "Open Configuration"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
3
packages/node_modules/@node-red/nodes/locales/ja/common/91-global-config.html
vendored
Normal file
3
packages/node_modules/@node-red/nodes/locales/ja/common/91-global-config.html
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
<script type="text/html" data-help-name="global-config">
|
||||
<p>大域的なフローの設定を保持するノード。大域的な環境変数の定義を含みます。</p>
|
||||
</script>p
|
@ -1124,5 +1124,10 @@
|
||||
"warn": {
|
||||
"nonumber": "ペイロードに数値が含まれていません"
|
||||
}
|
||||
},
|
||||
"global-config": {
|
||||
"label": {
|
||||
"open-conf": "設定を開く"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
||||
|
47
test/nodes/core/common/91-global-config_spec.js
Normal file
47
test/nodes/core/common/91-global-config_spec.js
Normal file
@ -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({});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
Loading…
Reference in New Issue
Block a user