').appendTo(container);
+ content.css("padding","8px 5px")
+ var min = ui.opts.min;
+ var max = ui.opts.max;
+ var minInput = $('
');
+ minInput.val(min);
+ var maxInput = $('
');
+ maxInput.val(max);
+ $('
').append(minInput).appendTo(content);
+ $('
').append(maxInput).appendTo(content);
+ return {
+ onclose: function() {
+ var min = minInput.val().trim();
+ var max = maxInput.val().trim();
+ if (min !== "") {
+ ui.opts.min = parseInt(min);
+ } else {
+ delete ui.opts.min;
+ }
+ if (max !== "") {
+ ui.opts.max = parseInt(max);
+ } else {
+ delete ui.opts.max;
+ }
+ inputCellInput.typedInput('value',Date.now())
+ }
+ }
+ }
+ }
+ },
+ {
+ value:"none",
+ label:RED._("editor.inputs.none"), icon:"fa fa-times",hasValue:false
+ },
+ {
+ value:"hide",
+ label:RED._("editor.inputs.hidden"), icon:"fa fa-ban",hasValue:false
+ }
+ ],
+ default: 'none'
+ }).on("typedinputtypechange", function(evt,type) {
+ ui.type = $(this).typedInput("type");
+ ui.opts = typeOptions[ui.type];
+ if (ui.type === 'input') {
+ // In the case of 'input' type, the typedInput uses the multiple-option
+ // mode. Its value needs to be set to a comma-separately list of the
+ // selected options.
+ inputCellInput.typedInput('value',ui.opts.types.join(","))
+ } else {
+ // No other type cares about `value`, but doing this will
+ // force a refresh of the label now that `ui.opts` has
+ // been updated.
+ inputCellInput.typedInput('value',Date.now())
+ }
- switch (ui.type) {
- case 'input':
+ switch (ui.type) {
+ case 'input':
valueField.typedInput('types',ui.opts.types);
break;
case 'select':
- valueField.typedInput('types',['str']);
- break;
- case 'checkbox':
+ valueField.typedInput('types',['str']);
+ break;
+ case 'checkbox':
valueField.typedInput('types',['bool']);
break;
case 'spinner':
- valueField.typedInput('types',['num'])
+ valueField.typedInput('types',['num']);
+ break;
+ case 'cred':
+ valueField.typedInput('types',['cred']);
break;
default:
- valueField.typedInput('types',['str','num','bool','json','bin','env'])
- }
- if (ui.type === 'checkbox') {
- valueField.typedInput('type','bool');
- } else if (ui.type === 'spinner') {
- valueField.typedInput('type','num');
- }
- if (ui.type !== 'checkbox') {
- checkbox = null;
- }
+ valueField.typedInput('types',DEFAULT_ENV_TYPE_LIST)
+ }
+ if (ui.type === 'checkbox') {
+ valueField.typedInput('type','bool');
+ } else if (ui.type === 'spinner') {
+ valueField.typedInput('type','num');
+ }
+ if (ui.type !== 'checkbox') {
+ checkbox = null;
+ }
- }).on("change", function(evt,type) {
- if (ui.type === 'input') {
- ui.opts.types = inputCellInput.typedInput('value').split(",");
- valueField.typedInput('types',ui.opts.types);
- }
- });
- valueField.on("change", function(evt) {
- if (checkbox) {
- checkbox.prop('checked',$(this).typedInput('value')==="true")
- }
- })
- // Set the input to the right type. This will trigger the 'typedinputtypechange'
- // event handler (just above ^^) to update the value if needed
- inputCellInput.typedInput('type',ui.type)
+ }).on("change", function(evt,type) {
+ if (ui.type === 'input') {
+ ui.opts.types = inputCellInput.typedInput('value').split(",");
+ valueField.typedInput('types',ui.opts.types);
+ }
+ });
+ valueField.on("change", function(evt) {
+ if (checkbox) {
+ checkbox.prop('checked',$(this).typedInput('value')==="true")
+ }
+ })
+ // Set the input to the right type. This will trigger the 'typedinputtypechange'
+ // event handler (just above ^^) to update the value if needed
+ inputCellInput.typedInput('type',ui.type)
}
- function buildEnvUIRow(row, tenv, ui) {
+ function buildEnvUIRow(row, tenv, ui, node) {
ui.label = ui.label||{};
- if (!ui.type) {
+ if ((tenv.type === "cred" || (tenv.parent && tenv.parent.type === "cred")) && !ui.type) {
+ ui.type = "cred";
+ ui.opts = {};
+ } else if (!ui.type) {
ui.type = "input";
- ui.opts = {types:['str','num','bool','json','bin','env']}
+ ui.opts = {types:DEFAULT_ENV_TYPE_LIST}
} else {
if (!ui.opts) {
ui.opts = (ui.type === "select") ? {opts:[]} : {};
@@ -1467,6 +1512,24 @@ RED.subflow = (function() {
input.spinner(spinnerOpts).parent().width('70%');
input.val(val.value);
break;
+ case "cred":
+ input = $('
').css('width','70%').appendTo(row);
+ if (node.credentials) {
+ if (node.credentials[tenv.name]) {
+ input.val(node.credentials[tenv.name]);
+ } else if (node.credentials['has_'+tenv.name]) {
+ input.val("__PWRD__")
+ } else {
+ input.val("");
+ }
+ } else {
+ input.val("");
+ }
+ input.typedInput({
+ types: ['cred'],
+ default: 'cred'
+ })
+ break;
}
if (input) {
input.attr('id',getSubflowEnvPropertyName(tenv.name))
@@ -1478,7 +1541,7 @@ RED.subflow = (function() {
* @param uiContainer - container for UI
* @param envList - env var definitions of template
*/
- function buildEnvUI(uiContainer, envList) {
+ function buildEnvUI(uiContainer, envList,node) {
uiContainer.empty();
var elementID = 0;
for (var i = 0; i < envList.length; i++) {
@@ -1487,7 +1550,7 @@ RED.subflow = (function() {
continue;
}
var row = $("
", { class: "form-row" }).appendTo(uiContainer);
- buildEnvUIRow(row,tenv, tenv.ui || {});
+ buildEnvUIRow(row,tenv, tenv.ui || {}, node);
// console.log(ui);
}
@@ -1525,7 +1588,7 @@ RED.subflow = (function() {
// icon: "",
// label: {},
// type: "input",
- // opts: {types:['str','num','bool','json','bin','env']}
+ // opts: {types:DEFAULT_ENV_TYPE_LIST}
// }
if (!ui.icon) {
delete ui.icon;
@@ -1535,13 +1598,19 @@ RED.subflow = (function() {
}
switch (ui.type) {
case "input":
- if (JSON.stringify(ui.opts) === JSON.stringify({types:['str','num','bool','json','bin','env']})) {
+ if (JSON.stringify(ui.opts) === JSON.stringify({types:DEFAULT_ENV_TYPE_LIST})) {
// This is the default input config. Delete it as it will
// be applied automatically
delete ui.type;
delete ui.opts;
}
break;
+ case "cred":
+ if (envItem.type === "cred") {
+ delete ui.type;
+ }
+ delete ui.opts;
+ break;
case "select":
if (ui.opts && $.isEmptyObject(ui.opts.opts)) {
// This is the default select config.
@@ -1585,7 +1654,7 @@ RED.subflow = (function() {
type: env.type,
value: env.value
},
- ui: env.ui
+ ui: $.extend(true,{},env.ui)
}
envList.push(item);
parentEnv[env.name] = item;
@@ -1621,13 +1690,17 @@ RED.subflow = (function() {
var item;
var ui = data.ui || {};
if (!ui.type) {
- ui.type = "input";
- ui.opts = {types:['str','num','bool','json','bin','env']}
+ if (data.parent && data.parent.type === "cred") {
+ ui.type = "cred";
+ } else {
+ ui.type = "input";
+ ui.opts = {types:DEFAULT_ENV_TYPE_LIST}
+ }
} else {
ui.opts = ui.opts || {};
}
var input = $("#"+getSubflowEnvPropertyName(data.name));
- if (input.length) {
+ if (input.length || ui.type === "cred") {
item = { name: data.name };
switch(ui.type) {
case "input":
@@ -1639,6 +1712,10 @@ RED.subflow = (function() {
item.type = 'str';
}
break;
+ case "cred":
+ item.value = input.val();
+ item.type = 'cred';
+ break;
case "spinner":
item.value = input.val();
item.type = 'num';
@@ -1652,7 +1729,7 @@ RED.subflow = (function() {
item.value = ""+input.prop("checked");
break;
}
- if (item.type !== data.parent.type || item.value !== data.parent.value) {
+ if (ui.type === "cred" || item.type !== data.parent.type || item.value !== data.parent.value) {
env.push(item);
}
}
@@ -1702,14 +1779,17 @@ RED.subflow = (function() {
return defaultLabel;
}
- function buildEditForm(container,type,node) {
+ function buildEditForm(type,node) {
if (type === "subflow-template") {
buildPropertiesList($('#node-input-env-container'), node);
} else if (type === "subflow") {
- buildEnvUI($("#subflow-input-ui"), getSubflowInstanceParentEnv(node));
+ // This gets called by the subflow type `oneditprepare` function
+ // registered in nodes.js#addSubflow()
+ buildEnvUI($("#subflow-input-ui"), getSubflowInstanceParentEnv(node), node);
}
}
- function buildPropertiesForm(container, node) {
+ function buildPropertiesForm(node) {
+ var container = $('#editor-subflow-envProperties-content');
var form = $('
').appendTo(container);
var listContainer = $('
').appendTo(form);
var list = $('
').appendTo(listContainer);
diff --git a/packages/node_modules/@node-red/runtime/lib/api/flows.js b/packages/node_modules/@node-red/runtime/lib/api/flows.js
index 1e6e72dc7..76e645b92 100644
--- a/packages/node_modules/@node-red/runtime/lib/api/flows.js
+++ b/packages/node_modules/@node-red/runtime/lib/api/flows.js
@@ -238,17 +238,25 @@ var api = module.exports = {
if (!credentials) {
return resolve({});
}
- var definition = runtime.nodes.getCredentialDefinition(opts.type) || {};
-
var sendCredentials = {};
- for (var cred in definition) {
- if (definition.hasOwnProperty(cred)) {
- if (definition[cred].type == "password") {
- var key = 'has_' + cred;
- sendCredentials[key] = credentials[cred] != null && credentials[cred] !== '';
- continue;
+ var cred;
+ if (/^subflow(:|$)/.test(opts.type)) {
+ 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] || '';
}
- sendCredentials[cred] = credentials[cred] || '';
}
}
resolve(sendCredentials);
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 355bd9bc5..153ef4b06 100644
--- a/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js
+++ b/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js
@@ -341,33 +341,62 @@ var api = module.exports = {
extract: function(node) {
var nodeID = node.id;
var nodeType = node.type;
+ var cred;
var newCreds = node.credentials;
if (newCreds) {
delete node.credentials;
var savedCredentials = credentialCache[nodeID] || {};
- var dashedType = nodeType.replace(/\s+/g, '-');
- var definition = credentialsDef[dashedType];
- if (!definition) {
- log.warn(log._("nodes.credentials.not-registered",{type:nodeType}));
- return;
- }
+ if (/^subflow(:|$)/.test(nodeType)) {
+ for (cred in newCreds) {
+ if (newCreds.hasOwnProperty(cred)) {
+ if (newCreds[cred] === "__PWRD__") {
+ continue;
+ }
+ if (0 === newCreds[cred].length || /^\s*$/.test(newCreds[cred])) {
+ delete savedCredentials[cred];
+ dirty = true;
+ continue;
+ }
+ if (!savedCredentials.hasOwnProperty(cred) || JSON.stringify(savedCredentials[cred]) !== JSON.stringify(newCreds[cred])) {
+ savedCredentials[cred] = newCreds[cred];
+ dirty = true;
+ }
- for (var cred in definition) {
- if (definition.hasOwnProperty(cred)) {
- if (newCreds[cred] === undefined) {
- continue;
}
- if (definition[cred].type == "password" && newCreds[cred] == '__PWRD__') {
- continue;
+ }
+ for (cred in savedCredentials) {
+ if (savedCredentials.hasOwnProperty(cred)) {
+ if (!newCreds.hasOwnProperty(cred)) {
+ delete savedCredentials[cred];
+ dirty = true;
+ }
}
- if (0 === newCreds[cred].length || /^\s*$/.test(newCreds[cred])) {
- delete savedCredentials[cred];
- dirty = true;
- continue;
- }
- if (!savedCredentials.hasOwnProperty(cred) || JSON.stringify(savedCredentials[cred]) !== JSON.stringify(newCreds[cred])) {
- savedCredentials[cred] = newCreds[cred];
- dirty = true;
+ }
+ } else {
+ var dashedType = nodeType.replace(/\s+/g, '-');
+ var definition = credentialsDef[dashedType];
+ if (!definition) {
+ log.warn(log._("nodes.credentials.not-registered",{type:nodeType}));
+ return;
+ }
+
+ for (cred in definition) {
+ if (definition.hasOwnProperty(cred)) {
+ if (newCreds[cred] === undefined) {
+ continue;
+ }
+ if (definition[cred].type == "password" && newCreds[cred] == '__PWRD__') {
+ continue;
+ }
+ if (0 === newCreds[cred].length || /^\s*$/.test(newCreds[cred])) {
+ delete savedCredentials[cred];
+ dirty = true;
+ continue;
+ }
+ if (!savedCredentials.hasOwnProperty(cred) || JSON.stringify(savedCredentials[cred]) !== JSON.stringify(newCreds[cred])) {
+ savedCredentials[cred] = newCreds[cred];
+ dirty = true;
+ }
}
}
}
diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js
index a7bec1234..c0bc63dd2 100644
--- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js
+++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js
@@ -22,6 +22,9 @@ const util = require("util");
const redUtil = require("@node-red/util").util;
const flowUtil = require("./util");
+
+const credentials = require("../credentials");
+
var Log;
/**
@@ -38,6 +41,9 @@ function evaluateInputValue(value, type, node) {
if (type === "bool") {
return (value === "true") || (value === true);
}
+ if (type === "cred") {
+ return value;
+ }
return redUtil.evaluateNodeProperty(value, type, node, null, null);
}
@@ -142,10 +148,16 @@ class Subflow extends Flow {
this.node_map = node_map;
this.path = parent.path+"/"+(subflowInstance._alias||subflowInstance.id);
+ this.templateCredentials = credentials.get(subflowDef.id);
+ this.instanceCredentials = credentials.get(this.id);
+
var env = [];
if (this.subflowDef.env) {
this.subflowDef.env.forEach(e => {
env[e.name] = e;
+ if (e.type === "cred") {
+ e.value = this.templateCredentials[e.name];
+ }
});
}
if (this.subflowInstance.env) {
@@ -154,7 +166,14 @@ class Subflow extends Flow {
var ui = old ? old.ui : null;
env[e.name] = e;
if (ui) {
- env[e.name].ui = ui;
+ env[e.name].ui = ui;
+ }
+ if (e.type === "cred") {
+ if (!old || this.instanceCredentials.hasOwnProperty(e.name) ) {
+ e.value = this.instanceCredentials[e.name];
+ } else if (old) {
+ e.value = this.templateCredentials[e.name];
+ }
}
});
}
@@ -324,7 +343,6 @@ class Subflow extends Flow {
* @return {Object} val value of env var
*/
getSetting(name) {
- this.trace("getSetting:"+name);
if (!/^\$parent\./.test(name)) {
var env = this.env;
var is_info = name.endsWith("_info");
@@ -333,7 +351,6 @@ class Subflow extends Flow {
if (env && env.hasOwnProperty(ename)) {
var val = env[ename];
-
if (is_type) {
return val ? val.type : undefined;
}