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 1cc571200..fe5120302 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 @@ -1204,5 +1204,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 fb3458eed..2b438050e 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 @@ -1349,5 +1349,10 @@ "show-version-control-tab": "バージョンコントロールタブを表示", "start-flows": "フローを開始", "stop-flows": "フローを停止" + }, + "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/deploy.js b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js index 8a8df6837..254ade55f 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js @@ -421,7 +421,7 @@ RED.deploy = (function() { const unusedConfigNodes = []; RED.nodes.eachConfig(function (node) { - if ((node._def.hasUsers !== false) && (node.users.length === 0)) { + if ((node._def.hasUsers !== false) && (node.users.length === 0) && (node.type !== "global-config")) { unusedConfigNodes.push(getNodeInfo(node)); hasUnusedConfig = true; } 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..a8f852307 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/env-var.js @@ -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. + **/ + +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" + }); + 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 = $("This node holds global configuration of flows.
Do not delete this node unless you understand underlying mechanism.
" } } 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..6d0602f86 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/ja/common/91-global-config.html @@ -0,0 +1,19 @@ + + +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 6e16daa6f..c5773972d 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ja/messages.json @@ -1123,5 +1123,8 @@ "warn": { "nonumber": "ペイロードに数値が含まれていません" } + }, + "global-config": { + "tip": "このノードはフローの大域的な設定を保持するために利用されます。
実装の詳細を理解していない場合、このノードの削除は推奨しません。
" } } 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..c9e7a78e6 --- /dev/null +++ b/test/nodes/core/common/91-global-config_spec.js @@ -0,0 +1,63 @@ +/** + * 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 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({}); + }); + }); + +});