mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 20:24:05 -08:00
refactor(editor): Convert credential related components to composition API (no-changelog) (#10530)
This commit is contained in:
parent
405c55a1f7
commit
402a8b40c0
|
@ -1130,10 +1130,7 @@ function resetCredentialData(): void {
|
|||
/>
|
||||
</div>
|
||||
<div v-else-if="activeTab === 'details' && credentialType" :class="$style.mainContent">
|
||||
<CredentialInfo
|
||||
:current-credential="currentCredential"
|
||||
:credential-permissions="credentialPermissions"
|
||||
/>
|
||||
<CredentialInfo :current-credential="currentCredential" />
|
||||
</div>
|
||||
<div v-else-if="activeTab.startsWith('coming-soon')" :class="$style.mainContent">
|
||||
<FeatureComingSoon :feature-id="activeTab.split('/')[1]"></FeatureComingSoon>
|
||||
|
|
|
@ -1,57 +1,52 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
<script setup lang="ts">
|
||||
import TimeAgo from '../TimeAgo.vue';
|
||||
import type { INodeTypeDescription } from 'n8n-workflow';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import type { ICredentialsDecryptedResponse, ICredentialsResponse } from '@/Interface';
|
||||
import { N8nText } from 'n8n-design-system';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CredentialInfo',
|
||||
components: {
|
||||
TimeAgo,
|
||||
},
|
||||
props: ['currentCredential', 'credentialPermissions'],
|
||||
methods: {
|
||||
shortNodeType(nodeType: INodeTypeDescription) {
|
||||
return this.$locale.shortNodeType(nodeType.name);
|
||||
},
|
||||
},
|
||||
});
|
||||
type Props = {
|
||||
currentCredential: ICredentialsResponse | ICredentialsDecryptedResponse | null;
|
||||
};
|
||||
|
||||
defineProps<Props>();
|
||||
|
||||
const i18n = useI18n();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="$style.container">
|
||||
<el-row v-if="currentCredential">
|
||||
<el-col :span="8" :class="$style.label">
|
||||
<n8n-text :compact="true" :bold="true">
|
||||
{{ $locale.baseText('credentialEdit.credentialInfo.created') }}
|
||||
</n8n-text>
|
||||
<N8nText :compact="true" :bold="true">
|
||||
{{ i18n.baseText('credentialEdit.credentialInfo.created') }}
|
||||
</N8nText>
|
||||
</el-col>
|
||||
<el-col :span="16" :class="$style.valueLabel">
|
||||
<n8n-text :compact="true"
|
||||
<N8nText :compact="true"
|
||||
><TimeAgo :date="currentCredential.createdAt" :capitalize="true"
|
||||
/></n8n-text>
|
||||
/></N8nText>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row v-if="currentCredential">
|
||||
<el-col :span="8" :class="$style.label">
|
||||
<n8n-text :compact="true" :bold="true">
|
||||
{{ $locale.baseText('credentialEdit.credentialInfo.lastModified') }}
|
||||
</n8n-text>
|
||||
<N8nText :compact="true" :bold="true">
|
||||
{{ i18n.baseText('credentialEdit.credentialInfo.lastModified') }}
|
||||
</N8nText>
|
||||
</el-col>
|
||||
<el-col :span="16" :class="$style.valueLabel">
|
||||
<n8n-text :compact="true"
|
||||
<N8nText :compact="true"
|
||||
><TimeAgo :date="currentCredential.updatedAt" :capitalize="true"
|
||||
/></n8n-text>
|
||||
/></N8nText>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row v-if="currentCredential">
|
||||
<el-col :span="8" :class="$style.label">
|
||||
<n8n-text :compact="true" :bold="true">
|
||||
{{ $locale.baseText('credentialEdit.credentialInfo.id') }}
|
||||
</n8n-text>
|
||||
<N8nText :compact="true" :bold="true">
|
||||
{{ i18n.baseText('credentialEdit.credentialInfo.id') }}
|
||||
</N8nText>
|
||||
</el-col>
|
||||
<el-col :span="16" :class="$style.valueLabel">
|
||||
<n8n-text :compact="true">{{ currentCredential.id }}</n8n-text>
|
||||
<N8nText :compact="true">{{ currentCredential.id }}</N8nText>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
|
|
@ -1,162 +1,114 @@
|
|||
<script lang="ts">
|
||||
import type {
|
||||
ICredentialsResponse,
|
||||
ICredentialsDecryptedResponse,
|
||||
IUserListAction,
|
||||
} from '@/Interface';
|
||||
import { defineComponent } from 'vue';
|
||||
import type { PropType } from 'vue';
|
||||
import { useMessage } from '@/composables/useMessage';
|
||||
import { mapStores } from 'pinia';
|
||||
import { useUsersStore } from '@/stores/users.store';
|
||||
<script setup lang="ts">
|
||||
import ProjectSharing from '@/components/Projects/ProjectSharing.vue';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import { EnterpriseEditionFeature } from '@/constants';
|
||||
import type { ICredentialsDecryptedResponse, ICredentialsResponse } from '@/Interface';
|
||||
import type { PermissionsRecord } from '@/permissions';
|
||||
import { useProjectsStore } from '@/stores/projects.store';
|
||||
import { useRolesStore } from '@/stores/roles.store';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import { useCredentialsStore } from '@/stores/credentials.store';
|
||||
import { useUsageStore } from '@/stores/usage.store';
|
||||
import { EnterpriseEditionFeature } from '@/constants';
|
||||
import ProjectSharing from '@/components/Projects/ProjectSharing.vue';
|
||||
import { useProjectsStore } from '@/stores/projects.store';
|
||||
import { useUsersStore } from '@/stores/users.store';
|
||||
import type { ProjectListItem, ProjectSharingData } from '@/types/projects.types';
|
||||
import { ProjectTypes } from '@/types/projects.types';
|
||||
import type { ICredentialDataDecryptedObject } from 'n8n-workflow';
|
||||
import type { PermissionsRecord } from '@/permissions';
|
||||
import type { EventBus } from 'n8n-design-system/utils';
|
||||
import { useRolesStore } from '@/stores/roles.store';
|
||||
import type { RoleMap } from '@/types/roles.types';
|
||||
import { splitName } from '@/utils/projects.utils';
|
||||
import type { EventBus } from 'n8n-design-system/utils';
|
||||
import type { ICredentialDataDecryptedObject } from 'n8n-workflow';
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CredentialSharing',
|
||||
components: {
|
||||
ProjectSharing,
|
||||
},
|
||||
props: {
|
||||
credential: {
|
||||
type: Object as PropType<ICredentialsResponse | ICredentialsDecryptedResponse | null>,
|
||||
default: null,
|
||||
},
|
||||
credentialId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
credentialData: {
|
||||
type: Object as PropType<ICredentialDataDecryptedObject>,
|
||||
required: true,
|
||||
},
|
||||
credentialPermissions: {
|
||||
type: Object as PropType<PermissionsRecord['credential']>,
|
||||
required: true,
|
||||
},
|
||||
modalBus: {
|
||||
type: Object as PropType<EventBus>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
setup() {
|
||||
return {
|
||||
...useMessage(),
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
sharedWithProjects: [...(this.credential?.sharedWithProjects ?? [])] as ProjectSharingData[],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapStores(
|
||||
useCredentialsStore,
|
||||
useUsersStore,
|
||||
useUsageStore,
|
||||
useUIStore,
|
||||
useSettingsStore,
|
||||
useProjectsStore,
|
||||
useRolesStore,
|
||||
),
|
||||
usersListActions(): IUserListAction[] {
|
||||
return [
|
||||
{
|
||||
label: this.$locale.baseText('credentialEdit.credentialSharing.list.delete'),
|
||||
value: 'delete',
|
||||
},
|
||||
];
|
||||
},
|
||||
isSharingEnabled(): boolean {
|
||||
return this.settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing];
|
||||
},
|
||||
credentialOwnerName(): string {
|
||||
const { firstName, lastName, email } = splitName(this.credential?.homeProject?.name ?? '');
|
||||
return firstName || lastName ? `${firstName}${lastName ? ' ' + lastName : ''}` : email ?? '';
|
||||
},
|
||||
credentialDataHomeProject(): ProjectSharingData | undefined {
|
||||
const credentialContainsProjectSharingData = (
|
||||
data: ICredentialDataDecryptedObject,
|
||||
): data is { homeProject: ProjectSharingData } => {
|
||||
return 'homeProject' in data;
|
||||
};
|
||||
type Props = {
|
||||
credentialId: string;
|
||||
credentialData: ICredentialDataDecryptedObject;
|
||||
credentialPermissions: PermissionsRecord['credential'];
|
||||
credential?: ICredentialsResponse | ICredentialsDecryptedResponse | null;
|
||||
modalBus: EventBus;
|
||||
};
|
||||
|
||||
return this.credentialData && credentialContainsProjectSharingData(this.credentialData)
|
||||
? this.credentialData.homeProject
|
||||
: undefined;
|
||||
},
|
||||
isCredentialSharedWithCurrentUser(): boolean {
|
||||
if (!Array.isArray(this.credentialData.sharedWithProjects)) return false;
|
||||
const props = withDefaults(defineProps<Props>(), { credential: null });
|
||||
|
||||
return this.credentialData.sharedWithProjects.some((sharee) => {
|
||||
return typeof sharee === 'object' && 'id' in sharee
|
||||
? sharee.id === this.usersStore.currentUser?.id
|
||||
: false;
|
||||
});
|
||||
},
|
||||
projects(): ProjectListItem[] {
|
||||
return this.projectsStore.projects.filter(
|
||||
(project) =>
|
||||
project.id !== this.credential?.homeProject?.id &&
|
||||
project.id !== this.credentialDataHomeProject?.id,
|
||||
);
|
||||
},
|
||||
homeProject(): ProjectSharingData | undefined {
|
||||
return this.credential?.homeProject ?? this.credentialDataHomeProject;
|
||||
},
|
||||
isHomeTeamProject(): boolean {
|
||||
return this.homeProject?.type === ProjectTypes.Team;
|
||||
},
|
||||
credentialRoleTranslations(): Record<string, string> {
|
||||
return {
|
||||
'credential:user': this.$locale.baseText('credentialEdit.credentialSharing.role.user'),
|
||||
};
|
||||
},
|
||||
credentialRoles(): RoleMap['credential'] {
|
||||
return this.rolesStore.processedCredentialRoles.map(({ role, scopes, licensed }) => ({
|
||||
role,
|
||||
name: this.credentialRoleTranslations[role],
|
||||
scopes,
|
||||
licensed,
|
||||
}));
|
||||
},
|
||||
sharingSelectPlaceholder() {
|
||||
return this.projectsStore.teamProjects.length
|
||||
? this.$locale.baseText('projects.sharing.select.placeholder.project')
|
||||
: this.$locale.baseText('projects.sharing.select.placeholder.user');
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
sharedWithProjects: {
|
||||
handler(changedSharedWithProjects: ProjectSharingData[]) {
|
||||
this.$emit('update:modelValue', changedSharedWithProjects);
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
await Promise.all([this.usersStore.fetchUsers(), this.projectsStore.getAllProjects()]);
|
||||
},
|
||||
methods: {
|
||||
goToUpgrade() {
|
||||
void this.uiStore.goToUpgrade('credential_sharing', 'upgrade-credentials-sharing');
|
||||
},
|
||||
},
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: ProjectSharingData[]];
|
||||
}>();
|
||||
|
||||
const i18n = useI18n();
|
||||
|
||||
const usersStore = useUsersStore();
|
||||
const uiStore = useUIStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
const projectsStore = useProjectsStore();
|
||||
const rolesStore = useRolesStore();
|
||||
|
||||
const sharedWithProjects = ref([...(props.credential?.sharedWithProjects ?? [])]);
|
||||
|
||||
const isSharingEnabled = computed(
|
||||
() => settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing],
|
||||
);
|
||||
const credentialOwnerName = computed(() => {
|
||||
const { firstName, lastName, email } = splitName(props.credential?.homeProject?.name ?? '');
|
||||
return firstName || lastName ? `${firstName}${lastName ? ' ' + lastName : ''}` : email ?? '';
|
||||
});
|
||||
|
||||
const credentialDataHomeProject = computed<ProjectSharingData | undefined>(() => {
|
||||
const credentialContainsProjectSharingData = (
|
||||
data: ICredentialDataDecryptedObject,
|
||||
): data is { homeProject: ProjectSharingData } => {
|
||||
return 'homeProject' in data;
|
||||
};
|
||||
|
||||
return props.credentialData && credentialContainsProjectSharingData(props.credentialData)
|
||||
? props.credentialData.homeProject
|
||||
: undefined;
|
||||
});
|
||||
|
||||
const projects = computed<ProjectListItem[]>(() => {
|
||||
return projectsStore.projects.filter(
|
||||
(project) =>
|
||||
project.id !== props.credential?.homeProject?.id &&
|
||||
project.id !== credentialDataHomeProject.value?.id,
|
||||
);
|
||||
});
|
||||
|
||||
const homeProject = computed<ProjectSharingData | undefined>(
|
||||
() => props.credential?.homeProject ?? credentialDataHomeProject.value,
|
||||
);
|
||||
const isHomeTeamProject = computed(() => homeProject.value?.type === ProjectTypes.Team);
|
||||
const credentialRoleTranslations = computed<Record<string, string>>(() => {
|
||||
return {
|
||||
'credential:user': i18n.baseText('credentialEdit.credentialSharing.role.user'),
|
||||
};
|
||||
});
|
||||
|
||||
const credentialRoles = computed<RoleMap['credential']>(() => {
|
||||
return rolesStore.processedCredentialRoles.map(({ role, scopes, licensed }) => ({
|
||||
role,
|
||||
name: credentialRoleTranslations.value[role],
|
||||
scopes,
|
||||
licensed,
|
||||
}));
|
||||
});
|
||||
|
||||
const sharingSelectPlaceholder = computed(() =>
|
||||
projectsStore.teamProjects.length
|
||||
? i18n.baseText('projects.sharing.select.placeholder.project')
|
||||
: i18n.baseText('projects.sharing.select.placeholder.user'),
|
||||
);
|
||||
|
||||
watch(
|
||||
sharedWithProjects,
|
||||
(changedSharedWithProjects) => {
|
||||
emit('update:modelValue', changedSharedWithProjects);
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
await Promise.all([usersStore.fetchUsers(), projectsStore.getAllProjects()]);
|
||||
});
|
||||
|
||||
function goToUpgrade() {
|
||||
void uiStore.goToUpgrade('credential_sharing', 'upgrade-credentials-sharing');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -164,33 +116,29 @@ export default defineComponent({
|
|||
<div v-if="!isSharingEnabled">
|
||||
<N8nActionBox
|
||||
:heading="
|
||||
$locale.baseText(
|
||||
uiStore.contextBasedTranslationKeys.credentials.sharing.unavailable.title,
|
||||
)
|
||||
i18n.baseText(uiStore.contextBasedTranslationKeys.credentials.sharing.unavailable.title)
|
||||
"
|
||||
:description="
|
||||
$locale.baseText(
|
||||
i18n.baseText(
|
||||
uiStore.contextBasedTranslationKeys.credentials.sharing.unavailable.description,
|
||||
)
|
||||
"
|
||||
:button-text="
|
||||
$locale.baseText(
|
||||
uiStore.contextBasedTranslationKeys.credentials.sharing.unavailable.button,
|
||||
)
|
||||
i18n.baseText(uiStore.contextBasedTranslationKeys.credentials.sharing.unavailable.button)
|
||||
"
|
||||
@click:button="goToUpgrade"
|
||||
/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<N8nInfoTip v-if="credentialPermissions.share" :bold="false" class="mb-s">
|
||||
{{ $locale.baseText('credentialEdit.credentialSharing.info.owner') }}
|
||||
{{ i18n.baseText('credentialEdit.credentialSharing.info.owner') }}
|
||||
</N8nInfoTip>
|
||||
<N8nInfoTip v-else-if="isHomeTeamProject" :bold="false" class="mb-s">
|
||||
{{ $locale.baseText('credentialEdit.credentialSharing.info.sharee.team') }}
|
||||
{{ i18n.baseText('credentialEdit.credentialSharing.info.sharee.team') }}
|
||||
</N8nInfoTip>
|
||||
<N8nInfoTip v-else :bold="false" class="mb-s">
|
||||
{{
|
||||
$locale.baseText('credentialEdit.credentialSharing.info.sharee.personal', {
|
||||
i18n.baseText('credentialEdit.credentialSharing.info.sharee.personal', {
|
||||
interpolate: { credentialOwnerName },
|
||||
})
|
||||
}}
|
||||
|
|
|
@ -1,116 +1,121 @@
|
|||
<script lang="ts">
|
||||
import type { ICredentialType } from 'n8n-workflow';
|
||||
import { defineComponent } from 'vue';
|
||||
<script setup lang="ts">
|
||||
import type { ICredentialType, INodeProperties, NodeParameterValue } from 'n8n-workflow';
|
||||
import { computed, ref } from 'vue';
|
||||
import ScopesNotice from '@/components/ScopesNotice.vue';
|
||||
import NodeCredentials from '@/components/NodeCredentials.vue';
|
||||
import { mapStores } from 'pinia';
|
||||
import { useCredentialsStore } from '@/stores/credentials.store';
|
||||
import { N8nOption, N8nSelect } from 'n8n-design-system';
|
||||
import type { INodeUi, INodeUpdatePropertiesInformation } from '@/Interface';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CredentialsSelect',
|
||||
components: {
|
||||
ScopesNotice,
|
||||
NodeCredentials,
|
||||
},
|
||||
props: [
|
||||
'activeCredentialType',
|
||||
'node',
|
||||
'parameter',
|
||||
'inputSize',
|
||||
'displayValue',
|
||||
'isReadOnly',
|
||||
'displayTitle',
|
||||
],
|
||||
emits: ['update:modelValue', 'setFocus', 'onBlur', 'credentialSelected'],
|
||||
computed: {
|
||||
...mapStores(useCredentialsStore),
|
||||
allCredentialTypes(): ICredentialType[] {
|
||||
return this.credentialsStore.allCredentialTypes;
|
||||
},
|
||||
scopes(): string[] {
|
||||
if (!this.activeCredentialType) return [];
|
||||
type Props = {
|
||||
activeCredentialType: string;
|
||||
parameter: INodeProperties;
|
||||
node?: INodeUi;
|
||||
inputSize?: 'small' | 'large' | 'mini' | 'medium' | 'xlarge';
|
||||
displayValue: NodeParameterValue;
|
||||
isReadOnly: boolean;
|
||||
displayTitle: string;
|
||||
};
|
||||
|
||||
return this.credentialsStore.getScopesByCredentialType(this.activeCredentialType);
|
||||
},
|
||||
supportedCredentialTypes(): ICredentialType[] {
|
||||
return this.allCredentialTypes.filter((c: ICredentialType) => this.isSupported(c.name));
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
focus() {
|
||||
const selectRef = this.$refs.innerSelect as HTMLElement | undefined;
|
||||
if (selectRef) {
|
||||
selectRef.focus();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Check if a credential type belongs to one of the supported sets defined
|
||||
* in the `credentialTypes` key in a `credentialsSelect` parameter
|
||||
*/
|
||||
isSupported(name: string): boolean {
|
||||
const supported = this.getSupportedSets(this.parameter.credentialTypes);
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const checkedCredType = this.credentialsStore.getCredentialTypeByName(name);
|
||||
if (!checkedCredType) return false;
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: string];
|
||||
setFocus: [];
|
||||
onBlur: [];
|
||||
credentialSelected: [update: INodeUpdatePropertiesInformation];
|
||||
}>();
|
||||
|
||||
for (const property of supported.has) {
|
||||
if (checkedCredType[property as keyof ICredentialType] !== undefined) {
|
||||
// edge case: `httpHeaderAuth` has `authenticate` auth but belongs to generic auth
|
||||
if (name === 'httpHeaderAuth' && property === 'authenticate') continue;
|
||||
const credentialsStore = useCredentialsStore();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
const innerSelectRef = ref<HTMLSelectElement>();
|
||||
|
||||
if (
|
||||
checkedCredType.extends &&
|
||||
checkedCredType.extends.some((parentType: string) => supported.extends.includes(parentType))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
const allCredentialTypes = computed(() => credentialsStore.allCredentialTypes);
|
||||
const scopes = computed(() => {
|
||||
if (!props.activeCredentialType) return [];
|
||||
|
||||
if (checkedCredType.extends && supported.extends.length) {
|
||||
// recurse upward until base credential type
|
||||
// e.g. microsoftDynamicsOAuth2Api -> microsoftOAuth2Api -> oAuth2Api
|
||||
return checkedCredType.extends.reduce(
|
||||
(acc: boolean, parentType: string) => acc || this.isSupported(parentType),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
getSupportedSets(credentialTypes: string[]) {
|
||||
return credentialTypes.reduce<{ extends: string[]; has: string[] }>(
|
||||
(acc, cur) => {
|
||||
const _extends = cur.split('extends:');
|
||||
|
||||
if (_extends.length === 2) {
|
||||
acc.extends.push(_extends[1]);
|
||||
return acc;
|
||||
}
|
||||
|
||||
const _has = cur.split('has:');
|
||||
|
||||
if (_has.length === 2) {
|
||||
acc.has.push(_has[1]);
|
||||
return acc;
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{ extends: [], has: [] },
|
||||
);
|
||||
},
|
||||
},
|
||||
return credentialsStore.getScopesByCredentialType(props.activeCredentialType);
|
||||
});
|
||||
|
||||
const supportedCredentialTypes = computed(() => {
|
||||
return allCredentialTypes.value.filter((c: ICredentialType) => isSupported(c.name));
|
||||
});
|
||||
|
||||
function focus() {
|
||||
if (innerSelectRef.value) {
|
||||
innerSelectRef.value.focus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a credential type belongs to one of the supported sets defined
|
||||
* in the `credentialTypes` key in a `credentialsSelect` parameter
|
||||
*/
|
||||
function isSupported(name: string): boolean {
|
||||
const supported = getSupportedSets(props.parameter.credentialTypes ?? []);
|
||||
|
||||
const checkedCredType = credentialsStore.getCredentialTypeByName(name);
|
||||
if (!checkedCredType) return false;
|
||||
|
||||
for (const property of supported.has) {
|
||||
if (checkedCredType[property as keyof ICredentialType] !== undefined) {
|
||||
// edge case: `httpHeaderAuth` has `authenticate` auth but belongs to generic auth
|
||||
if (name === 'httpHeaderAuth' && property === 'authenticate') continue;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
checkedCredType.extends &&
|
||||
checkedCredType.extends.some((parentType: string) => supported.extends.includes(parentType))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (checkedCredType.extends && supported.extends.length) {
|
||||
// recurse upward until base credential type
|
||||
// e.g. microsoftDynamicsOAuth2Api -> microsoftOAuth2Api -> oAuth2Api
|
||||
return checkedCredType.extends.reduce(
|
||||
(acc: boolean, parentType: string) => acc || isSupported(parentType),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getSupportedSets(credentialTypes: string[]) {
|
||||
return credentialTypes.reduce<{ extends: string[]; has: string[] }>(
|
||||
(acc, cur) => {
|
||||
const _extends = cur.split('extends:');
|
||||
|
||||
if (_extends.length === 2) {
|
||||
acc.extends.push(_extends[1]);
|
||||
return acc;
|
||||
}
|
||||
|
||||
const _has = cur.split('has:');
|
||||
|
||||
if (_has.length === 2) {
|
||||
acc.has.push(_has[1]);
|
||||
return acc;
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{ extends: [], has: [] },
|
||||
);
|
||||
}
|
||||
|
||||
defineExpose({ focus });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div :class="$style['parameter-value-container']">
|
||||
<n8n-select
|
||||
ref="innerSelect"
|
||||
<N8nSelect
|
||||
ref="innerSelectRef"
|
||||
:size="inputSize"
|
||||
filterable
|
||||
:model-value="displayValue"
|
||||
|
@ -118,12 +123,12 @@ export default defineComponent({
|
|||
:title="displayTitle"
|
||||
:disabled="isReadOnly"
|
||||
data-test-id="credential-select"
|
||||
@update:model-value="(value: string) => $emit('update:modelValue', value)"
|
||||
@update:model-value="(value: string) => emit('update:modelValue', value)"
|
||||
@keydown.stop
|
||||
@focus="$emit('setFocus')"
|
||||
@blur="$emit('onBlur')"
|
||||
@focus="emit('setFocus')"
|
||||
@blur="emit('onBlur')"
|
||||
>
|
||||
<n8n-option
|
||||
<N8nOption
|
||||
v-for="credType in supportedCredentialTypes"
|
||||
:key="credType.name"
|
||||
:value="credType.name"
|
||||
|
@ -135,8 +140,8 @@ export default defineComponent({
|
|||
{{ credType.displayName }}
|
||||
</div>
|
||||
</div>
|
||||
</n8n-option>
|
||||
</n8n-select>
|
||||
</N8nOption>
|
||||
</N8nSelect>
|
||||
<slot name="issues-and-options" />
|
||||
</div>
|
||||
|
||||
|
@ -147,10 +152,11 @@ export default defineComponent({
|
|||
/>
|
||||
<div>
|
||||
<NodeCredentials
|
||||
v-if="node"
|
||||
:node="node"
|
||||
:readonly="isReadOnly"
|
||||
:override-cred-type="node.parameters[parameter.name]"
|
||||
@credential-selected="(updateInformation) => $emit('credentialSelected', updateInformation)"
|
||||
:override-cred-type="node?.parameters[parameter.name]"
|
||||
@credential-selected="(updateInformation) => emit('credentialSelected', updateInformation)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,69 +1,59 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import Modal from './Modal.vue';
|
||||
import { CREDENTIAL_SELECT_MODAL_KEY } from '../constants';
|
||||
import { mapStores } from 'pinia';
|
||||
<script setup lang="ts">
|
||||
import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { useCredentialsStore } from '@/stores/credentials.store';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { useCredentialsStore } from '@/stores/credentials.store';
|
||||
import { N8nButton, N8nSelect } from 'n8n-design-system';
|
||||
import { createEventBus } from 'n8n-design-system/utils';
|
||||
import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { CREDENTIAL_SELECT_MODAL_KEY } from '../constants';
|
||||
import Modal from './Modal.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CredentialsSelectModal',
|
||||
components: {
|
||||
Modal,
|
||||
},
|
||||
setup() {
|
||||
const externalHooks = useExternalHooks();
|
||||
return {
|
||||
externalHooks,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
modalBus: createEventBus(),
|
||||
selected: '',
|
||||
loading: true,
|
||||
CREDENTIAL_SELECT_MODAL_KEY,
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
try {
|
||||
await this.credentialsStore.fetchCredentialTypes(false);
|
||||
} catch (e) {}
|
||||
this.loading = false;
|
||||
const externalHooks = useExternalHooks();
|
||||
const telemetry = useTelemetry();
|
||||
|
||||
setTimeout(() => {
|
||||
const elementRef = this.$refs.select as HTMLSelectElement | undefined;
|
||||
if (elementRef) {
|
||||
elementRef.focus();
|
||||
}
|
||||
}, 0);
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useCredentialsStore, useUIStore, useWorkflowsStore),
|
||||
},
|
||||
methods: {
|
||||
onSelect(type: string) {
|
||||
this.selected = type;
|
||||
},
|
||||
openCredentialType() {
|
||||
this.modalBus.emit('close');
|
||||
this.uiStore.openNewCredential(this.selected);
|
||||
const modalBus = ref(createEventBus());
|
||||
const selected = ref('');
|
||||
const loading = ref(true);
|
||||
const selectRef = ref<HTMLSelectElement>();
|
||||
|
||||
const telemetryPayload = {
|
||||
credential_type: this.selected,
|
||||
source: 'primary_menu',
|
||||
new_credential: true,
|
||||
workflow_id: this.workflowsStore.workflowId,
|
||||
};
|
||||
const credentialsStore = useCredentialsStore();
|
||||
const uiStore = useUIStore();
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
|
||||
this.$telemetry.track('User opened Credential modal', telemetryPayload);
|
||||
void this.externalHooks.run('credentialsSelectModal.openCredentialType', telemetryPayload);
|
||||
},
|
||||
},
|
||||
onMounted(async () => {
|
||||
try {
|
||||
await credentialsStore.fetchCredentialTypes(false);
|
||||
} catch (e) {}
|
||||
|
||||
loading.value = false;
|
||||
|
||||
setTimeout(() => {
|
||||
if (selectRef.value) {
|
||||
selectRef.value.focus();
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
|
||||
function onSelect(type: string) {
|
||||
selected.value = type;
|
||||
}
|
||||
|
||||
function openCredentialType() {
|
||||
modalBus.value.emit('close');
|
||||
uiStore.openNewCredential(selected.value);
|
||||
|
||||
const telemetryPayload = {
|
||||
credential_type: selected.value,
|
||||
source: 'primary_menu',
|
||||
new_credential: true,
|
||||
workflow_id: workflowsStore.workflowId,
|
||||
};
|
||||
|
||||
telemetry.track('User opened Credential modal', telemetryPayload);
|
||||
void externalHooks.run('credentialsSelectModal.openCredentialType', telemetryPayload);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -86,8 +76,8 @@ export default defineComponent({
|
|||
<div :class="$style.subtitle">
|
||||
{{ $locale.baseText('credentialSelectModal.selectAnAppOrServiceToConnectTo') }}
|
||||
</div>
|
||||
<n8n-select
|
||||
ref="select"
|
||||
<N8nSelect
|
||||
ref="selectRef"
|
||||
filterable
|
||||
default-first-option
|
||||
:placeholder="$locale.baseText('credentialSelectModal.searchForApp')"
|
||||
|
@ -99,7 +89,7 @@ export default defineComponent({
|
|||
<template #prefix>
|
||||
<font-awesome-icon icon="search" />
|
||||
</template>
|
||||
<n8n-option
|
||||
<N8nOption
|
||||
v-for="credential in credentialsStore.allCredentialTypes"
|
||||
:key="credential.name"
|
||||
:value="credential.name"
|
||||
|
@ -107,12 +97,12 @@ export default defineComponent({
|
|||
filterable
|
||||
data-test-id="new-credential-type-select-option"
|
||||
/>
|
||||
</n8n-select>
|
||||
</N8nSelect>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div :class="$style.footer">
|
||||
<n8n-button
|
||||
<N8nButton
|
||||
:label="$locale.baseText('credentialSelectModal.continue')"
|
||||
float="right"
|
||||
size="large"
|
||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue