2019-06-23 03:35:23 -07:00
< template >
2022-05-24 02:36:19 -07:00
< div v-if ="credentialTypesNodeDescriptionDisplayed.length" :class="['node-credentials', $style.container]" >
2021-10-27 12:55:37 -07:00
< div v-for ="credentialTypeDescription in credentialTypesNodeDescriptionDisplayed" :key="credentialTypeDescription.name" >
< n8n -input -label
2021-12-15 04:16:53 -08:00
: label = " $locale . baseText (
2021-11-10 10:41:40 -08:00
'nodeCredentials.credentialFor' ,
{
interpolate : {
credentialType : credentialTypeNames [ credentialTypeDescription . name ]
}
}
) "
2021-10-27 12:55:37 -07:00
: bold = "false"
: set = "issues = getIssues(credentialTypeDescription.name)"
2022-05-24 02:36:19 -07:00
size = "small"
2022-10-12 05:06:28 -07:00
color = "text-dark"
2021-10-27 12:55:37 -07:00
>
2022-11-15 04:25:04 -08:00
< div v-if ="readonly || isReadOnly" >
2022-05-24 02:36:19 -07:00
< n8n -input
: value = "selected && selected[credentialTypeDescription.name] && selected[credentialTypeDescription.name].name"
disabled
size = "small"
/ >
2021-10-27 12:55:37 -07:00
< / div >
2022-05-24 02:36:19 -07:00
< div
v - else
: class = "issues.length ? $style.hasIssues : $style.input"
>
2021-12-15 04:16:53 -08:00
< n8n -select :value ="getSelectedId(credentialTypeDescription.name)" @ change = "(value) => onCredentialSelected(credentialTypeDescription.name, value)" :placeholder ="$locale.baseText('nodeCredentials.selectCredential')" size = "small" >
2021-10-27 12:55:37 -07:00
< n8n -option
2022-09-21 01:20:29 -07:00
v - for = "(item) in getCredentialOptions(credentialTypeDescription.name)"
2021-10-27 12:55:37 -07:00
: key = "item.id"
: label = "item.name"
: value = "item.id" >
< / n 8 n - o p t i o n >
< n8n -option
: key = "NEW_CREDENTIALS_TEXT"
: value = "NEW_CREDENTIALS_TEXT"
: label = "NEW_CREDENTIALS_TEXT"
>
< / n 8 n - o p t i o n >
< / n 8 n - s e l e c t >
< div :class ="$style.warning" v-if ="issues.length" >
2021-08-29 04:36:17 -07:00
< n8n -tooltip placement = "top" >
2022-11-18 05:59:31 -08:00
< template # content >
< titled -list :title ="`${$locale.baseText('nodeCredentials.issues')}:`" :items ="issues" / >
< / template >
2021-08-29 04:36:17 -07:00
< font -awesome -icon icon = "exclamation-triangle" / >
< / n 8 n - t o o l t i p >
2019-06-23 03:35:23 -07:00
< / div >
2021-10-27 12:55:37 -07:00
< div :class ="$style.edit" v-if ="selected[credentialTypeDescription.name] && isCredentialExisting(credentialTypeDescription.name)" >
2021-12-15 04:16:53 -08:00
< font -awesome -icon icon = "pen" @click ="editCredential(credentialTypeDescription.name)" class = "clickable" :title ="$locale.baseText('nodeCredentials.updateCredential')" / >
2021-10-27 12:55:37 -07:00
< / div >
< / div >
< / n 8 n - i n p u t - l a b e l >
2019-06-23 03:35:23 -07:00
< / div >
< / div >
< / template >
< script lang = "ts" >
import { restApi } from '@/components/mixins/restApi' ;
import {
2021-10-13 15:21:00 -07:00
ICredentialsResponse ,
2019-06-23 03:35:23 -07:00
INodeUi ,
INodeUpdatePropertiesInformation ,
2022-11-04 06:04:31 -07:00
IUser ,
2019-06-23 03:35:23 -07:00
} from '@/Interface' ;
import {
ICredentialType ,
INodeCredentialDescription ,
2021-10-13 15:21:00 -07:00
INodeCredentialsDetails ,
2019-06-23 03:35:23 -07:00
} from 'n8n-workflow' ;
import { genericHelpers } from '@/components/mixins/genericHelpers' ;
import { nodeHelpers } from '@/components/mixins/nodeHelpers' ;
import { showMessage } from '@/components/mixins/showMessage' ;
2022-09-05 07:36:22 -07:00
import TitledList from '@/components/TitledList.vue' ;
2019-06-23 03:35:23 -07:00
import mixins from 'vue-typed-mixins' ;
2022-09-21 01:20:29 -07:00
import { getCredentialPermissions } from "@/permissions" ;
2022-11-04 06:04:31 -07:00
import { mapStores } from 'pinia' ;
import { useUIStore } from '@/stores/ui' ;
import { useUsersStore } from '@/stores/users' ;
import { useWorkflowsStore } from '@/stores/workflows' ;
import { useNodeTypesStore } from '@/stores/nodeTypes' ;
2022-11-09 01:01:50 -08:00
import { useCredentialsStore } from '@/stores/credentials' ;
2019-06-23 03:35:23 -07:00
export default mixins (
genericHelpers ,
nodeHelpers ,
restApi ,
showMessage ,
) . extend ( {
name : 'NodeCredentials' ,
props : [
2022-11-15 04:25:04 -08:00
'readonly' ,
2019-06-23 03:35:23 -07:00
'node' , // INodeUi
2022-05-24 02:36:19 -07:00
'overrideCredType' , // cred type
2019-06-23 03:35:23 -07:00
] ,
2022-09-05 07:36:22 -07:00
components : {
TitledList ,
} ,
2021-09-11 01:15:36 -07:00
data ( ) {
return {
2021-12-15 04:16:53 -08:00
NEW _CREDENTIALS _TEXT : ` - ${ this . $locale . baseText ( 'nodeCredentials.createNew' ) } - ` ,
2022-11-09 01:01:50 -08:00
subscribedToCredentialType : '' ,
2021-09-11 01:15:36 -07:00
} ;
2019-06-23 03:35:23 -07:00
} ,
2022-11-09 01:01:50 -08:00
mounted ( ) {
this . listenForNewCredentials ( ) ;
} ,
2019-06-23 03:35:23 -07:00
computed : {
2022-11-04 06:04:31 -07:00
... mapStores (
2022-11-09 01:01:50 -08:00
useCredentialsStore ,
2022-11-04 06:04:31 -07:00
useNodeTypesStore ,
useUIStore ,
useUsersStore ,
useWorkflowsStore ,
) ,
2022-11-09 01:01:50 -08:00
allCredentialsByType ( ) : { [ type : string ] : ICredentialsResponse [ ] } {
return this . credentialsStore . allCredentialsByType ;
} ,
2022-11-04 06:04:31 -07:00
currentUser ( ) : IUser {
return this . usersStore . currentUser || { } as IUser ;
} ,
2019-06-23 03:35:23 -07:00
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 ;
2022-11-09 01:01:50 -08:00
const credType = this . credentialsStore . getCredentialTypeByName ( this . overrideCredType ) ;
2022-05-24 02:36:19 -07:00
if ( credType ) return [ credType ] ;
2022-11-04 06:04:31 -07:00
const activeNodeType = this . nodeTypesStore . getNodeType ( node . type , node . typeVersion ) ;
2019-06-23 03:35:23 -07:00
if ( activeNodeType && activeNodeType . credentials ) {
return activeNodeType . credentials ;
}
return [ ] ;
} ,
credentialTypeNames ( ) {
const returnData : {
[ key : string ] : string ;
} = { } ;
let credentialType : ICredentialType | null ;
for ( const credentialTypeName of this . credentialTypesNode ) {
2022-11-09 01:01:50 -08:00
credentialType = this . credentialsStore . getCredentialTypeByName ( credentialTypeName ) ;
2019-06-23 03:35:23 -07:00
returnData [ credentialTypeName ] = credentialType !== null ? credentialType . displayName : credentialTypeName ;
}
return returnData ;
} ,
2021-10-13 15:21:00 -07:00
selected ( ) : { [ type : string ] : INodeCredentialsDetails } {
2021-09-11 01:15:36 -07:00
return this . node . credentials || { } ;
2019-06-23 03:35:23 -07:00
} ,
} ,
2022-10-17 04:39:42 -07:00
2019-06-23 03:35:23 -07:00
methods : {
2022-09-21 01:20:29 -07:00
getCredentialOptions ( type : string ) : ICredentialsResponse [ ] {
return ( this . allCredentialsByType as Record < string , ICredentialsResponse [ ] > ) [ type ] . filter ( ( credential ) => {
2022-11-09 01:01:50 -08:00
const permissions = getCredentialPermissions ( this . currentUser , credential ) ;
2022-09-21 01:20:29 -07:00
return permissions . use ;
} ) ;
} ,
2021-10-13 15:21:00 -07:00
getSelectedId ( type : string ) {
if ( this . isCredentialExisting ( type ) ) {
return this . selected [ type ] . id ;
}
return undefined ;
} ,
2019-06-23 03:35:23 -07:00
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 ;
} ,
2022-11-09 01:01:50 -08:00
// TODO: Investigate if this can be solved using only the store data (storing selected flag in credentials objects, ...)
listenForNewCredentials ( ) {
// Listen for credentials store changes so credential selection can be updated if creds are changed from the modal
this . credentialsStore . $subscribe ( ( mutation , state ) => {
// This data pro stores credential type that the component is currently interested in
const credentialType = this . subscribedToCredentialType ;
2022-11-10 03:51:50 -08:00
let credentialsOfType = this . credentialsStore . allCredentialsByType [ credentialType ] ;
if ( credentialsOfType ) {
credentialsOfType = credentialsOfType . sort ( ( a , b ) => ( a . id < b . id ? - 1 : 1 ) ) ;
if ( credentialsOfType . length > 0 ) {
// If nothing has been selected previously, select the first one (newly added)
if ( ! this . selected [ credentialType ] ) {
this . onCredentialSelected ( credentialType , credentialsOfType [ 0 ] . id ) ;
} else {
// Else, check id currently selected cred has been updated
const newSelected = credentialsOfType . find ( cred => cred . id === this . selected [ credentialType ] . id ) ;
// If it has changed, select it
if ( newSelected && newSelected . name !== this . selected [ credentialType ] . name ) {
this . onCredentialSelected ( credentialType , newSelected . id ) ;
} else { // Else select the last cred with that type since selected has been deleted or a new one has been added
this . onCredentialSelected ( credentialType , credentialsOfType [ credentialsOfType . length - 1 ] . id ) ;
}
2022-11-09 01:01:50 -08:00
}
}
2021-09-11 01:15:36 -07:00
}
2022-11-09 01:01:50 -08:00
this . subscribedToCredentialType = '' ;
2021-09-11 01:15:36 -07:00
} ) ;
} ,
2021-10-13 15:21:00 -07:00
clearSelectedCredential ( credentialType : string ) {
const node : INodeUi = this . node ;
const credentials = {
... ( node . credentials || { } ) ,
} ;
delete credentials [ credentialType ] ;
const updateInformation : INodeUpdatePropertiesInformation = {
name : this . node . name ,
properties : {
credentials ,
} ,
} ;
this . $emit ( 'credentialSelected' , updateInformation ) ;
} ,
onCredentialSelected ( credentialType : string , credentialId : string | null | undefined ) {
2021-11-20 06:58:21 -08:00
if ( credentialId === this . NEW _CREDENTIALS _TEXT ) {
2022-11-09 01:01:50 -08:00
// this.listenForNewCredentials(credentialType);
this . subscribedToCredentialType = credentialType ;
}
if ( ! credentialId || credentialId === this . NEW _CREDENTIALS _TEXT ) {
2022-11-04 06:04:31 -07:00
this . uiStore . openNewCredential ( credentialType ) ;
this . $telemetry . track ( 'User opened Credential modal' , { credential _type : credentialType , source : 'node' , new _credential : true , workflow _id : this . workflowsStore . workflowId } ) ;
2021-10-13 15:21:00 -07:00
return ;
2021-09-11 01:15:36 -07:00
}
2021-10-13 15:21:00 -07:00
2022-05-24 02:36:19 -07:00
this . $telemetry . track (
'User selected credential from node modal' ,
{
credential _type : credentialType ,
node _type : this . node . type ,
... ( this . hasProxyAuth ( this . node ) ? { is _service _specific : true } : { } ) ,
2022-11-04 06:04:31 -07:00
workflow _id : this . workflowsStore . workflowId ,
2022-09-21 01:20:29 -07:00
credential _id : credentialId ,
2022-05-24 02:36:19 -07:00
} ,
) ;
2021-10-18 20:57:49 -07:00
2022-11-09 01:01:50 -08:00
const selectedCredentials = this . credentialsStore . getCredentialById ( credentialId ) ;
2021-10-13 15:21:00 -07:00
const oldCredentials = this . node . credentials && this . node . credentials [ credentialType ] ? this . node . credentials [ credentialType ] : { } ;
2021-10-18 20:57:49 -07:00
const selected = { id : selectedCredentials . id , name : selectedCredentials . name } ;
2021-10-13 15:21:00 -07:00
// if credentials has been string or neither id matched nor name matched uniquely
2022-11-09 01:01:50 -08:00
if ( oldCredentials . id === null || ( oldCredentials . id && ! this . credentialsStore . getCredentialByIdAndType ( oldCredentials . id , credentialType ) ) ) {
2021-10-13 15:21:00 -07:00
// update all nodes in the workflow with the same old/invalid credentials
2022-11-04 06:04:31 -07:00
this . workflowsStore . replaceInvalidWorkflowCredentials ( {
2021-10-13 15:21:00 -07:00
credentials : selected ,
invalid : oldCredentials ,
type : credentialType ,
} ) ;
this . updateNodesCredentialsIssues ( ) ;
this . $showMessage ( {
2021-12-15 04:16:53 -08:00
title : this . $locale . baseText ( 'nodeCredentials.showMessage.title' ) ,
message : this . $locale . baseText (
2021-11-10 10:41:40 -08:00
'nodeCredentials.showMessage.message' ,
{
interpolate : {
oldCredentialName : oldCredentials . name ,
newCredentialName : selected . name ,
} ,
} ,
) ,
2021-10-13 15:21:00 -07:00
type : 'success' ,
} ) ;
2021-09-11 01:15:36 -07:00
}
const node : INodeUi = this . node ;
const credentials = {
... ( node . credentials || { } ) ,
[ credentialType ] : selected ,
} ;
2019-06-23 03:35:23 -07:00
const updateInformation : INodeUpdatePropertiesInformation = {
2021-09-11 01:15:36 -07:00
name : this . node . name ,
2019-06-23 03:35:23 -07:00
properties : {
2021-09-11 01:15:36 -07:00
credentials ,
2019-06-23 03:35:23 -07:00
} ,
} ;
this . $emit ( 'credentialSelected' , updateInformation ) ;
} ,
2021-09-11 01:15:36 -07:00
2019-06-23 03:35:23 -07:00
displayCredentials ( credentialTypeDescription : INodeCredentialDescription ) : boolean {
if ( credentialTypeDescription . displayOptions === undefined ) {
// If it is not defined no need to do a proper check
return true ;
}
2022-04-28 10:04:09 -07:00
return this . displayParameter ( this . node . parameters , credentialTypeDescription , '' , this . node ) ;
2019-06-23 03:35:23 -07:00
} ,
2021-09-11 01:15:36 -07:00
2019-06-23 03:35:23 -07:00
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 ] ;
} ,
2021-10-13 15:21:00 -07:00
isCredentialExisting ( credentialType : string ) : boolean {
if ( ! this . node . credentials || ! this . node . credentials [ credentialType ] || ! this . node . credentials [ credentialType ] . id ) {
return false ;
}
const { id } = this . node . credentials [ credentialType ] ;
2022-09-21 01:20:29 -07:00
const options = this . getCredentialOptions ( credentialType ) ;
2019-06-23 03:35:23 -07:00
2021-10-13 15:21:00 -07:00
return ! ! options . find ( ( option : ICredentialsResponse ) => option . id === id ) ;
2019-06-23 03:35:23 -07:00
} ,
2021-09-11 01:15:36 -07:00
editCredential ( credentialType : string ) : void {
2021-10-13 15:21:00 -07:00
const { id } = this . node . credentials [ credentialType ] ;
2022-11-04 06:04:31 -07:00
this . uiStore . openExistingCredential ( id ) ;
2019-06-23 03:35:23 -07:00
2022-11-04 06:04:31 -07:00
this . $telemetry . track ( 'User opened Credential modal' , { credential _type : credentialType , source : 'node' , new _credential : false , workflow _id : this . workflowsStore . workflowId } ) ;
2022-11-09 01:01:50 -08:00
this . subscribedToCredentialType = credentialType ;
2019-06-23 03:35:23 -07:00
} ,
} ,
} ) ;
< / script >
2021-10-27 12:55:37 -07:00
< style lang = "scss" module >
. container {
2022-05-24 02:36:19 -07:00
margin - top : var ( -- spacing - xs ) ;
2022-06-07 06:02:08 -07:00
& > div : not ( : first - child ) {
margin - top : var ( -- spacing - xs ) ;
}
2021-10-27 12:55:37 -07:00
}
2019-06-23 03:35:23 -07:00
2021-10-27 12:55:37 -07:00
. warning {
min - width : 20 px ;
margin - left : 5 px ;
color : # ff8080 ;
font - size : var ( -- font - size - s ) ;
}
2021-08-29 04:36:17 -07:00
2021-10-27 12:55:37 -07:00
. edit {
display : flex ;
justify - content : center ;
align - items : center ;
color : var ( -- color - text - base ) ;
min - width : 20 px ;
margin - left : 5 px ;
font - size : var ( -- font - size - s ) ;
}
2021-10-13 15:21:00 -07:00
2021-10-27 12:55:37 -07:00
. input {
display : flex ;
align - items : center ;
2019-06-23 03:35:23 -07:00
}
2021-10-27 12:55:37 -07:00
. hasIssues {
composes : input ;
-- input - border - color : var ( -- color - danger ) ;
}
2019-06-23 03:35:23 -07:00
< / style >