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>
This commit is contained in:
Mutasem Aldmour
2021-09-11 10:15:36 +02:00
committed by GitHub
parent 63e2bd25c9
commit 3d6b40b852
109 changed files with 3554 additions and 1933 deletions

View File

@@ -1,27 +1,30 @@
<template>
<div v-if="credentialTypesNodeDescriptionDisplayed.length" class="node-credentials">
<credentials-edit :dialogVisible="credentialNewDialogVisible" :editCredentials="editCredentials" :setCredentialType="addType" :nodesInit="nodesInit" :node="node" @closeDialog="closeCredentialNewDialog" @credentialsCreated="credentialsCreated" @credentialsUpdated="credentialsUpdated"></credentials-edit>
<div class="headline">
Credentials
</div>
<div v-for="credentialTypeDescription in credentialTypesNodeDescriptionDisplayed" :key="credentialTypeDescription.name" class="credential-data">
<el-row v-if="displayCredentials(credentialTypeDescription)" class="credential-parameter-wrapper">
<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 v-model="credentials[credentialTypeDescription.name]" :disabled="isReadOnly" @change="credentialSelected(credentialTypeDescription.name)" placeholder="Select Credential" size="small">
<n8n-select :value="selected[credentialTypeDescription.name]" :disabled="isReadOnly" @change="(value) => credentialSelected(credentialTypeDescription.name, value)" placeholder="Select Credential" size="small">
<n8n-option
v-for="(item, index) in credentialOptions[credentialTypeDescription.name]"
:key="item.name + '_' + index"
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>
@@ -34,7 +37,7 @@
</el-col>
<el-col :span="2" class="parameter-value credential-action">
<font-awesome-icon v-if="credentials[credentialTypeDescription.name]" icon="pen" @click="updateCredentials(credentialTypeDescription.name)" class="update-credentials clickable" title="Update Credentials" />
<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>
@@ -44,12 +47,8 @@
</template>
<script lang="ts">
import Vue from 'vue';
import { restApi } from '@/components/mixins/restApi';
import {
ICredentialsCreatedEvent,
ICredentialsResponse,
INodeUi,
INodeUpdatePropertiesInformation,
} from '@/Interface';
@@ -59,15 +58,16 @@ import {
INodeTypeDescription,
} from 'n8n-workflow';
import CredentialsEdit from '@/components/CredentialsEdit.vue';
import ParameterInput from '@/components/ParameterInput.vue';
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,
@@ -78,11 +78,16 @@ export default mixins(
props: [
'node', // INodeUi
],
components: {
CredentialsEdit,
ParameterInput,
data () {
return {
NEW_CREDENTIALS_TEXT,
newCredentialUnsubscribe: null as null | (() => void),
};
},
computed: {
...mapGetters('credentials', {
credentialOptions: 'allCredentialsByType',
}),
credentialTypesNode (): string[] {
return this.credentialTypesNodeDescription
.map((credentialTypeDescription) => credentialTypeDescription.name);
@@ -109,52 +114,16 @@ export default mixins(
} = {};
let credentialType: ICredentialType | null;
for (const credentialTypeName of this.credentialTypesNode) {
credentialType = this.$store.getters.credentialType(credentialTypeName);
credentialType = this.$store.getters['credentials/getCredentialTypeByName'](credentialTypeName);
returnData[credentialTypeName] = credentialType !== null ? credentialType.displayName : credentialTypeName;
}
return returnData;
},
},
data () {
return {
addType: undefined as string | undefined,
credentialNewDialogVisible: false,
credentialOptions: {} as { [key: string]: ICredentialsResponse[]; },
credentials: {} as {
[key: string]: string | undefined
},
editCredentials: null as object | null, // Credentials filter
newCredentialText: '- Create New -',
nodesInit: undefined as string[] | undefined,
};
},
watch: {
node () {
this.init();
selected(): {[type: string]: string} {
return this.node.credentials || {};
},
},
methods: {
closeCredentialNewDialog () {
this.credentialNewDialogVisible = false;
},
async credentialsCreated (eventData: ICredentialsCreatedEvent) {
await this.credentialsUpdated(eventData);
},
credentialsUpdated (eventData: ICredentialsCreatedEvent) {
if (!this.credentialTypesNode.includes(eventData.data.type)) {
return;
}
this.init();
Vue.set(this.credentials, eventData.data.type, eventData.data.name);
// Makes sure that it does also get set correctly on the node not just the UI
this.credentialSelected(eventData.data.type);
if (eventData.options.closeDialog === true) {
this.closeCredentialNewDialog();
}
},
credentialInputWrapperStyle (credentialType: string) {
let deductWidth = 0;
const styles = {
@@ -170,29 +139,54 @@ export default mixins(
return styles;
},
credentialSelected (credentialType: string) {
const credential = this.credentials[credentialType];
if (credential === this.newCredentialText) {
// New credentials should be created
this.addType = credentialType;
this.editCredentials = null;
this.nodesInit = [ (this.node as INodeUi).type ];
this.credentialNewDialogVisible = true;
this.credentials[credentialType] = undefined;
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 = this.node as INodeUi;
const node: INodeUi = this.node;
const credentials = {
...(node.credentials || {}),
[credentialType]: selected,
};
const updateInformation: INodeUpdatePropertiesInformation = {
name: node.name,
name: this.node.name,
properties: {
credentials: JSON.parse(JSON.stringify(this.credentials)),
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
@@ -200,6 +194,7 @@ export default mixins(
}
return this.displayParameter(this.node.parameters, credentialTypeDescription, '');
},
getIssues (credentialTypeName: string): string[] {
const node = this.node as INodeUi;
@@ -213,56 +208,25 @@ export default mixins(
return node.issues.credentials[credentialTypeName];
},
updateCredentials (credentialType: string): void {
const name = this.credentials[credentialType];
const credentialData = this.credentialOptions[credentialType].find((optionData: ICredentialsResponse) => optionData.name === name);
if (credentialData === undefined) {
this.$showMessage({
title: 'Credentials not found',
message: `The credentials named "${name}" of type "${credentialType}" could not be found!`,
type: 'error',
});
return;
}
const editCredentials = {
id: credentialData.id,
name,
type: credentialType,
};
isCredentialValid(credentialType: string): boolean {
const name = this.node.credentials[credentialType];
const options = this.credentialOptions[credentialType];
this.editCredentials = editCredentials;
this.addType = credentialType;
this.credentialNewDialogVisible = true;
return options.find((option: ICredentialType) => option.name === name);
},
init () {
const node = this.node as INodeUi;
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 });
const newOption = {
name: this.newCredentialText,
};
let options = [];
// Get the available credentials for each type
for (const credentialType of this.credentialTypesNode) {
options = this.$store.getters.credentialsByType(credentialType);
options.push(newOption as ICredentialsResponse);
Vue.set(this.credentialOptions, credentialType, options);
}
// Set the current node credentials
if (node.credentials) {
Vue.set(this, 'credentials', JSON.parse(JSON.stringify(node.credentials)));
} else {
Vue.set(this, 'credentials', {});
}
this.listenForNewCredentials(credentialType);
},
},
mounted () {
this.init();
beforeDestroy () {
this.stopListeningForNewCredentials();
},
});
</script>
@@ -317,6 +281,7 @@ export default mixins(
display: flex;
justify-content: center;
align-items: center;
color: var(--color-text-base);
}
}