mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
⚡ Refactor credentials dropdown for HTTP Request node (#3222)
This commit is contained in:
parent
30c6940135
commit
47185d16f6
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="credentialTypesNodeDescriptionDisplayed.length" :class="$style.container">
|
<div v-if="credentialTypesNodeDescriptionDisplayed.length" :class="['node-credentials', $style.container]">
|
||||||
<div v-for="credentialTypeDescription in credentialTypesNodeDescriptionDisplayed" :key="credentialTypeDescription.name">
|
<div v-for="credentialTypeDescription in credentialTypesNodeDescriptionDisplayed" :key="credentialTypeDescription.name">
|
||||||
<n8n-input-label
|
<n8n-input-label
|
||||||
:label="$locale.baseText(
|
:label="$locale.baseText(
|
||||||
|
@ -11,15 +11,20 @@
|
||||||
}
|
}
|
||||||
)"
|
)"
|
||||||
:bold="false"
|
:bold="false"
|
||||||
size="small"
|
|
||||||
|
|
||||||
:set="issues = getIssues(credentialTypeDescription.name)"
|
:set="issues = getIssues(credentialTypeDescription.name)"
|
||||||
|
size="small"
|
||||||
>
|
>
|
||||||
<div v-if="isReadOnly">
|
<div v-if="isReadOnly">
|
||||||
<n8n-input disabled :value="selected && selected[credentialTypeDescription.name] && selected[credentialTypeDescription.name].name" size="small" />
|
<n8n-input
|
||||||
|
:value="selected && selected[credentialTypeDescription.name] && selected[credentialTypeDescription.name].name"
|
||||||
|
disabled
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
<div :class="issues.length ? $style.hasIssues : $style.input" v-else >
|
v-else
|
||||||
|
:class="issues.length ? $style.hasIssues : $style.input"
|
||||||
|
>
|
||||||
<n8n-select :value="getSelectedId(credentialTypeDescription.name)" @change="(value) => onCredentialSelected(credentialTypeDescription.name, value)" :placeholder="$locale.baseText('nodeCredentials.selectCredential')" size="small">
|
<n8n-select :value="getSelectedId(credentialTypeDescription.name)" @change="(value) => onCredentialSelected(credentialTypeDescription.name, value)" :placeholder="$locale.baseText('nodeCredentials.selectCredential')" size="small">
|
||||||
<n8n-option
|
<n8n-option
|
||||||
v-for="(item) in credentialOptions[credentialTypeDescription.name]"
|
v-for="(item) in credentialOptions[credentialTypeDescription.name]"
|
||||||
|
@ -92,7 +97,16 @@ export default mixins(
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters('credentials', {
|
...mapGetters('credentials', {
|
||||||
credentialOptions: 'allCredentialsByType',
|
credentialOptions: 'allCredentialsByType',
|
||||||
|
getCredentialTypeByName: 'getCredentialTypeByName',
|
||||||
}),
|
}),
|
||||||
|
isProxyAuth(): boolean {
|
||||||
|
return this.isHttpRequestNodeV2(this.node) &&
|
||||||
|
this.node.parameters.authenticateWith === 'nodeCredential';
|
||||||
|
},
|
||||||
|
isGenericAuth(): boolean {
|
||||||
|
return this.isHttpRequestNodeV2(this.node) &&
|
||||||
|
this.node.parameters.authenticateWith === 'genericAuth';
|
||||||
|
},
|
||||||
credentialTypesNode (): string[] {
|
credentialTypesNode (): string[] {
|
||||||
return this.credentialTypesNodeDescription
|
return this.credentialTypesNodeDescription
|
||||||
.map((credentialTypeDescription) => credentialTypeDescription.name);
|
.map((credentialTypeDescription) => credentialTypeDescription.name);
|
||||||
|
@ -106,6 +120,18 @@ export default mixins(
|
||||||
credentialTypesNodeDescription (): INodeCredentialDescription[] {
|
credentialTypesNodeDescription (): INodeCredentialDescription[] {
|
||||||
const node = this.node as INodeUi;
|
const node = this.node as INodeUi;
|
||||||
|
|
||||||
|
if (this.isGenericAuth) {
|
||||||
|
const { genericAuthType } = this.node.parameters as { genericAuthType: string };
|
||||||
|
|
||||||
|
return [this.getCredentialTypeByName(genericAuthType)];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isProxyAuth) {
|
||||||
|
const { nodeCredentialType } = this.node.parameters as { nodeCredentialType?: string };
|
||||||
|
|
||||||
|
if (nodeCredentialType) return [this.getCredentialTypeByName(nodeCredentialType)];
|
||||||
|
}
|
||||||
|
|
||||||
const activeNodeType = this.$store.getters.nodeType(node.type, node.typeVersion) as INodeTypeDescription | null;
|
const activeNodeType = this.$store.getters.nodeType(node.type, node.typeVersion) as INodeTypeDescription | null;
|
||||||
if (activeNodeType && activeNodeType.credentials) {
|
if (activeNodeType && activeNodeType.credentials) {
|
||||||
return activeNodeType.credentials;
|
return activeNodeType.credentials;
|
||||||
|
@ -295,7 +321,7 @@ export default mixins(
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
.container {
|
.container {
|
||||||
margin: var(--spacing-xs) 0;
|
margin: 0;
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
margin-bottom: var(--spacing-xs);
|
margin-bottom: var(--spacing-xs);
|
||||||
|
|
|
@ -23,9 +23,20 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="node-parameters-wrapper" v-if="node && nodeValid">
|
<div class="node-parameters-wrapper" v-if="node && nodeValid">
|
||||||
<div v-show="openPanel === 'params'">
|
<div v-show="openPanel === 'params'">
|
||||||
<node-credentials :node="node" @credentialSelected="credentialSelected"></node-credentials>
|
<node-webhooks
|
||||||
<node-webhooks :node="node" :nodeType="nodeType" />
|
:node="node"
|
||||||
<parameter-input-list :parameters="parametersNoneSetting" :hideDelete="true" :nodeValues="nodeValues" path="parameters" @valueChanged="valueChanged" />
|
:nodeType="nodeType"
|
||||||
|
/>
|
||||||
|
<parameter-input-list
|
||||||
|
:parameters="parametersNoneSetting"
|
||||||
|
:hideDelete="true"
|
||||||
|
:nodeValues="nodeValues" path="parameters" @valueChanged="valueChanged"
|
||||||
|
>
|
||||||
|
<node-credentials
|
||||||
|
:node="node"
|
||||||
|
@credentialSelected="credentialSelected"
|
||||||
|
/>
|
||||||
|
</parameter-input-list>
|
||||||
<div v-if="parametersNoneSetting.length === 0" class="no-parameters">
|
<div v-if="parametersNoneSetting.length === 0" class="no-parameters">
|
||||||
<n8n-text>
|
<n8n-text>
|
||||||
{{ $locale.baseText('nodeSettings.thisNodeDoesNotHaveAnyParameters') }}
|
{{ $locale.baseText('nodeSettings.thisNodeDoesNotHaveAnyParameters') }}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="paramter-input-list-wrapper">
|
<div class="parameter-input-list-wrapper">
|
||||||
<div v-for="parameter in filteredParameters" :key="parameter.name" :class="{indent}">
|
<div v-for="(parameter, index) in filteredParameters" :key="parameter.name">
|
||||||
|
<slot v-if="indexToShowSlotAt === index" />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="multipleValues(parameter) === true && parameter.type !== 'fixedCollection'"
|
v-if="multipleValues(parameter) === true && parameter.type !== 'fixedCollection'"
|
||||||
class="parameter-item"
|
class="parameter-item"
|
||||||
|
@ -129,6 +131,11 @@ export default mixins(
|
||||||
node (): INodeUi {
|
node (): INodeUi {
|
||||||
return this.$store.getters.activeNode;
|
return this.$store.getters.activeNode;
|
||||||
},
|
},
|
||||||
|
indexToShowSlotAt (): number {
|
||||||
|
if (this.isHttpRequestNodeV2(this.node)) return 2;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
multipleValues (parameter: INodeProperties): boolean {
|
multipleValues (parameter: INodeProperties): boolean {
|
||||||
|
@ -260,7 +267,12 @@ export default mixins(
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.paramter-input-list-wrapper {
|
.parameter-input-list-wrapper {
|
||||||
|
|
||||||
|
div:first-child > .node-credentials {
|
||||||
|
padding-top: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
.delete-option {
|
.delete-option {
|
||||||
display: none;
|
display: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
HTTP_REQUEST_NODE_TYPE,
|
||||||
PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
|
PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
|
||||||
} from '@/constants';
|
} from '@/constants';
|
||||||
|
|
||||||
|
@ -32,12 +33,19 @@ import { restApi } from '@/components/mixins/restApi';
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
|
|
||||||
import mixins from 'vue-typed-mixins';
|
import mixins from 'vue-typed-mixins';
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
|
||||||
export const nodeHelpers = mixins(
|
export const nodeHelpers = mixins(
|
||||||
restApi,
|
restApi,
|
||||||
)
|
)
|
||||||
.extend({
|
.extend({
|
||||||
|
computed: {
|
||||||
|
...mapGetters('credentials', [ 'getCredentialTypeByName', 'getCredentialsByType' ]),
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
isHttpRequestNodeV2 (node: INodeUi): boolean {
|
||||||
|
return node.type === HTTP_REQUEST_NODE_TYPE && node.typeVersion === 2;
|
||||||
|
},
|
||||||
|
|
||||||
// Returns the parameter value
|
// Returns the parameter value
|
||||||
getParameterValue (nodeValues: INodeParameters, parameterName: string, path: string) {
|
getParameterValue (nodeValues: INodeParameters, parameterName: string, path: string) {
|
||||||
|
@ -116,6 +124,23 @@ export const nodeHelpers = mixins(
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
reportUnsetCredential(credentialType: ICredentialType) {
|
||||||
|
return {
|
||||||
|
credentials: {
|
||||||
|
[credentialType.name]: [
|
||||||
|
this.$locale.baseText(
|
||||||
|
'nodeHelpers.credentialsUnset',
|
||||||
|
{
|
||||||
|
interpolate: {
|
||||||
|
credentialType: credentialType.displayName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
// Updates the execution issues.
|
// Updates the execution issues.
|
||||||
updateNodesExecutionIssues () {
|
updateNodesExecutionIssues () {
|
||||||
const nodes = this.$store.getters.allNodes;
|
const nodes = this.$store.getters.allNodes;
|
||||||
|
@ -198,6 +223,46 @@ export const nodeHelpers = mixins(
|
||||||
let credentialType: ICredentialType | null;
|
let credentialType: ICredentialType | null;
|
||||||
let credentialDisplayName: string;
|
let credentialDisplayName: string;
|
||||||
let selectedCredentials: INodeCredentialsDetails;
|
let selectedCredentials: INodeCredentialsDetails;
|
||||||
|
|
||||||
|
const {
|
||||||
|
authenticateWith,
|
||||||
|
genericAuthType,
|
||||||
|
nodeCredentialType,
|
||||||
|
} = node.parameters as HttpRequestNode.V2.AuthParams;
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.isHttpRequestNodeV2(node) &&
|
||||||
|
authenticateWith === 'genericAuth' &&
|
||||||
|
selectedCredsAreUnusable(node, genericAuthType)
|
||||||
|
) {
|
||||||
|
const credential = this.getCredentialTypeByName(genericAuthType);
|
||||||
|
return this.reportUnsetCredential(credential);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.isHttpRequestNodeV2(node) &&
|
||||||
|
authenticateWith === 'nodeCredential' &&
|
||||||
|
nodeCredentialType !== '' &&
|
||||||
|
node.credentials !== undefined
|
||||||
|
) {
|
||||||
|
const stored = this.getCredentialsByType(nodeCredentialType);
|
||||||
|
|
||||||
|
if (selectedCredsDoNotExist(node, nodeCredentialType, stored)) {
|
||||||
|
const credential = this.getCredentialTypeByName(nodeCredentialType);
|
||||||
|
return this.reportUnsetCredential(credential);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.isHttpRequestNodeV2(node) &&
|
||||||
|
authenticateWith === 'nodeCredential' &&
|
||||||
|
nodeCredentialType !== '' &&
|
||||||
|
selectedCredsAreUnusable(node, nodeCredentialType)
|
||||||
|
) {
|
||||||
|
const credential = this.getCredentialTypeByName(nodeCredentialType);
|
||||||
|
return this.reportUnsetCredential(credential);
|
||||||
|
}
|
||||||
|
|
||||||
for (const credentialTypeDescription of nodeType!.credentials!) {
|
for (const credentialTypeDescription of nodeType!.credentials!) {
|
||||||
// Check if credentials should be displayed else ignore
|
// Check if credentials should be displayed else ignore
|
||||||
if (this.displayParameter(node.parameters, credentialTypeDescription, '', node) !== true) {
|
if (this.displayParameter(node.parameters, credentialTypeDescription, '', node) !== true) {
|
||||||
|
@ -394,3 +459,39 @@ export const nodeHelpers = mixins(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the node has no selected credentials, or none of the node's
|
||||||
|
* selected credentials are of the specified type.
|
||||||
|
*/
|
||||||
|
function selectedCredsAreUnusable(node: INodeUi, credentialType: string) {
|
||||||
|
return node.credentials === undefined || Object.keys(node.credentials).includes(credentialType) === false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the node's selected credentials of the specified type
|
||||||
|
* can no longer be found in the database.
|
||||||
|
*/
|
||||||
|
function selectedCredsDoNotExist(
|
||||||
|
node: INodeUi,
|
||||||
|
nodeCredentialType: string,
|
||||||
|
storedCredsByType: ICredentialsResponse[] | null,
|
||||||
|
) {
|
||||||
|
if (!node.credentials || !storedCredsByType) return false;
|
||||||
|
|
||||||
|
const selectedCredsByType = node.credentials[nodeCredentialType];
|
||||||
|
|
||||||
|
if (!selectedCredsByType) return false;
|
||||||
|
|
||||||
|
return !storedCredsByType.find((c) => c.id === selectedCredsByType.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
declare namespace HttpRequestNode {
|
||||||
|
namespace V2 {
|
||||||
|
type AuthParams = {
|
||||||
|
authenticateWith: 'none' | 'genericAuth' | 'nodeCredential';
|
||||||
|
genericAuthType: string;
|
||||||
|
nodeCredentialType: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -330,6 +330,11 @@ export const workflowHelpers = mixins(
|
||||||
if (node.credentials !== undefined && nodeType.credentials !== undefined) {
|
if (node.credentials !== undefined && nodeType.credentials !== undefined) {
|
||||||
const saveCredenetials: INodeCredentials = {};
|
const saveCredenetials: INodeCredentials = {};
|
||||||
for (const nodeCredentialTypeName of Object.keys(node.credentials)) {
|
for (const nodeCredentialTypeName of Object.keys(node.credentials)) {
|
||||||
|
if (this.isHttpRequestNodeV2(node)) {
|
||||||
|
saveCredenetials[nodeCredentialTypeName] = node.credentials[nodeCredentialTypeName];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const credentialTypeDescription = nodeType.credentials
|
const credentialTypeDescription = nodeType.credentials
|
||||||
.find((credentialTypeDescription) => credentialTypeDescription.name === nodeCredentialTypeName);
|
.find((credentialTypeDescription) => credentialTypeDescription.name === nodeCredentialTypeName);
|
||||||
|
|
||||||
|
|
|
@ -387,6 +387,7 @@
|
||||||
"nodeErrorView.stack": "Stack",
|
"nodeErrorView.stack": "Stack",
|
||||||
"nodeErrorView.theErrorCauseIsTooLargeToBeDisplayed": "The error cause is too large to be displayed",
|
"nodeErrorView.theErrorCauseIsTooLargeToBeDisplayed": "The error cause is too large to be displayed",
|
||||||
"nodeErrorView.time": "Time",
|
"nodeErrorView.time": "Time",
|
||||||
|
"nodeHelpers.credentialsUnset": "Credentials for '{credentialType}' are not set.",
|
||||||
"nodeSettings.alwaysOutputData.description": "If active, will output a single, empty item when the output would have been empty. Use to prevent the workflow finishing on this node.",
|
"nodeSettings.alwaysOutputData.description": "If active, will output a single, empty item when the output would have been empty. Use to prevent the workflow finishing on this node.",
|
||||||
"nodeSettings.alwaysOutputData.displayName": "Always Output Data",
|
"nodeSettings.alwaysOutputData.displayName": "Always Output Data",
|
||||||
"nodeSettings.clickOnTheQuestionMarkIcon": "Click the '?' icon to open this node on n8n.io",
|
"nodeSettings.clickOnTheQuestionMarkIcon": "Click the '?' icon to open this node on n8n.io",
|
||||||
|
|
|
@ -52,7 +52,7 @@ export class HttpRequest implements INodeType {
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
authenticateWith: [
|
authenticateWith: [
|
||||||
'basicAuth',
|
'httpBasicAuth',
|
||||||
],
|
],
|
||||||
'@version': [
|
'@version': [
|
||||||
2,
|
2,
|
||||||
|
@ -66,7 +66,7 @@ export class HttpRequest implements INodeType {
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
authenticateWith: [
|
authenticateWith: [
|
||||||
'digestAuth',
|
'httpDigestAuth',
|
||||||
],
|
],
|
||||||
'@version': [
|
'@version': [
|
||||||
2,
|
2,
|
||||||
|
@ -80,7 +80,7 @@ export class HttpRequest implements INodeType {
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
authenticateWith: [
|
authenticateWith: [
|
||||||
'headerAuth',
|
'httpHeaderAuth',
|
||||||
],
|
],
|
||||||
'@version': [
|
'@version': [
|
||||||
2,
|
2,
|
||||||
|
@ -94,7 +94,7 @@ export class HttpRequest implements INodeType {
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
authenticateWith: [
|
authenticateWith: [
|
||||||
'queryAuth',
|
'httpQueryAuth',
|
||||||
],
|
],
|
||||||
'@version': [
|
'@version': [
|
||||||
2,
|
2,
|
||||||
|
@ -108,7 +108,7 @@ export class HttpRequest implements INodeType {
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
authenticateWith: [
|
authenticateWith: [
|
||||||
'oAuth1',
|
'oAuth1Api',
|
||||||
],
|
],
|
||||||
'@version': [
|
'@version': [
|
||||||
2,
|
2,
|
||||||
|
@ -122,7 +122,7 @@ export class HttpRequest implements INodeType {
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
authenticateWith: [
|
authenticateWith: [
|
||||||
'oAuth2',
|
'oAuth2Api',
|
||||||
],
|
],
|
||||||
'@version': [
|
'@version': [
|
||||||
2,
|
2,
|
||||||
|
@ -279,30 +279,30 @@ export class HttpRequest implements INodeType {
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: 'Basic Auth',
|
name: 'Basic Auth',
|
||||||
value: 'basicAuth',
|
value: 'httpBasicAuth',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Digest Auth',
|
name: 'Digest Auth',
|
||||||
value: 'digestAuth',
|
value: 'httpDigestAuth',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Header Auth',
|
name: 'Header Auth',
|
||||||
value: 'headerAuth',
|
value: 'httpHeaderAuth',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Query Auth',
|
name: 'Query Auth',
|
||||||
value: 'queryAuth',
|
value: 'httpQueryAuth',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'OAuth1',
|
name: 'OAuth1',
|
||||||
value: 'oAuth1',
|
value: 'oAuth1Api',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'OAuth2',
|
name: 'OAuth2',
|
||||||
value: 'oAuth2',
|
value: 'oAuth2Api',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
default: 'basicAuth',
|
default: 'httpBasicAuth',
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
authenticateWith: [
|
authenticateWith: [
|
||||||
|
|
Loading…
Reference in a new issue