Files
n8n-enterprise-unlocked/packages/editor-ui/src/components/NodeCredentials.vue
Mutasem Aldmour 3d6b40b852 Update credentials modal (#2154)
*  Generalize unique entity name generation

*  Standardize variable names

* redo credentials

* revert some changes, replace got with was

* fix v-if order

* fix v-if order

* update linting

* update gulpfile

* update ssh display name

* update height

* update params

* update info tip sizes

* address design comments

* update google button disabled

* update icon size to 28px

* update design issues

* update info tab design

* address design comments

* update tab size

* update run data spacing

* address comments, update logo design

* fix spacing issues

* clean up store

* fix create new bug

* add loading state

* rename prop

* remove unused prop

* fix select bug

* remove label tag

* update word break

* build

* address design comments

* update font family of button

* update menu opacity

* update text

* update title

* address more comments

* update oauth messages

* add oauth validation

* hide disabled state

* update warning modal

* show button on text input

* clean up cred details

* add validation errors

* fix bug when deleting cred

* Frontend hack to display test button

* Created interfaces for testing and endpoint

* Testing slack node credentials working

* Adding test with node to endpoint for credential testing

* Fixed linting and test detectability

* Adding required for slack token

* Added google sheets credential testing

* update message

* Adding suggestions by Ivan and Mutasem

* Address comments

* keep blurred when focused

* update font weight of errors

* add oauth banner

* remove toast

* Fixed code bug and added telegram credential testing

* scroll to top on success

* clean up duplication

* Fixed telegram trigger node and added tests to typeform

* refactor modal

* add more validation support

* refactor info tab

* scroll to bottom on save, handle cred saving

* refactor save button

* save cred on valid

* save cred on valid

* scroll to top if has error

* add targets on input labels

* delete credentails input

* revert fe changes

* update validation logic

* clean interface

* test credentials

* update banner design

* show testing state

* update x position

* fix issues

* fix focus issues

* clean up validation behavior

* make error relative

* update banner component

* update error spacing

* don't close dialog

* rename button

* update how banners behave

* if has unsaved changes first

* move confirm message

* add success banner

* update time state

* disable transitions

* test on open

* clean up banner behavior

* update banner styling

* capitalize

* update error banner styling to handle long texts

* avoid unnessary content jostling

* add loading label

* show validation warnings when opening modal

* retest cred if not all props req

* update scroll to auto

* add error warning

* update color saturation

* set overflow to auto

* fix bug to get credentials when connected

* round down to minutes

* change tab name

* update casing oauth

* disable credential testing if it has expressions

* label same as title

* add more space between close and save

* remove check on making any changes

* hide close on confirm modals

* don't accept clicks outside dialog

* fix build issues

* undo test changes

* fix table scrollbar logs

* rename modals

* fix bug with same name

* refactor modal

* fix tslint issue

* refactor name

* update name behavior

* update monospace font

* remove comment

* refactor inputs

* refactor error handling

* reduce spacing changes

* fix doc url oauth1 oauth2

* build

* hide infotip if no inputs

* address most comments

* rename file

* fix menu alignment

* gst

* update types

Co-authored-by: Iván Ovejero <ivov.src@gmail.com>
Co-authored-by: Omar Ajoue <krynble@gmail.com>
2021-09-11 10:15:36 +02:00

289 lines
7.8 KiB
Vue

<template>
<div v-if="credentialTypesNodeDescriptionDisplayed.length" class="node-credentials">
<div class="headline">
Credentials
</div>
<div v-for="credentialTypeDescription in credentialTypesNodeDescriptionDisplayed" :key="credentialTypeDescription.name" class="credential-data">
<el-row class="credential-parameter-wrapper">
<el-col :span="10" class="parameter-name">
{{credentialTypeNames[credentialTypeDescription.name]}}:
</el-col>
<el-col :span="12" class="parameter-value" :class="getIssues(credentialTypeDescription.name).length?'has-issues':''">
<div :style="credentialInputWrapperStyle(credentialTypeDescription.name)">
<n8n-select :value="selected[credentialTypeDescription.name]" :disabled="isReadOnly" @change="(value) => credentialSelected(credentialTypeDescription.name, value)" placeholder="Select Credential" size="small">
<n8n-option
v-for="(item) in credentialOptions[credentialTypeDescription.name]"
:key="item.id"
:label="item.name"
:value="item.name">
</n8n-option>
<n8n-option
:key="NEW_CREDENTIALS_TEXT"
:value="NEW_CREDENTIALS_TEXT"
:label="NEW_CREDENTIALS_TEXT"
>
</n8n-option>
</n8n-select>
</div>
<div class="credential-issues">
<n8n-tooltip placement="top" >
<div slot="content" v-html="'Issues:<br />&nbsp;&nbsp;- ' + getIssues(credentialTypeDescription.name).join('<br />&nbsp;&nbsp;- ')"></div>
<font-awesome-icon icon="exclamation-triangle" />
</n8n-tooltip>
</div>
</el-col>
<el-col :span="2" class="parameter-value credential-action">
<font-awesome-icon v-if="selected[credentialTypeDescription.name] && isCredentialValid(credentialTypeDescription.name)" icon="pen" @click="editCredential(credentialTypeDescription.name)" class="update-credentials clickable" title="Update Credentials" />
</el-col>
</el-row>
</div>
</div>
</template>
<script lang="ts">
import { restApi } from '@/components/mixins/restApi';
import {
INodeUi,
INodeUpdatePropertiesInformation,
} from '@/Interface';
import {
ICredentialType,
INodeCredentialDescription,
INodeTypeDescription,
} from 'n8n-workflow';
import { genericHelpers } from '@/components/mixins/genericHelpers';
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
import { showMessage } from '@/components/mixins/showMessage';
import { mapGetters } from "vuex";
import mixins from 'vue-typed-mixins';
const NEW_CREDENTIALS_TEXT = '- Create New -';
export default mixins(
genericHelpers,
nodeHelpers,
restApi,
showMessage,
).extend({
name: 'NodeCredentials',
props: [
'node', // INodeUi
],
data () {
return {
NEW_CREDENTIALS_TEXT,
newCredentialUnsubscribe: null as null | (() => void),
};
},
computed: {
...mapGetters('credentials', {
credentialOptions: 'allCredentialsByType',
}),
credentialTypesNode (): string[] {
return this.credentialTypesNodeDescription
.map((credentialTypeDescription) => credentialTypeDescription.name);
},
credentialTypesNodeDescriptionDisplayed (): INodeCredentialDescription[] {
return this.credentialTypesNodeDescription
.filter((credentialTypeDescription) => {
return this.displayCredentials(credentialTypeDescription);
});
},
credentialTypesNodeDescription (): INodeCredentialDescription[] {
const node = this.node as INodeUi;
const activeNodeType = this.$store.getters.nodeType(node.type) as INodeTypeDescription;
if (activeNodeType && activeNodeType.credentials) {
return activeNodeType.credentials;
}
return [];
},
credentialTypeNames () {
const returnData: {
[key: string]: string;
} = {};
let credentialType: ICredentialType | null;
for (const credentialTypeName of this.credentialTypesNode) {
credentialType = this.$store.getters['credentials/getCredentialTypeByName'](credentialTypeName);
returnData[credentialTypeName] = credentialType !== null ? credentialType.displayName : credentialTypeName;
}
return returnData;
},
selected(): {[type: string]: string} {
return this.node.credentials || {};
},
},
methods: {
credentialInputWrapperStyle (credentialType: string) {
let deductWidth = 0;
const styles = {
width: '100%',
};
if (this.getIssues(credentialType).length) {
deductWidth += 20;
}
if (deductWidth !== 0) {
styles.width = `calc(100% - ${deductWidth}px)`;
}
return styles;
},
listenForNewCredentials(credentialType: string) {
this.stopListeningForNewCredentials();
this.newCredentialUnsubscribe = this.$store.subscribe((mutation, state) => {
if (mutation.type === 'credentials/upsertCredential' || mutation.type === 'credentials/enableOAuthCredential'){
this.credentialSelected(credentialType, mutation.payload.name);
}
if (mutation.type === 'credentials/deleteCredential') {
this.credentialSelected(credentialType, mutation.payload.name);
this.stopListeningForNewCredentials();
}
});
},
stopListeningForNewCredentials() {
if (this.newCredentialUnsubscribe) {
this.newCredentialUnsubscribe();
}
},
credentialSelected (credentialType: string, credentialName: string) {
let selected = undefined;
if (credentialName === NEW_CREDENTIALS_TEXT) {
this.listenForNewCredentials(credentialType);
this.$store.dispatch('ui/openNewCredential', { type: credentialType });
}
else {
selected = credentialName;
}
const node: INodeUi = this.node;
const credentials = {
...(node.credentials || {}),
[credentialType]: selected,
};
const updateInformation: INodeUpdatePropertiesInformation = {
name: this.node.name,
properties: {
credentials,
},
};
this.$emit('credentialSelected', updateInformation);
},
displayCredentials (credentialTypeDescription: INodeCredentialDescription): boolean {
if (credentialTypeDescription.displayOptions === undefined) {
// If it is not defined no need to do a proper check
return true;
}
return this.displayParameter(this.node.parameters, credentialTypeDescription, '');
},
getIssues (credentialTypeName: string): string[] {
const node = this.node as INodeUi;
if (node.issues === undefined || node.issues.credentials === undefined) {
return [];
}
if (!node.issues.credentials.hasOwnProperty(credentialTypeName)) {
return [];
}
return node.issues.credentials[credentialTypeName];
},
isCredentialValid(credentialType: string): boolean {
const name = this.node.credentials[credentialType];
const options = this.credentialOptions[credentialType];
return options.find((option: ICredentialType) => option.name === name);
},
editCredential(credentialType: string): void {
const name = this.node.credentials[credentialType];
const options = this.credentialOptions[credentialType];
const selected = options.find((option: ICredentialType) => option.name === name);
this.$store.dispatch('ui/openExisitngCredential', { id: selected.id });
this.listenForNewCredentials(credentialType);
},
},
beforeDestroy () {
this.stopListeningForNewCredentials();
},
});
</script>
<style lang="scss">
.node-credentials {
padding-bottom: 1em;
margin: 0.5em;
border-bottom: 1px solid #ccc;
.credential-issues {
display: none;
width: 20px;
text-align: right;
float: right;
color: #ff8080;
font-size: 1.2em;
margin-top: 3px;
}
.credential-data + .credential-data {
margin-top: 1em;
}
.has-issues {
.credential-issues {
display: inline-block;
}
}
.headline {
font-weight: bold;
margin-bottom: 0.7em;
}
.credential-parameter-wrapper {
display: flex;
align-items: center;
}
.parameter-name {
font-weight: 400;
}
.parameter-value {
display: flex;
align-items: center;
}
.credential-action {
display: flex;
justify-content: center;
align-items: center;
color: var(--color-text-base);
}
}
</style>