mirror of
				https://github.com/node-red/node-red.git
				synced 2025-03-01 10:36:34 +00:00 
			
		
		
		
	Add support for credential-stored env var in subflow
This commit is contained in:
		| @@ -398,6 +398,10 @@ RED.nodes = (function() { | ||||
|             paletteLabel: function() { return RED.nodes.subflow(sf.id).name }, | ||||
|             inputLabels: function(i) { return sf.inputLabels?sf.inputLabels[i]:null }, | ||||
|             outputLabels: function(i) { return sf.outputLabels?sf.outputLabels[i]:null }, | ||||
|             oneditprepare: function() { | ||||
|                 RED.subflow.buildEditForm("subflow",this); | ||||
|                 RED.subflow.buildPropertiesForm(this); | ||||
|             }, | ||||
|             oneditresize: function(size) { | ||||
|                 // var rows = $(".dialog-form>div:not(.node-input-env-container-row)"); | ||||
|                 var height = size.height; | ||||
| @@ -505,19 +509,33 @@ RED.nodes = (function() { | ||||
|                     node[d] = n[d]; | ||||
|                 } | ||||
|             } | ||||
|             if(exportCreds && n.credentials) { | ||||
|             if (exportCreds) { | ||||
|                 var credentialSet = {}; | ||||
|                 node.credentials = {}; | ||||
|                 for (var cred in n._def.credentials) { | ||||
|                     if (n._def.credentials.hasOwnProperty(cred)) { | ||||
|                         if (n._def.credentials[cred].type == 'password') { | ||||
|                 if (/^subflow:/.test(node.type) && n.credentials) { | ||||
|                     // A subflow instance node can have arbitrary creds | ||||
|                     for (var sfCred in n.credentials) { | ||||
|                         if (n.credentials.hasOwnProperty(sfCred)) { | ||||
|                             if (!n.credentials._ || | ||||
|                                 n.credentials["has_"+cred] != n.credentials._["has_"+cred] || | ||||
|                                 (n.credentials["has_"+cred] && n.credentials[cred])) { | ||||
|                                 n.credentials["has_"+sfCred] != n.credentials._["has_"+sfCred] || | ||||
|                                 (n.credentials["has_"+sfCred] && n.credentials[sfCred])) { | ||||
|                                 credentialSet[sfCred] = n.credentials[sfCred]; | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } else if (n.credentials) { | ||||
|                     node.credentials = {}; | ||||
|                     // All other nodes have a well-defined list of possible credentials | ||||
|                     for (var cred in n._def.credentials) { | ||||
|                         if (n._def.credentials.hasOwnProperty(cred)) { | ||||
|                             if (n._def.credentials[cred].type == 'password') { | ||||
|                                 if (!n.credentials._ || | ||||
|                                     n.credentials["has_"+cred] != n.credentials._["has_"+cred] || | ||||
|                                     (n.credentials["has_"+cred] && n.credentials[cred])) { | ||||
|                                     credentialSet[cred] = n.credentials[cred]; | ||||
|                                 } | ||||
|                             } else if (n.credentials[cred] != null && (!n.credentials._ || n.credentials[cred] != n.credentials._[cred])) { | ||||
|                                 credentialSet[cred] = n.credentials[cred]; | ||||
|                             } | ||||
|                         } else if (n.credentials[cred] != null && (!n.credentials._ || n.credentials[cred] != n.credentials._[cred])) { | ||||
|                             credentialSet[cred] = n.credentials[cred]; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
| @@ -568,7 +586,8 @@ RED.nodes = (function() { | ||||
|         return node; | ||||
|     } | ||||
|  | ||||
|     function convertSubflow(n) { | ||||
|     function convertSubflow(n, exportCreds) { | ||||
|         exportCreds = true; | ||||
|         var node = {}; | ||||
|         node.id = n.id; | ||||
|         node.type = n.type; | ||||
| @@ -578,6 +597,24 @@ RED.nodes = (function() { | ||||
|         node.in = []; | ||||
|         node.out = []; | ||||
|         node.env = n.env; | ||||
|  | ||||
|         if (exportCreds) { | ||||
|             var credentialSet = {}; | ||||
|             // A subflow node can have arbitrary creds | ||||
|             for (var sfCred in n.credentials) { | ||||
|                 if (n.credentials.hasOwnProperty(sfCred)) { | ||||
|                     if (!n.credentials._ || | ||||
|                         n.credentials["has_"+sfCred] != n.credentials._["has_"+sfCred] || | ||||
|                         (n.credentials["has_"+sfCred] && n.credentials[sfCred])) { | ||||
|                         credentialSet[sfCred] = n.credentials[sfCred]; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             if (Object.keys(credentialSet).length > 0) { | ||||
|                 node.credentials = credentialSet; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         node.color = n.color; | ||||
|  | ||||
|         n.in.forEach(function(p) { | ||||
| @@ -693,7 +730,7 @@ RED.nodes = (function() { | ||||
|         } | ||||
|         for (i in subflows) { | ||||
|             if (subflows.hasOwnProperty(i)) { | ||||
|                 nns.push(convertSubflow(subflows[i])); | ||||
|                 nns.push(convertSubflow(subflows[i], exportCredentials)); | ||||
|             } | ||||
|         } | ||||
|         for (i in configNodes) { | ||||
|   | ||||
| @@ -490,8 +490,7 @@ RED.editor = (function() { | ||||
|                 done(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (definition.credentials) { | ||||
|         if (definition.credentials || /^subflow:/.test(definition.type)) { | ||||
|             if (node.credentials) { | ||||
|                 populateCredentialsInputs(node, definition.credentials, node.credentials, prefix); | ||||
|                 completePrepare(); | ||||
| @@ -499,7 +498,9 @@ RED.editor = (function() { | ||||
|                 $.getJSON(getCredentialsURL(node.type, node.id), function (data) { | ||||
|                     node.credentials = data; | ||||
|                     node.credentials._ = $.extend(true,{},data); | ||||
|                     populateCredentialsInputs(node, definition.credentials, node.credentials, prefix); | ||||
|                     if (!/^subflow:/.test(definition.type)) { | ||||
|                         populateCredentialsInputs(node, definition.credentials, node.credentials, prefix); | ||||
|                     } | ||||
|                     completePrepare(); | ||||
|                 }); | ||||
|             } | ||||
| @@ -576,8 +577,11 @@ RED.editor = (function() { | ||||
|             $(this).attr("data-i18n",keys.join(";")); | ||||
|         }); | ||||
|  | ||||
|         if (type === "subflow-template" || type === "subflow") { | ||||
|             RED.subflow.buildEditForm(dialogForm,type,node); | ||||
|         if (type === "subflow-template") { | ||||
|             // This is the 'edit properties' dialog for a subflow template | ||||
|             // TODO: this needs to happen later in the dialog open sequence | ||||
|             //       so that credentials can be loaded prior to building the form | ||||
|             RED.subflow.buildEditForm(type,node); | ||||
|         } | ||||
|  | ||||
|         // Add dummy fields to prevent 'Enter' submitting the form in some | ||||
| @@ -1471,6 +1475,19 @@ RED.editor = (function() { | ||||
|                         if (type === "subflow") { | ||||
|                             var old_env = editing_node.env; | ||||
|                             var new_env = RED.subflow.exportSubflowInstanceEnv(editing_node); | ||||
|                             if (new_env && new_env.length > 0) { | ||||
|                                 new_env.forEach(function(prop) { | ||||
|                                     if (prop.type === "cred") { | ||||
|                                         editing_node.credentials = editing_node.credentials || {_:{}}; | ||||
|                                         editing_node.credentials[prop.name] = prop.value; | ||||
|                                         editing_node.credentials['has_'+prop.name] = (prop.value !== ""); | ||||
|                                         if (prop.value !== '__PWRD__') { | ||||
|                                             changed = true; | ||||
|                                         } | ||||
|                                         delete prop.value; | ||||
|                                     } | ||||
|                                 }); | ||||
|                             } | ||||
|                             if (!isSameObj(old_env, new_env)) { | ||||
|                                 editing_node.env = new_env; | ||||
|                                 changes.env = editing_node.env; | ||||
| @@ -1599,12 +1616,13 @@ RED.editor = (function() { | ||||
|                         id: "editor-subflow-envProperties", | ||||
|                         label: RED._("editor-tab.envProperties"), | ||||
|                         name: RED._("editor-tab.envProperties"), | ||||
|                         content: $('<div>', {class:"red-ui-tray-content"}).appendTo(editorContent).hide(), | ||||
|                         content: $('<div>', {id:"editor-subflow-envProperties-content",class:"red-ui-tray-content"}).appendTo(editorContent).hide(), | ||||
|                         iconClass: "fa fa-list" | ||||
|                     }; | ||||
|  | ||||
|                     RED.subflow.buildPropertiesForm(subflowPropertiesTab.content,node); | ||||
|                     editorTabs.addTab(subflowPropertiesTab); | ||||
|                     // This tab is populated by the oneditprepare function of this | ||||
|                     // subflow. That ensures it is done *after* any credentials | ||||
|                     // have been loaded for the instance. | ||||
|                 } | ||||
|  | ||||
|                 if (!node._def.defaults || !node._def.defaults.hasOwnProperty('info'))  { | ||||
| @@ -2252,6 +2270,21 @@ RED.editor = (function() { | ||||
|  | ||||
|                         var old_env = editing_node.env; | ||||
|                         var new_env = RED.subflow.exportSubflowTemplateEnv($("#node-input-env-container").editableList("items")); | ||||
|  | ||||
|                         if (new_env && new_env.length > 0) { | ||||
|                             new_env.forEach(function(prop) { | ||||
|                                 if (prop.type === "cred") { | ||||
|                                     editing_node.credentials = editing_node.credentials || {_:{}}; | ||||
|                                     editing_node.credentials[prop.name] = prop.value; | ||||
|                                     editing_node.credentials['has_'+prop.name] = (prop.value !== ""); | ||||
|                                     if (prop.value !== '__PWRD__') { | ||||
|                                         changed = true; | ||||
|                                     } | ||||
|                                     delete prop.value; | ||||
|                                 } | ||||
|                             }); | ||||
|                         } | ||||
|  | ||||
|                         if (!isSameObj(old_env, new_env)) { | ||||
|                             editing_node.env = new_env; | ||||
|                             changes.env = editing_node.env; | ||||
| @@ -2311,7 +2344,7 @@ RED.editor = (function() { | ||||
|                     $("#node-input-env-container").editableList('height',height-95); | ||||
|                 } | ||||
|             }, | ||||
|             open: function(tray) { | ||||
|             open: function(tray, done) { | ||||
|                 var trayFooter = tray.find(".red-ui-tray-footer"); | ||||
|                 var trayFooterLeft = $("<div/>", { | ||||
|                     class: "red-ui-tray-footer-left" | ||||
| @@ -2362,7 +2395,6 @@ RED.editor = (function() { | ||||
|                     content: $('<div>', {class:"red-ui-tray-content"}).appendTo(editorContent).hide(), | ||||
|                     iconClass: "fa fa-cog" | ||||
|                 }; | ||||
|                 buildEditForm(nodePropertiesTab.content,"dialog-form","subflow-template", undefined, editing_node); | ||||
|                 editorTabs.addTab(nodePropertiesTab); | ||||
|  | ||||
|                 var descriptionTab = { | ||||
| @@ -2391,11 +2423,18 @@ RED.editor = (function() { | ||||
|                 buildAppearanceForm(appearanceTab.content,editing_node); | ||||
|                 editorTabs.addTab(appearanceTab); | ||||
|  | ||||
|                 $("#subflow-input-name").val(subflow.name); | ||||
|                 RED.text.bidi.prepareInput($("#subflow-input-name")); | ||||
|                 $.getJSON(getCredentialsURL("subflow", subflow.id), function (data) { | ||||
|                     subflow.credentials = data; | ||||
|                     subflow.credentials._ = $.extend(true,{},data); | ||||
|  | ||||
|                 trayBody.i18n(); | ||||
|                 finishedBuilding = true; | ||||
|                     buildEditForm(nodePropertiesTab.content,"dialog-form","subflow-template", undefined, editing_node); | ||||
|                     $("#subflow-input-name").val(subflow.name); | ||||
|                     RED.text.bidi.prepareInput($("#subflow-input-name")); | ||||
|  | ||||
|                     trayBody.i18n(); | ||||
|                     finishedBuilding = true; | ||||
|                     done(); | ||||
|                 }); | ||||
|             }, | ||||
|             close: function() { | ||||
|                 if (RED.view.state() != RED.state.IMPORT_DRAGGING) { | ||||
|   | ||||
| @@ -770,7 +770,7 @@ RED.subflow = (function() { | ||||
|     /** | ||||
|      * Create interface for controlling env var UI definition | ||||
|      */ | ||||
|     function buildEnvControl(envList) { | ||||
|     function buildEnvControl(envList,node) { | ||||
|  | ||||
|         var tabs = RED.tabs.create({ | ||||
|             id: "subflow-env-tabs", | ||||
| @@ -779,7 +779,7 @@ RED.subflow = (function() { | ||||
|                     var inputContainer = $("#subflow-input-ui"); | ||||
|                     var list = envList.editableList("items"); | ||||
|                     var exportedEnv = exportEnvList(list, true); | ||||
|                     buildEnvUI(inputContainer, exportedEnv); | ||||
|                     buildEnvUI(inputContainer, exportedEnv,node); | ||||
|                 } | ||||
|                 $("#subflow-env-tabs-content").children().hide(); | ||||
|                 $("#" + tab.id).show(); | ||||
| @@ -831,6 +831,9 @@ RED.subflow = (function() { | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     var DEFAULT_ENV_TYPE_LIST = ['str','num','bool','json','bin','env']; | ||||
|     var DEFAULT_ENV_TYPE_LIST_INC_CRED = ['str','num','bool','json','bin','env','cred']; | ||||
|  | ||||
|     /** | ||||
|      * Create env var edit interface | ||||
|      * @param container - container | ||||
| @@ -841,7 +844,7 @@ RED.subflow = (function() { | ||||
|         var isTemplateNode = (node.type === "subflow"); | ||||
|  | ||||
|         if (isTemplateNode) { | ||||
|             buildEnvControl(envContainer); | ||||
|             buildEnvControl(envContainer, node); | ||||
|         } | ||||
|         envContainer | ||||
|             .css({ | ||||
| @@ -851,6 +854,9 @@ RED.subflow = (function() { | ||||
|             .editableList({ | ||||
|                 header: isTemplateNode?$('<div><div><div></div><div data-i18n="common.label.name"></div><div data-i18n="editor-tab.defaultValue"></div><div></div></div></div>'):undefined, | ||||
|                 addItem: function(container, i, opt) { | ||||
|                     // If this is an instance node, these are properties unique to | ||||
|                     // this instance - ie opt.parent will not be defined. | ||||
|  | ||||
|                     if (isTemplateNode) { | ||||
|                         container.addClass("red-ui-editor-subflow-env-editable") | ||||
|                     } | ||||
| @@ -859,52 +865,64 @@ RED.subflow = (function() { | ||||
|                     var nameField = null; | ||||
|                     var valueField = null; | ||||
|  | ||||
|                     // if (opt.parent) { | ||||
|                     //     buildEnvUIRow(envRow,opt,opt.parent.ui||{}) | ||||
|                     // } else { | ||||
|                         nameField = $('<input/>', { | ||||
|                             class: "node-input-env-name", | ||||
|                             type: "text", | ||||
|                             placeholder: RED._("common.label.name") | ||||
|                         }).attr("autocomplete","disable").appendTo(envRow).val(opt.name); | ||||
|                         valueField = $('<input/>',{ | ||||
|                             style: "width:100%", | ||||
|                             class: "node-input-env-value", | ||||
|                             type: "text", | ||||
|                         }).attr("autocomplete","disable").appendTo(envRow) | ||||
|                         valueField.typedInput({default:'str',types:['str','num','bool','json','bin','env']}); | ||||
|                         valueField.typedInput('type', opt.parent?(opt.type||opt.parent.type):opt.type); | ||||
|                         valueField.typedInput('value', opt.parent?((opt.value !== undefined)?opt.value:opt.parent.value):opt.value); | ||||
|                     // } | ||||
|                     nameField = $('<input/>', { | ||||
|                         class: "node-input-env-name", | ||||
|                         type: "text", | ||||
|                         placeholder: RED._("common.label.name") | ||||
|                     }).attr("autocomplete","disable").appendTo(envRow).val(opt.name); | ||||
|                     valueField = $('<input/>',{ | ||||
|                         style: "width:100%", | ||||
|                         class: "node-input-env-value", | ||||
|                         type: "text", | ||||
|                     }).attr("autocomplete","disable").appendTo(envRow) | ||||
|                     valueField.typedInput({default:'str',types:isTemplateNode?DEFAULT_ENV_TYPE_LIST:DEFAULT_ENV_TYPE_LIST_INC_CRED}); | ||||
|                     valueField.typedInput('type', opt.type); | ||||
|                     if (opt.type === "cred") { | ||||
|                         if (opt.value) { | ||||
|                             valueField.typedInput('value', opt.value); | ||||
|                         } else if (node.credentials && node.credentials[opt.name]) { | ||||
|                             valueField.typedInput('value', node.credentials[opt.name]); | ||||
|                         } else if (node.credentials && node.credentials['has_'+opt.name]) { | ||||
|                             valueField.typedInput('value', "__PWRD__"); | ||||
|                         } else { | ||||
|                             valueField.typedInput('value', ""); | ||||
|                         } | ||||
|                     } else { | ||||
|                         valueField.typedInput('value', opt.value); | ||||
|                     } | ||||
|  | ||||
|  | ||||
|                     opt.nameField = nameField; | ||||
|                     opt.valueField = valueField; | ||||
|  | ||||
|                     if (!opt.parent) { | ||||
|                         var actionButton = $('<a/>',{href:"#",class:"red-ui-editableList-item-remove red-ui-button red-ui-button-small"}).appendTo(envRow); | ||||
|                         $('<i/>',{class:"fa "+(opt.parent?"fa-reply":"fa-remove")}).appendTo(actionButton); | ||||
|                         var removeTip = RED.popover.tooltip(actionButton,RED._("subflow.env.remove")); | ||||
|                         actionButton.on("click", function(evt) { | ||||
|                             evt.preventDefault(); | ||||
|                             removeTip.close(); | ||||
|                             container.parent().addClass("red-ui-editableList-item-deleting") | ||||
|                             container.fadeOut(300, function() { | ||||
|                                 envContainer.editableList('removeItem',opt); | ||||
|                             }); | ||||
|                     var actionButton = $('<a/>',{href:"#",class:"red-ui-editableList-item-remove red-ui-button red-ui-button-small"}).appendTo(envRow); | ||||
|                     $('<i/>',{class:"fa "+(opt.parent?"fa-reply":"fa-remove")}).appendTo(actionButton); | ||||
|                     var removeTip = RED.popover.tooltip(actionButton,RED._("subflow.env.remove")); | ||||
|                     actionButton.on("click", function(evt) { | ||||
|                         evt.preventDefault(); | ||||
|                         removeTip.close(); | ||||
|                         container.parent().addClass("red-ui-editableList-item-deleting") | ||||
|                         container.fadeOut(300, function() { | ||||
|                             envContainer.editableList('removeItem',opt); | ||||
|                         }); | ||||
|                     } | ||||
|                     }); | ||||
|  | ||||
|                     if (isTemplateNode) { | ||||
|                         // Add the UI customisation row | ||||
|                         // if `opt.ui` does not exist, then apply defaults. If these | ||||
|                         // defaults do not change then they will get stripped off | ||||
|                         // before saving. | ||||
|                         opt.ui = opt.ui || { | ||||
|                             icon: "", | ||||
|                             label: {}, | ||||
|                             type: "input", | ||||
|                             opts: {types:['str','num','bool','json','bin','env']} | ||||
|                         if (opt.type === 'cred') { | ||||
|                             opt.ui = opt.ui || { | ||||
|                                 icon: "", | ||||
|                                 type: "cred" | ||||
|                             } | ||||
|                         } else { | ||||
|                             opt.ui = opt.ui || { | ||||
|                                 icon: "", | ||||
|                                 type: "input", | ||||
|                                 opts: {types:DEFAULT_ENV_TYPE_LIST} | ||||
|                             } | ||||
|                         } | ||||
|                         opt.ui.label = opt.ui.label || {}; | ||||
|                         opt.ui.type = opt.ui.type || "input"; | ||||
| @@ -995,11 +1013,11 @@ RED.subflow = (function() { | ||||
|  | ||||
|          var row = $('<div></div>').appendTo(container); | ||||
|          $('<div><i class="red-ui-editableList-item-handle fa fa-bars"></i></div>').appendTo(row); | ||||
|  | ||||
|          var typeOptions = { | ||||
|              'input': {types:['str','num','bool','json','bin','env']}, | ||||
|              'input': {types:DEFAULT_ENV_TYPE_LIST}, | ||||
|              'select': {opts:[]}, | ||||
|              'spinner': {} | ||||
|              'spinner': {}, | ||||
|              'cred': {} | ||||
|          }; | ||||
|          if (ui.opts) { | ||||
|              typeOptions[ui.type] = ui.opts; | ||||
| @@ -1054,7 +1072,7 @@ RED.subflow = (function() { | ||||
|              } | ||||
|              langs.forEach(function(l) { | ||||
|                  var row = $('<div>').appendTo(content); | ||||
|                  $('<span>').css({display:"inline-block",width:"120px"}).text(RED._("languages."+l)+(l===currentLocale?"*":"")).appendTo(row);  | ||||
|                  $('<span>').css({display:"inline-block",width:"120px"}).text(RED._("languages."+l)+(l===currentLocale?"*":"")).appendTo(row); | ||||
|                  $('<span>').text(ui.label[l]||"").appendTo(row); | ||||
|              }); | ||||
|              return content; | ||||
| @@ -1062,314 +1080,341 @@ RED.subflow = (function() { | ||||
|  | ||||
|          nameField.on('change',function(evt) { | ||||
|             labelInput.attr("placeholder",$(this).val()) | ||||
|          }); | ||||
|         }); | ||||
|  | ||||
|          var inputCell = $('<div></div>').appendTo(row); | ||||
|          var inputCellInput = $('<input type="text">').css("width","100%").appendTo(inputCell); | ||||
|          if (ui.type === "input") { | ||||
|              inputCellInput.val(ui.opts.types.join(",")); | ||||
|          } | ||||
|          var checkbox; | ||||
|          var selectBox; | ||||
|         var inputCell = $('<div></div>').appendTo(row); | ||||
|         var inputCellInput = $('<input type="text">').css("width","100%").appendTo(inputCell); | ||||
|         if (ui.type === "input") { | ||||
|             inputCellInput.val(ui.opts.types.join(",")); | ||||
|         } | ||||
|         var checkbox; | ||||
|         var selectBox; | ||||
|  | ||||
|          inputCellInput.typedInput({ | ||||
|              types: [ | ||||
|                  { | ||||
|                      value:"input", | ||||
|                      label:RED._("editor.inputs.input"), icon:"fa fa-i-cursor",showLabel:false,multiple:true,options:[ | ||||
|                          {value:"str",label:RED._("editor.types.str"),icon:"red/images/typedInput/az.svg"}, | ||||
|                          {value:"num",label:RED._("editor.types.num"),icon:"red/images/typedInput/09.svg"}, | ||||
|                          {value:"bool",label:RED._("editor.types.bool"),icon:"red/images/typedInput/bool.svg"}, | ||||
|                          {value:"json",label:RED._("editor.types.json"),icon:"red/images/typedInput/json.svg"}, | ||||
|                          {value: "bin",label: RED._("editor.types.bin"),icon: "red/images/typedInput/bin.svg"}, | ||||
|                          {value: "env",label: RED._("editor.types.env"),icon: "red/images/typedInput/env.svg"} | ||||
|                      ], | ||||
|                      default: ['str','num','bool','json','bin','env'], | ||||
|                      valueLabel: function(container,value) { | ||||
|                          container.css("padding",0); | ||||
|                          var innerContainer = $('<div>').css({ | ||||
|                              "background":"white", | ||||
|                              "height":"100%", | ||||
|                              "box-sizing": "border-box" | ||||
|                          }).appendTo(container); | ||||
|         inputCellInput.typedInput({ | ||||
|             types: [ | ||||
|                 { | ||||
|                     value:"input", | ||||
|                     label:RED._("editor.inputs.input"), icon:"fa fa-i-cursor",showLabel:false,multiple:true,options:[ | ||||
|                         {value:"str",label:RED._("editor.types.str"),icon:"red/images/typedInput/az.svg"}, | ||||
|                         {value:"num",label:RED._("editor.types.num"),icon:"red/images/typedInput/09.svg"}, | ||||
|                         {value:"bool",label:RED._("editor.types.bool"),icon:"red/images/typedInput/bool.svg"}, | ||||
|                         {value:"json",label:RED._("editor.types.json"),icon:"red/images/typedInput/json.svg"}, | ||||
|                         {value: "bin",label: RED._("editor.types.bin"),icon: "red/images/typedInput/bin.svg"}, | ||||
|                         {value: "env",label: RED._("editor.types.env"),icon: "red/images/typedInput/env.svg"}, | ||||
|                         {value: "cred",label: RED._("editor.types.cred"),icon: "fa fa-lock"} | ||||
|                     ], | ||||
|                     default: DEFAULT_ENV_TYPE_LIST, | ||||
|                     valueLabel: function(container,value) { | ||||
|                         container.css("padding",0); | ||||
|                         var innerContainer = $('<div>').css({ | ||||
|                             "background":"white", | ||||
|                             "height":"100%", | ||||
|                             "box-sizing": "border-box" | ||||
|                         }).appendTo(container); | ||||
|  | ||||
|                          var input = $('<div class="placeholder-input">').appendTo(innerContainer); | ||||
|                          $('<span><i class="fa fa-i-cursor"></i></span>').appendTo(input); | ||||
|                          if (value.length) { | ||||
|                              value.forEach(function(v) { | ||||
|                                  $('<img>',{src:v.icon,style:"max-width:14px; padding: 0 3px; margin-top:-4px; margin-left: 3px"}).appendTo(input); | ||||
|                              }) | ||||
|                          } else { | ||||
|                              $("<span>").css({ | ||||
|                                  "color":"#aaa", | ||||
|                                  "padding-left": "4px" | ||||
|                              }).text("select types...").appendTo(input); | ||||
|                          } | ||||
|                      } | ||||
|                  }, | ||||
|                  { | ||||
|                      value:"select", | ||||
|                      label:RED._("editor.inputs.select"), icon:"fa fa-tasks",showLabel:false, | ||||
|                      valueLabel: function(container,value) { | ||||
|                          container.css("padding","0"); | ||||
|                         var input = $('<div class="placeholder-input">').appendTo(innerContainer); | ||||
|                         $('<span><i class="fa fa-i-cursor"></i></span>').appendTo(input); | ||||
|                         if (value.length) { | ||||
|                             value.forEach(function(v) { | ||||
|                                 if (!/^fa /.test(v.icon)) { | ||||
|                                     $('<img>',{src:v.icon,style:"max-width:14px; padding: 0 3px; margin-top:-4px; margin-left: 1px"}).appendTo(input); | ||||
|                                 } else { | ||||
|                                     var s = $('<span>',{style:"max-width:14px; padding: 0 3px; margin-top:-4px; margin-left: 1px"}).appendTo(input); | ||||
|                                     $("<i>",{class: v.icon}).appendTo(s); | ||||
|                                 } | ||||
|                             }) | ||||
|                         } else { | ||||
|                             $("<span>").css({ | ||||
|                                 "color":"#aaa", | ||||
|                                 "padding-left": "4px" | ||||
|                             }).text("select types...").appendTo(input); | ||||
|                         } | ||||
|                     } | ||||
|                 }, | ||||
|                 { | ||||
|                     value: "cred", | ||||
|                     label: RED._("typedInput.type.cred"), icon:"fa fa-lock", showLabel: false, | ||||
|                     valueLabel: function(container,value) { | ||||
|                         container.css("padding",0); | ||||
|                         var innerContainer = $('<div>').css({ | ||||
|                             "background":"white", | ||||
|                             "height":"100%", | ||||
|                             "box-sizing": "border-box", | ||||
|                             "border-top-right-radius": "4px", | ||||
|                             "border-bottom-right-radius": "4px" | ||||
|                         }).appendTo(container); | ||||
|                         $('<div class="placeholder-input">').html("••••••••").appendTo(innerContainer); | ||||
|                     } | ||||
|                 }, | ||||
|                 { | ||||
|                     value:"select", | ||||
|                     label:RED._("editor.inputs.select"), icon:"fa fa-tasks",showLabel:false, | ||||
|                     valueLabel: function(container,value) { | ||||
|                         container.css("padding","0"); | ||||
|  | ||||
|                          selectBox = $('<select></select>').appendTo(container); | ||||
|                          if (ui.opts && Array.isArray(ui.opts.opts)) { | ||||
|                              ui.opts.opts.forEach(function(o) { | ||||
|                                  var label = lookupLabel(o.l, o.l["en-US"]||o.v, currentLocale); | ||||
|                                  // $('<option>').val((o.t||'str')+":"+o.v).text(label).appendTo(selectBox); | ||||
|                                  $('<option>').val(o.v).text(label).appendTo(selectBox); | ||||
|                              }) | ||||
|                          } | ||||
|                          selectBox.on('change', function(evt) { | ||||
|                              var v = selectBox.val(); | ||||
|                              // var parts = v.split(":"); | ||||
|                              // var t = parts.shift(); | ||||
|                              // v = parts.join(":"); | ||||
|                              // | ||||
|                              // valueField.typedInput("type",'str') | ||||
|                              valueField.typedInput("value",v) | ||||
|                          }); | ||||
|                          selectBox.val(valueField.typedInput("value")); | ||||
|                          // selectBox.val(valueField.typedInput('type')+":"+valueField.typedInput("value")); | ||||
|                      }, | ||||
|                      expand: { | ||||
|                          icon: "fa-caret-down", | ||||
|                          minWidth: 400, | ||||
|                          content: function(container) { | ||||
|                              var content = $('<div class="red-ui-editor-subflow-ui-edit-panel">').appendTo(container); | ||||
|                              var optList = $('<ol>').appendTo(content).editableList({ | ||||
|                                  header:$("<div><div>"+RED._("editor.select.label")+"</div><div>"+RED._("editor.select.value")+"</div></div>"), | ||||
|                                  addItem: function(row,index,itemData) { | ||||
|                                      var labelDiv = $('<div>').appendTo(row); | ||||
|                                      var label = lookupLabel(itemData.l, "", currentLocale); | ||||
|                                      itemData.label = $('<input type="text">').val(label).appendTo(labelDiv); | ||||
|                                      itemData.label.on('keydown', function(evt) { | ||||
|                                          if (evt.keyCode === 13) { | ||||
|                                              itemData.input.focus(); | ||||
|                                              evt.preventDefault(); | ||||
|                                          } | ||||
|                                      }); | ||||
|                                      var labelIcon = $('<span class="red-ui-editor-subflow-env-lang-icon"><i class="fa fa-language"></i></span>').appendTo(labelDiv); | ||||
|                                      RED.popover.tooltip(labelIcon,function() { | ||||
|                                          return currentLocale; | ||||
|                                      }) | ||||
|                                      itemData.input = $('<input type="text">').val(itemData.v).appendTo(row); | ||||
|                         selectBox = $('<select></select>').appendTo(container); | ||||
|                         if (ui.opts && Array.isArray(ui.opts.opts)) { | ||||
|                             ui.opts.opts.forEach(function(o) { | ||||
|                                 var label = lookupLabel(o.l, o.l["en-US"]||o.v, currentLocale); | ||||
|                                 // $('<option>').val((o.t||'str')+":"+o.v).text(label).appendTo(selectBox); | ||||
|                                 $('<option>').val(o.v).text(label).appendTo(selectBox); | ||||
|                             }) | ||||
|                         } | ||||
|                         selectBox.on('change', function(evt) { | ||||
|                             var v = selectBox.val(); | ||||
|                             // var parts = v.split(":"); | ||||
|                             // var t = parts.shift(); | ||||
|                             // v = parts.join(":"); | ||||
|                             // | ||||
|                             // valueField.typedInput("type",'str') | ||||
|                             valueField.typedInput("value",v) | ||||
|                         }); | ||||
|                         selectBox.val(valueField.typedInput("value")); | ||||
|                         // selectBox.val(valueField.typedInput('type')+":"+valueField.typedInput("value")); | ||||
|                     }, | ||||
|                     expand: { | ||||
|                         icon: "fa-caret-down", | ||||
|                         minWidth: 400, | ||||
|                         content: function(container) { | ||||
|                             var content = $('<div class="red-ui-editor-subflow-ui-edit-panel">').appendTo(container); | ||||
|                             var optList = $('<ol>').appendTo(content).editableList({ | ||||
|                                 header:$("<div><div>"+RED._("editor.select.label")+"</div><div>"+RED._("editor.select.value")+"</div></div>"), | ||||
|                                 addItem: function(row,index,itemData) { | ||||
|                                     var labelDiv = $('<div>').appendTo(row); | ||||
|                                     var label = lookupLabel(itemData.l, "", currentLocale); | ||||
|                                     itemData.label = $('<input type="text">').val(label).appendTo(labelDiv); | ||||
|                                     itemData.label.on('keydown', function(evt) { | ||||
|                                         if (evt.keyCode === 13) { | ||||
|                                             itemData.input.focus(); | ||||
|                                             evt.preventDefault(); | ||||
|                                         } | ||||
|                                     }); | ||||
|                                     var labelIcon = $('<span class="red-ui-editor-subflow-env-lang-icon"><i class="fa fa-language"></i></span>').appendTo(labelDiv); | ||||
|                                     RED.popover.tooltip(labelIcon,function() { | ||||
|                                         return currentLocale; | ||||
|                                     }) | ||||
|                                     itemData.input = $('<input type="text">').val(itemData.v).appendTo(row); | ||||
|  | ||||
|                                      // Problem using a TI here: | ||||
|                                      //  - this is in a popout panel | ||||
|                                      //  - clicking the expand button in the TI will close the parent edit tray | ||||
|                                      //    and open the type editor. | ||||
|                                      //  - but it leaves the popout panel over the top. | ||||
|                                      //  - there is no way to get back to the popout panel after closing the type editor | ||||
|                                      //.typedInput({default:itemData.t||'str', types:['str','num','bool','json','bin','env']}); | ||||
|                                      itemData.input.on('keydown', function(evt) { | ||||
|                                          if (evt.keyCode === 13) { | ||||
|                                              // Enter or Tab | ||||
|                                              var index = optList.editableList('indexOf',itemData); | ||||
|                                              var length = optList.editableList('length'); | ||||
|                                              if (index + 1 === length) { | ||||
|                                                  var newItem = {}; | ||||
|                                                  optList.editableList('addItem',newItem); | ||||
|                                                  setTimeout(function() { | ||||
|                                                      if (newItem.label) { | ||||
|                                                          newItem.label.focus(); | ||||
|                                                      } | ||||
|                                                  },100) | ||||
|                                              } else { | ||||
|                                                  var nextItem = optList.editableList('getItemAt',index+1); | ||||
|                                                  if (nextItem.label) { | ||||
|                                                      nextItem.label.focus() | ||||
|                                                  } | ||||
|                                              } | ||||
|                                              evt.preventDefault(); | ||||
|                                          } | ||||
|                                      }); | ||||
|                                  }, | ||||
|                                  sortable: true, | ||||
|                                  removable: true, | ||||
|                                  height: 160 | ||||
|                              }) | ||||
|                              if (ui.opts.opts.length > 0) { | ||||
|                                  ui.opts.opts.forEach(function(o) { | ||||
|                                      optList.editableList('addItem',$.extend(true,{},o)) | ||||
|                                  }) | ||||
|                              } else { | ||||
|                                  optList.editableList('addItem',{}) | ||||
|                              } | ||||
|                              return { | ||||
|                                  onclose: function() { | ||||
|                                      var items = optList.editableList('items'); | ||||
|                                      var vals = []; | ||||
|                                      items.each(function (i,el) { | ||||
|                                          var data = el.data('data'); | ||||
|                                          var l = data.label.val().trim(); | ||||
|                                          var v = data.input.val(); | ||||
|                                          // var t = data.input.typedInput('type'); | ||||
|                                          // var v = data.input.typedInput('value'); | ||||
|                                          if (l.length > 0) { | ||||
|                                              data.l = data.l || {}; | ||||
|                                              data.l[currentLocale] = l; | ||||
|                                          } | ||||
|                                          data.v = v; | ||||
|                                     // Problem using a TI here: | ||||
|                                     //  - this is in a popout panel | ||||
|                                     //  - clicking the expand button in the TI will close the parent edit tray | ||||
|                                     //    and open the type editor. | ||||
|                                     //  - but it leaves the popout panel over the top. | ||||
|                                     //  - there is no way to get back to the popout panel after closing the type editor | ||||
|                                     //.typedInput({default:itemData.t||'str', types:DEFAULT_ENV_TYPE_LIST}); | ||||
|                                     itemData.input.on('keydown', function(evt) { | ||||
|                                         if (evt.keyCode === 13) { | ||||
|                                             // Enter or Tab | ||||
|                                             var index = optList.editableList('indexOf',itemData); | ||||
|                                             var length = optList.editableList('length'); | ||||
|                                             if (index + 1 === length) { | ||||
|                                                 var newItem = {}; | ||||
|                                                 optList.editableList('addItem',newItem); | ||||
|                                                 setTimeout(function() { | ||||
|                                                     if (newItem.label) { | ||||
|                                                         newItem.label.focus(); | ||||
|                                                     } | ||||
|                                                 },100) | ||||
|                                             } else { | ||||
|                                                 var nextItem = optList.editableList('getItemAt',index+1); | ||||
|                                                 if (nextItem.label) { | ||||
|                                                     nextItem.label.focus() | ||||
|                                                 } | ||||
|                                             } | ||||
|                                             evt.preventDefault(); | ||||
|                                         } | ||||
|                                     }); | ||||
|                                 }, | ||||
|                                 sortable: true, | ||||
|                                 removable: true, | ||||
|                                 height: 160 | ||||
|                             }) | ||||
|                             if (ui.opts.opts.length > 0) { | ||||
|                                 ui.opts.opts.forEach(function(o) { | ||||
|                                     optList.editableList('addItem',$.extend(true,{},o)) | ||||
|                                 }) | ||||
|                             } else { | ||||
|                                 optList.editableList('addItem',{}) | ||||
|                             } | ||||
|                             return { | ||||
|                                 onclose: function() { | ||||
|                                     var items = optList.editableList('items'); | ||||
|                                     var vals = []; | ||||
|                                     items.each(function (i,el) { | ||||
|                                         var data = el.data('data'); | ||||
|                                         var l = data.label.val().trim(); | ||||
|                                         var v = data.input.val(); | ||||
|                                         // var t = data.input.typedInput('type'); | ||||
|                                         // var v = data.input.typedInput('value'); | ||||
|                                         if (l.length > 0) { | ||||
|                                             data.l = data.l || {}; | ||||
|                                             data.l[currentLocale] = l; | ||||
|                                         } | ||||
|                                         data.v = v; | ||||
|  | ||||
|                                          if (l.length > 0 || v.length > 0) { | ||||
|                                              var val = {l:data.l,v:data.v}; | ||||
|                                              // if (t !== 'str') { | ||||
|                                              //     val.t = t; | ||||
|                                              // } | ||||
|                                              vals.push(val); | ||||
|                                          } | ||||
|                                      }); | ||||
|                                      ui.opts.opts = vals; | ||||
|                                      inputCellInput.typedInput('value',Date.now()) | ||||
|                                  } | ||||
|                              } | ||||
|                          } | ||||
|                      } | ||||
|                  }, | ||||
|                  { | ||||
|                      value:"checkbox", | ||||
|                      label:RED._("editor.inputs.checkbox"), icon:"fa fa-check-square-o",showLabel:false, | ||||
|                      valueLabel: function(container,value) { | ||||
|                          container.css("padding",0); | ||||
|                          checkbox = $('<input type="checkbox">').appendTo(container); | ||||
|                          checkbox.on('change', function(evt) { | ||||
|                              valueField.typedInput('value',$(this).prop('checked')?"true":"false"); | ||||
|                          }) | ||||
|                          checkbox.prop('checked',valueField.typedInput('value')==="true"); | ||||
|                      } | ||||
|                  }, | ||||
|                  { | ||||
|                      value:"spinner", | ||||
|                      label:RED._("editor.inputs.spinner"), icon:"fa fa-sort-numeric-asc", showLabel:false, | ||||
|                      valueLabel: function(container,value) { | ||||
|                          container.css("padding",0); | ||||
|                          var innerContainer = $('<div>').css({ | ||||
|                              "background":"white", | ||||
|                              "height":"100%", | ||||
|                              "box-sizing": "border-box" | ||||
|                          }).appendTo(container); | ||||
|                                         if (l.length > 0 || v.length > 0) { | ||||
|                                             var val = {l:data.l,v:data.v}; | ||||
|                                             // if (t !== 'str') { | ||||
|                                             //     val.t = t; | ||||
|                                             // } | ||||
|                                             vals.push(val); | ||||
|                                         } | ||||
|                                     }); | ||||
|                                     ui.opts.opts = vals; | ||||
|                                     inputCellInput.typedInput('value',Date.now()) | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 }, | ||||
|                 { | ||||
|                     value:"checkbox", | ||||
|                     label:RED._("editor.inputs.checkbox"), icon:"fa fa-check-square-o",showLabel:false, | ||||
|                     valueLabel: function(container,value) { | ||||
|                         container.css("padding",0); | ||||
|                         checkbox = $('<input type="checkbox">').appendTo(container); | ||||
|                         checkbox.on('change', function(evt) { | ||||
|                             valueField.typedInput('value',$(this).prop('checked')?"true":"false"); | ||||
|                         }) | ||||
|                         checkbox.prop('checked',valueField.typedInput('value')==="true"); | ||||
|                     } | ||||
|                 }, | ||||
|                 { | ||||
|                     value:"spinner", | ||||
|                     label:RED._("editor.inputs.spinner"), icon:"fa fa-sort-numeric-asc", showLabel:false, | ||||
|                     valueLabel: function(container,value) { | ||||
|                         container.css("padding",0); | ||||
|                         var innerContainer = $('<div>').css({ | ||||
|                             "background":"white", | ||||
|                             "height":"100%", | ||||
|                             "box-sizing": "border-box" | ||||
|                         }).appendTo(container); | ||||
|  | ||||
|                          var input = $('<div class="placeholder-input">').appendTo(innerContainer); | ||||
|                          $('<span><i class="fa fa-sort-numeric-asc"></i></span>').appendTo(input); | ||||
|                         var input = $('<div class="placeholder-input">').appendTo(innerContainer); | ||||
|                         $('<span><i class="fa fa-sort-numeric-asc"></i></span>').appendTo(input); | ||||
|  | ||||
|                          var min = ui.opts && ui.opts.min; | ||||
|                          var max = ui.opts && ui.opts.max; | ||||
|                          var label = ""; | ||||
|                          if (min !== undefined && max !== undefined) { | ||||
|                              label = Math.min(min,max)+" - "+Math.max(min,max); | ||||
|                          } else if (min !== undefined) { | ||||
|                              label = "> "+min; | ||||
|                          } else if (max !== undefined) { | ||||
|                              label = "< "+max; | ||||
|                          } | ||||
|                          $('<span>').css("margin-left","15px").text(label).appendTo(input); | ||||
|                      }, | ||||
|                      expand: { | ||||
|                          icon: "fa-caret-down", | ||||
|                          content: function(container) { | ||||
|                              var content = $('<div class="red-ui-editor-subflow-ui-edit-panel">').appendTo(container); | ||||
|                              content.css("padding","8px 5px") | ||||
|                              var min = ui.opts.min; | ||||
|                              var max = ui.opts.max; | ||||
|                              var minInput = $('<input type="number" style="margin-bottom:0; width:60px">'); | ||||
|                              minInput.val(min); | ||||
|                              var maxInput = $('<input type="number" style="margin-bottom:0; width:60px">'); | ||||
|                              maxInput.val(max); | ||||
|                              $('<div class="form-row" style="margin-bottom:3px"><label>'+RED._("editor.spinner.min")+'</label></div>').append(minInput).appendTo(content); | ||||
|                              $('<div class="form-row" style="margin-bottom:0"><label>'+RED._("editor.spinner.max")+'</label></div>').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()) | ||||
|              } | ||||
|                         var min = ui.opts && ui.opts.min; | ||||
|                         var max = ui.opts && ui.opts.max; | ||||
|                         var label = ""; | ||||
|                         if (min !== undefined && max !== undefined) { | ||||
|                             label = Math.min(min,max)+" - "+Math.max(min,max); | ||||
|                         } else if (min !== undefined) { | ||||
|                             label = "> "+min; | ||||
|                         } else if (max !== undefined) { | ||||
|                             label = "< "+max; | ||||
|                         } | ||||
|                         $('<span>').css("margin-left","15px").text(label).appendTo(input); | ||||
|                     }, | ||||
|                     expand: { | ||||
|                         icon: "fa-caret-down", | ||||
|                         content: function(container) { | ||||
|                             var content = $('<div class="red-ui-editor-subflow-ui-edit-panel">').appendTo(container); | ||||
|                             content.css("padding","8px 5px") | ||||
|                             var min = ui.opts.min; | ||||
|                             var max = ui.opts.max; | ||||
|                             var minInput = $('<input type="number" style="margin-bottom:0; width:60px">'); | ||||
|                             minInput.val(min); | ||||
|                             var maxInput = $('<input type="number" style="margin-bottom:0; width:60px">'); | ||||
|                             maxInput.val(max); | ||||
|                             $('<div class="form-row" style="margin-bottom:3px"><label>'+RED._("editor.spinner.min")+'</label></div>').append(minInput).appendTo(content); | ||||
|                             $('<div class="form-row" style="margin-bottom:0"><label>'+RED._("editor.spinner.max")+'</label></div>').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 = $('<input type="password">').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 = $("<div/>", { 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 = $('<form class="dialog-form form-horizontal"></form>').appendTo(container); | ||||
|         var listContainer = $('<div class="form-row node-input-env-container-row"></div>').appendTo(form); | ||||
|         var list = $('<ol id="red-ui-editor-subflow-env-list" class="red-ui-editor-subflow-env-list"></ol>').appendTo(listContainer); | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|   | ||||
| @@ -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; | ||||
|                 } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user