2019-06-23 03:35:23 -07:00
|
|
|
<template>
|
2020-08-27 01:48:57 -07:00
|
|
|
<div v-if="dialogVisible" @keydown.stop>
|
|
|
|
<el-dialog :visible="dialogVisible" append-to-body width="75%" class="credentials-edit-wrapper" :title="title" :nodeType="nodeType" :before-close="closeDialog">
|
2020-08-27 00:57:34 -07:00
|
|
|
<div name="title" class="title-container" slot="title">
|
2020-08-27 01:48:57 -07:00
|
|
|
<div class="title-left">{{title}}</div>
|
|
|
|
<div class="title-right">
|
2020-09-23 02:10:23 -07:00
|
|
|
<div v-if="credentialType && documentationUrl" class="docs-container">
|
2020-08-27 01:48:57 -07:00
|
|
|
<svg class="help-logo" target="_blank" width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
2020-08-25 05:28:16 -07:00
|
|
|
<title>Node Documentation</title>
|
2020-08-27 00:57:34 -07:00
|
|
|
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
|
|
|
<g transform="translate(-1127.000000, -836.000000)" fill-rule="nonzero">
|
|
|
|
<g transform="translate(1117.000000, 825.000000)">
|
|
|
|
<g transform="translate(10.000000, 11.000000)">
|
|
|
|
<g transform="translate(2.250000, 2.250000)" fill="#FF6150">
|
|
|
|
<path d="M6,11.25 L7.5,11.25 L7.5,9.75 L6,9.75 L6,11.25 M6.75,2.25 C5.09314575,2.25 3.75,3.59314575 3.75,5.25 L5.25,5.25 C5.25,4.42157288 5.92157288,3.75 6.75,3.75 C7.57842712,3.75 8.25,4.42157288 8.25,5.25 C8.25,6.75 6,6.5625 6,9 L7.5,9 C7.5,7.3125 9.75,7.125 9.75,5.25 C9.75,3.59314575 8.40685425,2.25 6.75,2.25 M1.5,0 L12,0 C12.8284271,0 13.5,0.671572875 13.5,1.5 L13.5,12 C13.5,12.8284271 12.8284271,13.5 12,13.5 L1.5,13.5 C0.671572875,13.5 0,12.8284271 0,12 L0,1.5 C0,0.671572875 0.671572875,0 1.5,0 Z"></path>
|
2020-08-25 05:28:16 -07:00
|
|
|
</g>
|
2020-08-27 00:57:34 -07:00
|
|
|
<rect x="0" y="0" width="18" height="18"></rect>
|
2020-08-20 05:47:59 -07:00
|
|
|
</g>
|
|
|
|
</g>
|
|
|
|
</g>
|
|
|
|
</g>
|
2020-08-25 05:28:16 -07:00
|
|
|
</svg>
|
2020-11-09 03:23:53 -08:00
|
|
|
<span class="doc-link-text">Need help? <a class="doc-hyperlink" :href="documentationUrl" target="_blank">Open credential docs</a></span>
|
2020-08-25 05:28:16 -07:00
|
|
|
</div>
|
2020-08-20 05:47:59 -07:00
|
|
|
</div>
|
|
|
|
</div>
|
2019-06-23 03:35:23 -07:00
|
|
|
<div class="credential-type-item">
|
|
|
|
<el-row v-if="!setCredentialType">
|
|
|
|
<el-col :span="6">
|
|
|
|
Credential type:
|
|
|
|
</el-col>
|
|
|
|
<el-col :span="18">
|
2021-04-24 13:41:00 -07:00
|
|
|
<el-select v-model="credentialType" filterable placeholder="Select Type" size="small" ref="credentialsDropdown">
|
2019-06-23 03:35:23 -07:00
|
|
|
<el-option
|
|
|
|
v-for="item in credentialTypes"
|
|
|
|
:key="item.name"
|
|
|
|
:label="item.displayName"
|
|
|
|
:value="item.name">
|
|
|
|
</el-option>
|
|
|
|
</el-select>
|
|
|
|
</el-col>
|
|
|
|
</el-row>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<credentials-input v-if="credentialType" @credentialsCreated="credentialsCreated" @credentialsUpdated="credentialsUpdated" :credentialTypeData="getCredentialTypeData(credentialType)" :credentialData="credentialData" :nodesInit="nodesInit"></credentials-input>
|
|
|
|
</el-dialog>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script lang="ts">
|
|
|
|
import Vue from 'vue';
|
|
|
|
|
|
|
|
import { restApi } from '@/components/mixins/restApi';
|
|
|
|
import { showMessage } from '@/components/mixins/showMessage';
|
|
|
|
import CredentialsInput from '@/components/CredentialsInput.vue';
|
2020-02-08 16:14:28 -08:00
|
|
|
import {
|
|
|
|
ICredentialsCreatedEvent,
|
|
|
|
ICredentialsDecryptedResponse,
|
|
|
|
} from '@/Interface';
|
2019-06-23 03:35:23 -07:00
|
|
|
|
|
|
|
import {
|
2020-05-16 10:05:40 -07:00
|
|
|
NodeHelpers,
|
2019-06-23 03:35:23 -07:00
|
|
|
ICredentialType,
|
2020-02-09 15:39:14 -08:00
|
|
|
INodeProperties,
|
2020-08-27 00:23:23 -07:00
|
|
|
INodeTypeDescription,
|
2019-06-23 03:35:23 -07:00
|
|
|
} from 'n8n-workflow';
|
|
|
|
|
|
|
|
import mixins from 'vue-typed-mixins';
|
2020-08-20 05:47:59 -07:00
|
|
|
import { INodeUi } from '../Interface';
|
2019-06-23 03:35:23 -07:00
|
|
|
|
|
|
|
export default mixins(
|
|
|
|
restApi,
|
|
|
|
showMessage,
|
|
|
|
).extend({
|
|
|
|
name: 'CredentialsEdit',
|
|
|
|
props: [
|
|
|
|
'dialogVisible', // Boolean
|
|
|
|
'editCredentials',
|
|
|
|
'setCredentialType', // String
|
|
|
|
'nodesInit', // Array
|
|
|
|
],
|
|
|
|
components: {
|
|
|
|
CredentialsInput,
|
|
|
|
},
|
|
|
|
data () {
|
|
|
|
return {
|
|
|
|
credentialData: null as ICredentialsDecryptedResponse | null,
|
|
|
|
credentialType: null as string | null,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
computed: {
|
|
|
|
credentialTypes (): ICredentialType[] {
|
|
|
|
const credentialTypes = this.$store.getters.allCredentialTypes;
|
|
|
|
if (credentialTypes === null) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
return credentialTypes;
|
|
|
|
},
|
|
|
|
title (): string {
|
|
|
|
if (this.editCredentials) {
|
|
|
|
const credentialType = this.$store.getters.credentialType(this.editCredentials.type);
|
|
|
|
return `Edit Credentials: "${credentialType.displayName}"`;
|
|
|
|
} else {
|
|
|
|
if (this.credentialType) {
|
|
|
|
const credentialType = this.$store.getters.credentialType(this.credentialType);
|
|
|
|
return `Create New Credentials: "${credentialType.displayName}"`;
|
|
|
|
} else {
|
|
|
|
return `Create New Credentials`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2020-09-23 02:10:23 -07:00
|
|
|
documentationUrl (): string | undefined {
|
|
|
|
let credentialTypeName = '';
|
2020-08-20 05:47:59 -07:00
|
|
|
if (this.editCredentials) {
|
2020-09-23 04:31:03 -07:00
|
|
|
credentialTypeName = this.editCredentials.type as string;
|
2020-08-20 06:34:45 -07:00
|
|
|
} else {
|
2020-09-23 04:31:03 -07:00
|
|
|
credentialTypeName = this.credentialType as string;
|
2020-09-23 02:10:23 -07:00
|
|
|
}
|
2020-08-25 05:28:16 -07:00
|
|
|
|
2020-09-23 02:10:23 -07:00
|
|
|
const credentialType = this.$store.getters.credentialType(credentialTypeName);
|
|
|
|
if (credentialType.documentationUrl !== undefined) {
|
2020-11-09 03:23:53 -08:00
|
|
|
if (credentialType.documentationUrl.startsWith('http')) {
|
|
|
|
return credentialType.documentationUrl;
|
|
|
|
} else {
|
|
|
|
return 'https://docs.n8n.io/credentials/' + credentialType.documentationUrl + '/?utm_source=n8n_app&utm_medium=left_nav_menu&utm_campaign=create_new_credentials_modal';
|
|
|
|
}
|
2020-08-20 05:47:59 -07:00
|
|
|
}
|
2020-09-23 04:31:03 -07:00
|
|
|
return undefined;
|
2020-08-20 05:47:59 -07:00
|
|
|
},
|
|
|
|
node (): INodeUi {
|
|
|
|
return this.$store.getters.activeNode;
|
|
|
|
},
|
|
|
|
nodeType (): INodeTypeDescription | null {
|
|
|
|
const activeNode = this.node;
|
|
|
|
if (this.node) {
|
|
|
|
return this.$store.getters.nodeType(this.node.type);
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
},
|
2019-06-23 03:35:23 -07:00
|
|
|
},
|
|
|
|
watch: {
|
|
|
|
async dialogVisible (newValue, oldValue): Promise<void> {
|
|
|
|
if (newValue) {
|
|
|
|
if (this.editCredentials) {
|
|
|
|
// Credentials which should be edited are given
|
|
|
|
const credentialType = this.$store.getters.credentialType(this.editCredentials.type);
|
|
|
|
|
|
|
|
if (credentialType === null) {
|
|
|
|
this.$showMessage({
|
|
|
|
title: 'Credential type not known',
|
|
|
|
message: `Credentials of type "${this.editCredentials.type}" are not known.`,
|
|
|
|
type: 'error',
|
|
|
|
duration: 0,
|
|
|
|
});
|
|
|
|
this.closeDialog();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.editCredentials.id === undefined) {
|
|
|
|
this.$showMessage({
|
|
|
|
title: 'Credential ID missing',
|
|
|
|
message: 'The ID of the credentials which should be edited is missing!',
|
|
|
|
type: 'error',
|
|
|
|
});
|
|
|
|
this.closeDialog();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let currentCredentials: ICredentialsDecryptedResponse | undefined;
|
|
|
|
try {
|
|
|
|
currentCredentials = await this.restApi().getCredentials(this.editCredentials.id as string, true) as ICredentialsDecryptedResponse | undefined;
|
|
|
|
} catch (error) {
|
|
|
|
this.$showError(error, 'Problem loading credentials', 'There was a problem loading the credentials:');
|
|
|
|
this.closeDialog();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (currentCredentials === undefined) {
|
|
|
|
this.$showMessage({
|
|
|
|
title: 'Credentials not found',
|
|
|
|
message: `Could not find the credentials with the id: ${this.editCredentials.id}`,
|
|
|
|
type: 'error',
|
|
|
|
duration: 0,
|
|
|
|
});
|
|
|
|
this.closeDialog();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (currentCredentials === undefined) {
|
|
|
|
this.$showMessage({
|
|
|
|
title: 'Problem loading credentials',
|
|
|
|
message: 'No credentials could be loaded!',
|
|
|
|
type: 'error',
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.credentialData = currentCredentials;
|
|
|
|
} else {
|
2021-04-24 13:41:00 -07:00
|
|
|
Vue.nextTick(() => {
|
|
|
|
(this.$refs.credentialsDropdown as HTMLDivElement).focus();
|
|
|
|
});
|
2019-06-23 03:35:23 -07:00
|
|
|
if (this.credentialType || this.setCredentialType) {
|
|
|
|
const credentialType = this.$store.getters.credentialType(this.credentialType || this.setCredentialType);
|
|
|
|
if (credentialType === null) {
|
|
|
|
this.$showMessage({
|
|
|
|
title: 'Credential type not known',
|
|
|
|
message: `Credentials of type "${this.credentialType || this.setCredentialType}" are not known.`,
|
|
|
|
type: 'error',
|
|
|
|
duration: 0,
|
|
|
|
});
|
|
|
|
this.closeDialog();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.credentialData = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.setCredentialType || (this.credentialData && this.credentialData.type)) {
|
|
|
|
this.credentialType = this.setCredentialType || (this.credentialData && this.credentialData.type);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Make sure that it gets always reset else it uses by default
|
|
|
|
// again the last selection from when it was open the previous time.
|
|
|
|
this.credentialType = null;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
methods: {
|
2020-02-09 15:39:14 -08:00
|
|
|
getCredentialProperties (name: string): INodeProperties[] {
|
2020-02-09 15:40:18 -08:00
|
|
|
const credentialsData = this.$store.getters.credentialType(name);
|
2020-02-09 15:39:14 -08:00
|
|
|
|
|
|
|
if (credentialsData === null) {
|
|
|
|
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);
|
2020-05-16 10:05:40 -07:00
|
|
|
NodeHelpers.mergeNodeProperties(combineProperties, mergeCredentialProperties);
|
2020-02-09 15:39:14 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// The properties defined on the parent credentials take presidence
|
2020-05-16 10:05:40 -07:00
|
|
|
NodeHelpers.mergeNodeProperties(combineProperties, credentialsData.properties);
|
2020-02-09 15:39:14 -08:00
|
|
|
|
|
|
|
return combineProperties;
|
|
|
|
},
|
2019-06-23 03:35:23 -07:00
|
|
|
getCredentialTypeData (name: string): ICredentialType | null {
|
2020-01-13 18:46:58 -08:00
|
|
|
let credentialData = this.$store.getters.credentialType(name);
|
|
|
|
|
|
|
|
if (credentialData === null || credentialData.extends === undefined) {
|
|
|
|
return credentialData;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Credentials extends another one. So get the properties of the one it
|
|
|
|
// extends and add them.
|
|
|
|
credentialData = JSON.parse(JSON.stringify(credentialData));
|
2020-02-09 15:39:14 -08:00
|
|
|
credentialData.properties = this.getCredentialProperties(credentialData.name);
|
2019-06-23 03:35:23 -07:00
|
|
|
|
2020-01-13 18:46:58 -08:00
|
|
|
return credentialData;
|
2019-06-23 03:35:23 -07:00
|
|
|
},
|
2020-02-08 16:14:28 -08:00
|
|
|
credentialsCreated (eventData: ICredentialsCreatedEvent): void {
|
|
|
|
this.$emit('credentialsCreated', eventData);
|
2019-06-23 03:35:23 -07:00
|
|
|
|
|
|
|
this.$showMessage({
|
|
|
|
title: 'Credentials created',
|
2020-08-26 12:25:59 -07:00
|
|
|
message: `"${eventData.data.name}" credentials were successfully created!`,
|
2019-06-23 03:35:23 -07:00
|
|
|
type: 'success',
|
|
|
|
});
|
|
|
|
|
2020-02-08 16:14:28 -08:00
|
|
|
if (eventData.options.closeDialog === true) {
|
|
|
|
this.closeDialog();
|
|
|
|
}
|
2019-06-23 03:35:23 -07:00
|
|
|
},
|
2020-02-08 16:14:28 -08:00
|
|
|
credentialsUpdated (eventData: ICredentialsCreatedEvent): void {
|
|
|
|
this.$emit('credentialsUpdated', eventData);
|
2019-06-23 03:35:23 -07:00
|
|
|
|
|
|
|
this.$showMessage({
|
|
|
|
title: 'Credentials updated',
|
2020-08-26 12:25:59 -07:00
|
|
|
message: `"${eventData.data.name}" credentials were successfully updated!`,
|
2019-06-23 03:35:23 -07:00
|
|
|
type: 'success',
|
|
|
|
});
|
|
|
|
|
2020-02-08 16:14:28 -08:00
|
|
|
if (eventData.options.closeDialog === true) {
|
|
|
|
this.closeDialog();
|
|
|
|
}
|
2019-06-23 03:35:23 -07:00
|
|
|
},
|
|
|
|
closeDialog (): void {
|
|
|
|
// Handle the close externally as the visible parameter is an external prop
|
|
|
|
// and is so not allowed to be changed here.
|
|
|
|
this.$emit('closeDialog');
|
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="scss">
|
2020-08-27 00:57:34 -07:00
|
|
|
.credentials-edit-wrapper {
|
|
|
|
.credential-type-item {
|
|
|
|
padding-bottom: 1em;
|
|
|
|
}
|
2019-06-23 03:35:23 -07:00
|
|
|
|
2020-08-27 00:57:34 -07:00
|
|
|
@media (min-width: 1200px){
|
|
|
|
.title-container {
|
|
|
|
display: flex;
|
|
|
|
flex-direction: row;
|
|
|
|
max-width: 100%;
|
|
|
|
line-height: 17px;
|
|
|
|
}
|
|
|
|
|
2020-08-27 01:48:57 -07:00
|
|
|
.docs-container {
|
2020-08-27 00:57:34 -07:00
|
|
|
margin-left: auto;
|
|
|
|
margin-right: 0;
|
|
|
|
}
|
|
|
|
}
|
2019-06-23 03:35:23 -07:00
|
|
|
|
2020-08-27 00:57:34 -07:00
|
|
|
@media (max-width: 1199px){
|
|
|
|
.title-container {
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
max-width: 100%;
|
|
|
|
line-height: 17px;
|
|
|
|
}
|
|
|
|
|
2020-08-27 01:48:57 -07:00
|
|
|
.docs-container {
|
2020-08-27 00:57:34 -07:00
|
|
|
margin-top: 10px;
|
|
|
|
margin-left: 0;
|
|
|
|
margin-right: auto;
|
|
|
|
}
|
2020-08-20 05:47:59 -07:00
|
|
|
}
|
|
|
|
|
2020-08-27 01:48:57 -07:00
|
|
|
.title-left {
|
2020-08-27 00:57:34 -07:00
|
|
|
flex: 7;
|
|
|
|
font-size: 16px;
|
|
|
|
font-weight: bold;
|
|
|
|
color: #7a7a7a;
|
|
|
|
vertical-align:middle;
|
2020-08-20 05:47:59 -07:00
|
|
|
}
|
|
|
|
|
2020-08-27 01:48:57 -07:00
|
|
|
.title-right {
|
2020-08-27 00:57:34 -07:00
|
|
|
vertical-align: middle;
|
|
|
|
flex: 3;
|
|
|
|
font-family: "Open Sans";
|
|
|
|
color: #666666;
|
|
|
|
font-size: 12px;
|
|
|
|
font-weight: 510;
|
|
|
|
letter-spacing: 0;
|
2020-08-20 05:47:59 -07:00
|
|
|
display: flex;
|
2020-08-27 00:57:34 -07:00
|
|
|
flex-direction: row;
|
|
|
|
min-width: 40%;
|
2020-08-20 05:47:59 -07:00
|
|
|
}
|
|
|
|
|
2020-08-27 01:48:57 -07:00
|
|
|
.help-logo {
|
2020-08-27 00:57:34 -07:00
|
|
|
flex: 1;
|
2020-08-20 05:47:59 -07:00
|
|
|
}
|
|
|
|
|
2020-08-27 01:48:57 -07:00
|
|
|
.doc-link-text {
|
2020-08-27 00:57:34 -07:00
|
|
|
margin-left: 2px;
|
|
|
|
float: right;
|
|
|
|
word-break: break-word;
|
|
|
|
flex: 9;
|
|
|
|
}
|
2020-08-20 05:47:59 -07:00
|
|
|
|
2020-08-27 01:48:57 -07:00
|
|
|
.doc-hyperlink,
|
|
|
|
.doc-hyperlink:visited,
|
|
|
|
.doc-hyperlink:focus,
|
|
|
|
.doc-hyperlink:active {
|
2020-08-27 00:57:34 -07:00
|
|
|
text-decoration: none;
|
|
|
|
color: #FF6150;
|
|
|
|
}
|
2020-08-20 05:47:59 -07:00
|
|
|
}
|
2020-08-25 05:28:16 -07:00
|
|
|
|
2019-06-23 03:35:23 -07:00
|
|
|
</style>
|