mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
✨ 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:
@@ -0,0 +1,853 @@
|
||||
<template>
|
||||
<Modal
|
||||
:name="modalName"
|
||||
size="lg"
|
||||
:customClass="$style.credentialModal"
|
||||
:eventBus="modalBus"
|
||||
:loading="loading"
|
||||
:beforeClose="beforeClose"
|
||||
>
|
||||
<template slot="header">
|
||||
<div v-if="credentialType" :class="$style.header">
|
||||
<div :class="$style.credInfo">
|
||||
<div :class="$style.credIcon">
|
||||
<CredentialIcon :credentialTypeName="credentialTypeName" />
|
||||
</div>
|
||||
<InlineNameEdit
|
||||
:name="credentialName"
|
||||
:subtitle="credentialType.displayName"
|
||||
type="Credential"
|
||||
@input="onNameEdit"
|
||||
/>
|
||||
</div>
|
||||
<div :class="$style.credActions">
|
||||
<n8n-icon-button
|
||||
v-if="currentCredential"
|
||||
size="medium"
|
||||
title="Delete"
|
||||
icon="trash"
|
||||
type="text"
|
||||
:disabled="isSaving"
|
||||
:loading="isDeleting"
|
||||
@click="deleteCredential"
|
||||
/>
|
||||
<SaveButton
|
||||
v-if="hasUnsavedChanges || credentialId"
|
||||
:saved="!hasUnsavedChanges && !isTesting"
|
||||
:isSaving="isSaving || isTesting"
|
||||
:savingLabel="isTesting ? 'Testing' : 'Saving'"
|
||||
@click="saveCredential"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
</template>
|
||||
<template slot="content">
|
||||
<div :class="$style.container">
|
||||
<div :class="$style.sidebar">
|
||||
<n8n-menu
|
||||
type="secondary"
|
||||
@select="onTabSelect"
|
||||
defaultActive="connection"
|
||||
:light="true"
|
||||
>
|
||||
<n8n-menu-item index="connection" :class="$style.credTab"
|
||||
><span slot="title">Connection</span></n8n-menu-item
|
||||
>
|
||||
<n8n-menu-item index="details" :class="$style.credTab"
|
||||
><span slot="title">Details</span></n8n-menu-item
|
||||
>
|
||||
</n8n-menu>
|
||||
</div>
|
||||
<div v-if="activeTab === 'connection'" :class="$style.mainContent" ref="content">
|
||||
<CredentialConfig
|
||||
:credentialType="credentialType"
|
||||
:credentialProperties="credentialProperties"
|
||||
:credentialData="credentialData"
|
||||
:showValidationWarning="showValidationWarning"
|
||||
:authError="authError"
|
||||
:testedSuccessfully="testedSuccessfully"
|
||||
:isOAuthType="isOAuthType"
|
||||
:isOAuthConnected="isOAuthConnected"
|
||||
:isRetesting="isRetesting"
|
||||
:parentTypes="parentTypes"
|
||||
:requiredPropertiesFilled="requiredPropertiesFilled"
|
||||
@change="onDataChange"
|
||||
@oauth="oAuthCredentialAuthorize"
|
||||
@retest="retestCredential"
|
||||
@scrollToTop="scrollToTop"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="activeTab === 'details'" :class="$style.mainContent">
|
||||
<CredentialInfo
|
||||
:nodeAccess="nodeAccess"
|
||||
:nodesWithAccess="nodesWithAccess"
|
||||
:currentCredential="currentCredential"
|
||||
@accessChange="onNodeAccessChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
import {
|
||||
ICredentialsDecryptedResponse,
|
||||
ICredentialsResponse,
|
||||
} from '@/Interface';
|
||||
|
||||
import {
|
||||
CredentialInformation,
|
||||
ICredentialDataDecryptedObject,
|
||||
ICredentialNodeAccess,
|
||||
ICredentialsDecrypted,
|
||||
ICredentialType,
|
||||
INodeParameters,
|
||||
INodeProperties,
|
||||
INodeTypeDescription,
|
||||
NodeCredentialTestResult,
|
||||
NodeHelpers,
|
||||
} from 'n8n-workflow';
|
||||
import CredentialIcon from '../CredentialIcon.vue';
|
||||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
import { nodeHelpers } from '../mixins/nodeHelpers';
|
||||
import { showMessage } from '../mixins/showMessage';
|
||||
|
||||
import CredentialConfig from './CredentialConfig.vue';
|
||||
import CredentialInfo from './CredentialInfo.vue';
|
||||
import SaveButton from '../SaveButton.vue';
|
||||
import Modal from '../Modal.vue';
|
||||
import InlineNameEdit from '../InlineNameEdit.vue';
|
||||
|
||||
interface NodeAccessMap {
|
||||
[nodeType: string]: ICredentialNodeAccess | null;
|
||||
}
|
||||
|
||||
export default mixins(showMessage, nodeHelpers).extend({
|
||||
name: 'CredentialsDetail',
|
||||
components: {
|
||||
CredentialConfig,
|
||||
CredentialIcon,
|
||||
CredentialInfo,
|
||||
InlineNameEdit,
|
||||
Modal,
|
||||
SaveButton,
|
||||
},
|
||||
props: {
|
||||
modalName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
activeId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'connection',
|
||||
authError: '',
|
||||
credentialId: '',
|
||||
credentialName: '',
|
||||
credentialData: {} as ICredentialDataDecryptedObject,
|
||||
modalBus: new Vue(),
|
||||
nodeAccess: {} as NodeAccessMap,
|
||||
isDeleting: false,
|
||||
isSaving: false,
|
||||
isTesting: false,
|
||||
hasUnsavedChanges: false,
|
||||
loading: true,
|
||||
showValidationWarning: false,
|
||||
testedSuccessfully: false,
|
||||
isRetesting: false,
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
this.nodeAccess = this.nodesWithAccess.reduce(
|
||||
(accu: NodeAccessMap, node: { name: string }) => {
|
||||
if (this.mode === 'new') {
|
||||
accu[node.name] = { nodeType: node.name }; // enable all nodes by default
|
||||
} else {
|
||||
accu[node.name] = null;
|
||||
}
|
||||
|
||||
return accu;
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
if (this.mode === 'new') {
|
||||
this.credentialName = await this.$store.dispatch(
|
||||
'credentials/getNewCredentialName',
|
||||
{ credentialTypeName: this.credentialTypeName },
|
||||
);
|
||||
} else {
|
||||
await this.loadCurrentCredential();
|
||||
}
|
||||
|
||||
if (this.credentialType) {
|
||||
for (const property of this.credentialType.properties) {
|
||||
if (!this.credentialData.hasOwnProperty(property.name)) {
|
||||
this.credentialData[property.name] =
|
||||
property.default as CredentialInformation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.credentialId) {
|
||||
if (!this.requiredPropertiesFilled) {
|
||||
this.showValidationWarning = true;
|
||||
}
|
||||
else {
|
||||
this.retestCredential();
|
||||
}
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
},
|
||||
computed: {
|
||||
currentCredential(): ICredentialsResponse | null {
|
||||
if (!this.credentialId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.$store.getters['credentials/getCredentialById'](
|
||||
this.credentialId,
|
||||
);
|
||||
},
|
||||
credentialTypeName(): string | null {
|
||||
if (this.mode === 'edit') {
|
||||
if (this.currentCredential) {
|
||||
return this.currentCredential.type;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.activeId;
|
||||
},
|
||||
credentialType(): ICredentialType | null {
|
||||
if (!this.credentialTypeName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const type = this.$store.getters['credentials/getCredentialTypeByName'](
|
||||
this.credentialTypeName,
|
||||
);
|
||||
|
||||
return {
|
||||
...type,
|
||||
properties: this.getCredentialProperties(this.credentialTypeName),
|
||||
};
|
||||
},
|
||||
isCredentialTestable (): boolean {
|
||||
if (this.isOAuthType || !this.requiredPropertiesFilled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const hasExpressions = Object.values(this.credentialData).reduce((accu: boolean, value: CredentialInformation) => accu || (typeof value === 'string' && value.startsWith('=')), false);
|
||||
if (hasExpressions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const nodesThatCanTest = this.nodesWithAccess.filter(node => {
|
||||
if (node.credentials) {
|
||||
// Returns a list of nodes that can test this credentials
|
||||
const eligibleTesters = node.credentials.filter(credential => {
|
||||
return credential.name === this.credentialTypeName && credential.testedBy;
|
||||
});
|
||||
// If we have any node that can test, return true.
|
||||
return !!eligibleTesters.length;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
return !!nodesThatCanTest.length;
|
||||
},
|
||||
nodesWithAccess(): INodeTypeDescription[] {
|
||||
if (this.credentialTypeName) {
|
||||
return this.$store.getters['credentials/getNodesWithAccess'](
|
||||
this.credentialTypeName,
|
||||
);
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
parentTypes(): string[] {
|
||||
if (this.credentialTypeName) {
|
||||
return this.getParentTypes(this.credentialTypeName);
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
isOAuthType(): boolean {
|
||||
return !!this.credentialTypeName && (
|
||||
['oAuth1Api', 'oAuth2Api'].includes(this.credentialTypeName) ||
|
||||
this.parentTypes.includes('oAuth1Api') ||
|
||||
this.parentTypes.includes('oAuth2Api')
|
||||
);
|
||||
},
|
||||
isOAuthConnected(): boolean {
|
||||
return this.isOAuthType && !!this.credentialData.oauthTokenData;
|
||||
},
|
||||
credentialProperties(): INodeProperties[] {
|
||||
if (!this.credentialType) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.credentialType.properties.filter(
|
||||
(propertyData: INodeProperties) => {
|
||||
if (!this.displayCredentialParameter(propertyData)) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
!this.credentialType!.__overwrittenProperties ||
|
||||
!this.credentialType!.__overwrittenProperties.includes(
|
||||
propertyData.name,
|
||||
)
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
requiredPropertiesFilled(): boolean {
|
||||
for (const property of this.credentialProperties) {
|
||||
if (property.required !== true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!this.credentialData[property.name]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async beforeClose(done: () => void) {
|
||||
let keepEditing = false;
|
||||
|
||||
if (this.hasUnsavedChanges) {
|
||||
const displayName = this.credentialType ? this.credentialType.displayName : '';
|
||||
keepEditing = await this.confirmMessage(
|
||||
`Are you sure you want to throw away the changes you made to the ${displayName} credential?`,
|
||||
'Close without saving?',
|
||||
null,
|
||||
'Keep editing',
|
||||
'Close',
|
||||
);
|
||||
}
|
||||
else if (this.isOAuthType && !this.isOAuthConnected) {
|
||||
keepEditing = await this.confirmMessage(
|
||||
`You need to connect your credential for it to work`,
|
||||
'Close without connecting?',
|
||||
null,
|
||||
'Keep editing',
|
||||
'Close',
|
||||
);
|
||||
}
|
||||
|
||||
if (!keepEditing) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
else if (!this.requiredPropertiesFilled) {
|
||||
this.showValidationWarning = true;
|
||||
this.scrollToTop();
|
||||
}
|
||||
else if (this.isOAuthType) {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
},
|
||||
|
||||
displayCredentialParameter(parameter: INodeProperties): boolean {
|
||||
if (parameter.type === 'hidden') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parameter.displayOptions === undefined) {
|
||||
// If it is not defined no need to do a proper check
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.displayParameter(
|
||||
this.credentialData as INodeParameters,
|
||||
parameter,
|
||||
'',
|
||||
);
|
||||
},
|
||||
getCredentialProperties(name: string): INodeProperties[] {
|
||||
const credentialsData =
|
||||
this.$store.getters['credentials/getCredentialTypeByName'](name);
|
||||
|
||||
if (!credentialsData) {
|
||||
throw new Error(`Could not find credentials of type: ${name}`);
|
||||
}
|
||||
|
||||
if (credentialsData.extends === undefined) {
|
||||
return credentialsData.properties;
|
||||
}
|
||||
|
||||
const combineProperties = [] as INodeProperties[];
|
||||
for (const credentialsTypeName of credentialsData.extends) {
|
||||
const mergeCredentialProperties =
|
||||
this.getCredentialProperties(credentialsTypeName);
|
||||
NodeHelpers.mergeNodeProperties(
|
||||
combineProperties,
|
||||
mergeCredentialProperties,
|
||||
);
|
||||
}
|
||||
|
||||
// The properties defined on the parent credentials take presidence
|
||||
NodeHelpers.mergeNodeProperties(
|
||||
combineProperties,
|
||||
credentialsData.properties,
|
||||
);
|
||||
|
||||
return combineProperties;
|
||||
},
|
||||
|
||||
async loadCurrentCredential() {
|
||||
this.credentialId = this.activeId;
|
||||
|
||||
try {
|
||||
const currentCredentials: ICredentialsDecryptedResponse =
|
||||
await this.$store.dispatch('credentials/getCredentialData', {
|
||||
id: this.credentialId,
|
||||
});
|
||||
if (!currentCredentials) {
|
||||
throw new Error(
|
||||
`Could not find the credentials with the id: ${this.credentialId}`,
|
||||
);
|
||||
}
|
||||
|
||||
this.credentialData = currentCredentials.data || {};
|
||||
this.credentialName = currentCredentials.name;
|
||||
currentCredentials.nodesAccess.forEach(
|
||||
(access: { nodeType: string }) => {
|
||||
// keep node access structure to keep dates when updating
|
||||
this.nodeAccess[access.nodeType] = access;
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
this.$showError(
|
||||
e,
|
||||
'Problem loading credentials',
|
||||
'There was a problem loading the credentials:',
|
||||
);
|
||||
this.closeDialog();
|
||||
|
||||
return;
|
||||
}
|
||||
},
|
||||
onTabSelect(tab: string) {
|
||||
this.activeTab = tab;
|
||||
},
|
||||
onNodeAccessChange({name, value}: {name: string, value: boolean}) {
|
||||
this.hasUnsavedChanges = true;
|
||||
|
||||
if (value) {
|
||||
this.nodeAccess = {
|
||||
...this.nodeAccess,
|
||||
[name]: {
|
||||
nodeType: name,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
this.nodeAccess = {
|
||||
...this.nodeAccess,
|
||||
[name]: null,
|
||||
};
|
||||
}
|
||||
},
|
||||
onDataChange({ name, value }: { name: string; value: any }) { // tslint:disable-line:no-any
|
||||
this.hasUnsavedChanges = true;
|
||||
|
||||
const { oauthTokenData, ...credData } = this.credentialData;
|
||||
|
||||
this.credentialData = {
|
||||
...credData,
|
||||
[name]: value,
|
||||
};
|
||||
},
|
||||
closeDialog() {
|
||||
this.modalBus.$emit('close');
|
||||
},
|
||||
|
||||
getParentTypes(name: string): string[] {
|
||||
const credentialType =
|
||||
this.$store.getters['credentials/getCredentialTypeByName'](name);
|
||||
|
||||
if (
|
||||
credentialType === undefined ||
|
||||
credentialType.extends === undefined
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const types: string[] = [];
|
||||
for (const typeName of credentialType.extends) {
|
||||
types.push(typeName);
|
||||
types.push.apply(types, this.getParentTypes(typeName));
|
||||
}
|
||||
|
||||
return types;
|
||||
},
|
||||
|
||||
onNameEdit(text: string) {
|
||||
this.hasUnsavedChanges = true;
|
||||
this.credentialName = text;
|
||||
},
|
||||
|
||||
scrollToTop() {
|
||||
setTimeout(() => {
|
||||
const content = this.$refs.content as Element;
|
||||
if (content) {
|
||||
content.scrollTop = 0;
|
||||
}
|
||||
}, 0);
|
||||
},
|
||||
|
||||
scrollToBottom() {
|
||||
setTimeout(() => {
|
||||
const content = this.$refs.content as Element;
|
||||
if (content) {
|
||||
content.scrollTop = content.scrollHeight;
|
||||
}
|
||||
}, 0);
|
||||
},
|
||||
|
||||
async retestCredential() {
|
||||
if (!this.isCredentialTestable) {
|
||||
this.authError = '';
|
||||
this.testedSuccessfully = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const nodesAccess = Object.values(this.nodeAccess).filter(
|
||||
(access) => !!access,
|
||||
) as ICredentialNodeAccess[];
|
||||
|
||||
// Save only the none default data
|
||||
const data = NodeHelpers.getNodeParameters(
|
||||
this.credentialType!.properties,
|
||||
this.credentialData as INodeParameters,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
|
||||
const details: ICredentialsDecrypted = {
|
||||
name: this.credentialName,
|
||||
type: this.credentialTypeName!,
|
||||
data: data as unknown as ICredentialDataDecryptedObject,
|
||||
nodesAccess,
|
||||
};
|
||||
|
||||
this.isRetesting = true;
|
||||
await this.testCredential(details);
|
||||
this.isRetesting = false;
|
||||
},
|
||||
|
||||
async testCredential(credentialDetails: ICredentialsDecrypted) {
|
||||
const result: NodeCredentialTestResult = await this.$store.dispatch('credentials/testCredential', credentialDetails);
|
||||
if (result.status === 'Error') {
|
||||
this.authError = result.message;
|
||||
this.testedSuccessfully = false;
|
||||
}
|
||||
else {
|
||||
this.authError = '';
|
||||
this.testedSuccessfully = true;
|
||||
}
|
||||
|
||||
this.scrollToTop();
|
||||
},
|
||||
|
||||
async saveCredential(): Promise<ICredentialsResponse | null> {
|
||||
if (!this.requiredPropertiesFilled) {
|
||||
this.showValidationWarning = true;
|
||||
this.scrollToTop();
|
||||
}
|
||||
else {
|
||||
this.showValidationWarning = false;
|
||||
}
|
||||
|
||||
this.isSaving = true;
|
||||
const nodesAccess = Object.values(this.nodeAccess).filter(
|
||||
(access) => !!access,
|
||||
) as ICredentialNodeAccess[];
|
||||
|
||||
// Save only the none default data
|
||||
const data = NodeHelpers.getNodeParameters(
|
||||
this.credentialType!.properties,
|
||||
this.credentialData as INodeParameters,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
|
||||
const credentialDetails: ICredentialsDecrypted = {
|
||||
name: this.credentialName,
|
||||
type: this.credentialTypeName!,
|
||||
data: data as unknown as ICredentialDataDecryptedObject,
|
||||
nodesAccess,
|
||||
};
|
||||
|
||||
let credential;
|
||||
|
||||
if (this.mode === 'new' && !this.credentialId) {
|
||||
credential = await this.createCredential(
|
||||
credentialDetails,
|
||||
);
|
||||
} else {
|
||||
credential = await this.updateCredential(
|
||||
credentialDetails,
|
||||
);
|
||||
}
|
||||
|
||||
this.isSaving = false;
|
||||
if (credential) {
|
||||
this.credentialId = credential.id as string;
|
||||
|
||||
if (this.isCredentialTestable) {
|
||||
this.isTesting = true;
|
||||
await this.testCredential(credentialDetails);
|
||||
this.isTesting = false;
|
||||
}
|
||||
else {
|
||||
this.authError = '';
|
||||
this.testedSuccessfully = false;
|
||||
}
|
||||
}
|
||||
|
||||
return credential;
|
||||
},
|
||||
|
||||
async createCredential(
|
||||
credentialDetails: ICredentialsDecrypted,
|
||||
): Promise<ICredentialsResponse | null> {
|
||||
let credential;
|
||||
|
||||
try {
|
||||
credential = (await this.$store.dispatch(
|
||||
'credentials/createNewCredential',
|
||||
credentialDetails,
|
||||
)) as ICredentialsResponse;
|
||||
this.hasUnsavedChanges = false;
|
||||
} catch (error) {
|
||||
this.$showError(
|
||||
error,
|
||||
'Problem creating credentials',
|
||||
'There was a problem creating the credentials:',
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
this.$externalHooks().run('credentials.create', {
|
||||
credentialTypeData: this.credentialData,
|
||||
});
|
||||
|
||||
return credential;
|
||||
},
|
||||
|
||||
async updateCredential(
|
||||
credentialDetails: ICredentialsDecrypted,
|
||||
): Promise<ICredentialsResponse | null> {
|
||||
let credential;
|
||||
try {
|
||||
credential = (await this.$store.dispatch(
|
||||
'credentials/updateCredential',
|
||||
{ id: this.credentialId, data: credentialDetails },
|
||||
)) as ICredentialsResponse;
|
||||
this.hasUnsavedChanges = false;
|
||||
} catch (error) {
|
||||
this.$showError(
|
||||
error,
|
||||
'Problem updating credentials',
|
||||
'There was a problem updating the credentials:',
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Now that the credentials changed check if any nodes use credentials
|
||||
// which have now a different name
|
||||
this.updateNodesCredentialsIssues();
|
||||
|
||||
return credential;
|
||||
},
|
||||
|
||||
async deleteCredential() {
|
||||
if (!this.currentCredential) {
|
||||
return;
|
||||
}
|
||||
|
||||
const savedCredentialName = this.currentCredential.name;
|
||||
|
||||
const deleteConfirmed = await this.confirmMessage(
|
||||
`Are you sure you want to delete "${savedCredentialName}" credentials?`,
|
||||
'Delete Credentials?',
|
||||
null,
|
||||
'Yes, delete!',
|
||||
);
|
||||
|
||||
if (deleteConfirmed === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.isDeleting = true;
|
||||
await this.$store.dispatch('credentials/deleteCredential', {
|
||||
id: this.credentialId,
|
||||
});
|
||||
this.hasUnsavedChanges = false;
|
||||
} catch (error) {
|
||||
this.$showError(
|
||||
error,
|
||||
'Problem deleting credentials',
|
||||
'There was a problem deleting the credentials:',
|
||||
);
|
||||
this.isDeleting = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.isDeleting = false;
|
||||
// Now that the credentials were removed check if any nodes used them
|
||||
this.updateNodesCredentialsIssues();
|
||||
|
||||
this.$showMessage({
|
||||
title: 'Credentials deleted',
|
||||
message: `The credential "${savedCredentialName}" was deleted!`,
|
||||
type: 'success',
|
||||
});
|
||||
this.closeDialog();
|
||||
},
|
||||
|
||||
async oAuthCredentialAuthorize() {
|
||||
let url;
|
||||
|
||||
const credential = await this.saveCredential();
|
||||
if (!credential) {
|
||||
return;
|
||||
}
|
||||
|
||||
const types = this.parentTypes;
|
||||
|
||||
try {
|
||||
if (
|
||||
this.credentialTypeName === 'oAuth2Api' ||
|
||||
types.includes('oAuth2Api')
|
||||
) {
|
||||
url = (await this.$store.dispatch('credentials/oAuth2Authorize', {
|
||||
...this.credentialData,
|
||||
id: credential.id,
|
||||
})) as string;
|
||||
} else if (
|
||||
this.credentialTypeName === 'oAuth1Api' ||
|
||||
types.includes('oAuth1Api')
|
||||
) {
|
||||
url = (await this.$store.dispatch('credentials/oAuth1Authorize', {
|
||||
...this.credentialData,
|
||||
id: credential.id,
|
||||
})) as string;
|
||||
}
|
||||
} catch (error) {
|
||||
this.$showError(
|
||||
error,
|
||||
'OAuth Authorization Error',
|
||||
'Error generating authorization URL:',
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const params = `scrollbars=no,resizable=yes,status=no,titlebar=noe,location=no,toolbar=no,menubar=no,width=500,height=700`;
|
||||
const oauthPopup = window.open(url, 'OAuth2 Authorization', params);
|
||||
Vue.set(this.credentialData, 'oauthTokenData', null);
|
||||
|
||||
const receiveMessage = (event: MessageEvent) => {
|
||||
// // TODO: Add check that it came from n8n
|
||||
// if (event.origin !== 'http://example.org:8080') {
|
||||
// return;
|
||||
// }
|
||||
if (event.data === 'success') {
|
||||
window.removeEventListener('message', receiveMessage, false);
|
||||
|
||||
// Set some kind of data that status changes.
|
||||
// As data does not get displayed directly it does not matter what data.
|
||||
Vue.set(this.credentialData, 'oauthTokenData', {});
|
||||
this.$store.commit('credentials/enableOAuthCredential', credential);
|
||||
|
||||
// Close the window
|
||||
if (oauthPopup) {
|
||||
oauthPopup.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', receiveMessage, false);
|
||||
},
|
||||
},
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
<style module lang="scss">
|
||||
.credentialModal {
|
||||
max-width: 900px;
|
||||
--dialog-close-top: 28px;
|
||||
}
|
||||
|
||||
.mainContent {
|
||||
flex-grow: 1;
|
||||
overflow: auto;
|
||||
padding-bottom: 100px;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
max-width: 170px;
|
||||
min-width: 170px;
|
||||
margin-right: var(--spacing-l);
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.credInfo {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.credTab {
|
||||
padding-left: 12px !important;
|
||||
}
|
||||
|
||||
.credActions {
|
||||
margin-right: var(--spacing-xl);
|
||||
> * {
|
||||
margin-left: var(--spacing-2xs);
|
||||
}
|
||||
}
|
||||
|
||||
.credIcon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: var(--spacing-xs);
|
||||
}
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user