refactor(editor): Finish pinia migration, remove all vuex dependancies (#4533)

*  Added pinia support. Migrated community nodes module.
*  Added ui pinia store, moved some data from root store to it, updated modals to work with pinia stores
*  Added ui pinia store and migrated a part of the root store
*  Migrated `settings` store to pinia
*  Removing vuex store refs from router
*  Migrated `users` module to pinia store
*  Fixing errors after sync with master
*  One more error after merge
*  Created `workflows` pinia store. Moved large part of root store to it. Started updating references.
*  Finished migrating workflows store to pinia
*  Renaming some getters and actions to make more sense
*  Finished migrating the root store to pinia
*  Migrated ndv store to pinia
*  Renaming main panel dimensions getter so it doesn't clash with data prop name
* ✔️ Fixing lint errors
*  Migrated `templates` store to pinia
*  Migrated the `nodeTypes`store
*  Removed unused pieces of code and oold vuex modules
*  Adding vuex calls to pinia store, fixing wrong references
* 💄 Removing leftover $store refs
*  Added legacy getters and mutations to store to support webhooks
*  Added missing front-end hooks, updated vuex state subscriptions to pinia
* ✔️ Fixing linting errors
*  Removing vue composition api plugin
*  Fixing main sidebar state when loading node view
* 🐛 Fixing an error when activating workflows
* 🐛 Fixing isses with workflow settings and executions auto-refresh
* 🐛 Removing duplicate listeners which cause import error
* 🐛 Fixing route authentication
*  Updating freshly pulled $store refs
*  Adding deleted const
*  Updating store references in ee features. Reseting NodeView credentials update flag when resetting workspace
*  Adding return type to email submission modal
*  Making NodeView only react to paste event when active
* 🐛 Fixing signup view errors
*  Started migrating the `credentials` module to pinia
* 👌 Addressing PR review comments
*  Migrated permissions module to pinia
*  Migrated `nodeCreator`, `tags` and `versions` modules to pinia
*  Implemented webhooks pinia store
*  Removing all leftover vuex files and references
*  Removing final vuex refs
*  Updating expected credentialId type
*  Removing node credentials subscription code, reducing node click debounce timeout
* 🐛 Fixing pushing nodes downstream when inserting new node
* ✔️ Fixing a lint error in new type guard
*  Updating helper reference
* ✔️ Removing unnecessary awaits
*  fix(editor): remove unnecessary imports from NDV
*  Merging mapStores blocks in NodeView
*  fix(editor): make sure JS Plumb not loaded earlier than needed
*  Updating type guard nad credentials subscriptions
*  Updating type guard so it doesn't use `any` type

Co-authored-by: Csaba Tuncsik <csaba@n8n.io>
This commit is contained in:
Milorad FIlipović 2022-11-09 10:01:50 +01:00 committed by GitHub
parent 825637f02a
commit bae3098e4e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
69 changed files with 891 additions and 947 deletions

View file

@ -76,7 +76,6 @@
"vue2-boring-avatars": "0.3.4", "vue2-boring-avatars": "0.3.4",
"vue2-teleport": "^1.0.1", "vue2-teleport": "^1.0.1",
"vue2-touch-events": "^3.2.1", "vue2-touch-events": "^3.2.1",
"vuex": "^3.6.2",
"xss": "^1.0.10" "xss": "^1.0.10"
}, },
"devDependencies": { "devDependencies": {

View file

@ -992,7 +992,7 @@ export interface ICredentialsState {
export interface ITagsState { export interface ITagsState {
tags: { [id: string]: ITag }; tags: { [id: string]: ITag };
isLoading: boolean; loading: boolean;
fetchedAll: boolean; fetchedAll: boolean;
fetchedUsageCount: boolean; fetchedUsageCount: boolean;
} }

View file

@ -50,6 +50,7 @@ import dateformat from "dateformat";
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui'; import { useUIStore } from '@/stores/ui';
import { useUsersStore } from '@/stores/users'; import { useUsersStore } from '@/stores/users';
import { useCredentialsStore } from '@/stores/credentials';
export const CREDENTIAL_LIST_ITEM_ACTIONS = { export const CREDENTIAL_LIST_ITEM_ACTIONS = {
OPEN: 'open', OPEN: 'open',
@ -89,19 +90,24 @@ export default mixins(
}, },
computed: { computed: {
...mapStores( ...mapStores(
useCredentialsStore,
useUIStore, useUIStore,
useUsersStore, useUsersStore,
), ),
currentUser (): IUser { currentUser (): IUser | null {
return this.usersStore.currentUser || {} as IUser; return this.usersStore.currentUser;
}, },
credentialType(): ICredentialType { credentialType(): ICredentialType {
return this.$store.getters['credentials/getCredentialTypeByName'](this.data.type); return this.credentialsStore.getCredentialTypeByName(this.data.type);
}, },
credentialPermissions(): IPermissions { credentialPermissions(): IPermissions | null {
return getCredentialPermissions(this.currentUser, this.data, this.$store); return !this.currentUser ? null : getCredentialPermissions(this.currentUser, this.data);
}, },
actions(): Array<{ label: string; value: string; }> { actions(): Array<{ label: string; value: string; }> {
if (!this.credentialPermissions) {
return [];
}
return [ return [
{ {
label: this.$locale.baseText('credentials.item.open'), label: this.$locale.baseText('credentials.item.open'),
@ -136,9 +142,7 @@ export default mixins(
); );
if (deleteConfirmed) { if (deleteConfirmed) {
await this.$store.dispatch('credentials/deleteCredential', { this.credentialsStore.deleteCredential({ id: this.data.id });
id: this.data.id,
});
} }
} }
}, },

View file

@ -107,6 +107,7 @@ import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows'; import { useWorkflowsStore } from '@/stores/workflows';
import { useRootStore } from '@/stores/n8nRootStore'; import { useRootStore } from '@/stores/n8nRootStore';
import { useNDVStore } from '@/stores/ndv'; import { useNDVStore } from '@/stores/ndv';
import { useCredentialsStore } from '@/stores/credentials';
export default mixins(restApi).extend({ export default mixins(restApi).extend({
name: 'CredentialConfig', name: 'CredentialConfig',
@ -182,6 +183,7 @@ export default mixins(restApi).extend({
}, },
computed: { computed: {
...mapStores( ...mapStores(
useCredentialsStore,
useNDVStore, useNDVStore,
useRootStore, useRootStore,
useUIStore, useUIStore,
@ -202,7 +204,7 @@ export default mixins(restApi).extend({
return (this.credentialType as ICredentialType).name; return (this.credentialType as ICredentialType).name;
}, },
credentialOwnerName(): string { credentialOwnerName(): string {
return this.$store.getters['credentials/getCredentialOwnerName'](this.credentialId); return this.credentialsStore.getCredentialOwnerName(this.credentialId);
}, },
documentationUrl(): string { documentationUrl(): string {
const type = this.credentialType as ICredentialType; const type = this.credentialType as ICredentialType;

View file

@ -106,7 +106,6 @@
import Vue from 'vue'; import Vue from 'vue';
import { import {
ICredentialsDecryptedResponse,
ICredentialsResponse, ICredentialsResponse,
IFakeDoor, IFakeDoor,
IUser, IUser,
@ -150,6 +149,8 @@ import { useSettingsStore } from '@/stores/settings';
import { useUsersStore } from '@/stores/users'; import { useUsersStore } from '@/stores/users';
import { useWorkflowsStore } from '@/stores/workflows'; import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv'; import { useNDVStore } from '@/stores/ndv';
import { useCredentialsStore } from '@/stores/credentials';
import { isValidCredentialResponse } from '@/typeGuards';
interface NodeAccessMap { interface NodeAccessMap {
[nodeType: string]: ICredentialNodeAccess | null; [nodeType: string]: ICredentialNodeAccess | null;
@ -184,7 +185,7 @@ export default mixins(showMessage, nodeHelpers).extend({
return { return {
activeTab: 'connection', activeTab: 'connection',
authError: '', authError: '',
credentialId: '' as string | number, credentialId: '',
credentialName: '', credentialName: '',
credentialData: {} as ICredentialDataDecryptedObject, credentialData: {} as ICredentialDataDecryptedObject,
modalBus: new Vue(), modalBus: new Vue(),
@ -214,18 +215,17 @@ export default mixins(showMessage, nodeHelpers).extend({
{}, {},
); );
if (this.mode === 'new') { if (this.mode === 'new' && this.credentialTypeName) {
this.credentialName = await this.$store.dispatch( this.credentialName = await this.credentialsStore.getNewCredentialName({ credentialTypeName: this.credentialTypeName });
'credentials/getNewCredentialName',
{ credentialTypeName: this.credentialTypeName },
);
Vue.set(this.credentialData, 'ownedBy', { if (this.currentUser) {
id: this.currentUser.id, Vue.set(this.credentialData, 'ownedBy', {
firstName: this.currentUser.firstName, id: this.currentUser.id,
lastName: this.currentUser.lastName, firstName: this.currentUser.firstName,
email: this.currentUser.email, lastName: this.currentUser.lastName,
}); email: this.currentUser.email,
});
}
} else { } else {
await this.loadCurrentCredential(); await this.loadCurrentCredential();
} }
@ -259,23 +259,22 @@ export default mixins(showMessage, nodeHelpers).extend({
}, },
computed: { computed: {
...mapStores( ...mapStores(
useCredentialsStore,
useNDVStore, useNDVStore,
useSettingsStore, useSettingsStore,
useUIStore, useUIStore,
useUsersStore, useUsersStore,
useWorkflowsStore, useWorkflowsStore,
), ),
currentUser(): IUser { currentUser(): IUser | null {
return this.usersStore.currentUser || {} as IUser; return this.usersStore.currentUser;
}, },
currentCredential(): ICredentialsResponse | null { currentCredential(): ICredentialsResponse | null {
if (!this.credentialId) { if (!this.credentialId) {
return null; return null;
} }
return this.$store.getters['credentials/getCredentialById']( return this.credentialsStore.getCredentialById(this.credentialId);
this.credentialId,
);
}, },
credentialTypeName(): string | null { credentialTypeName(): string | null {
if (this.mode === 'edit') { if (this.mode === 'edit') {
@ -293,9 +292,7 @@ export default mixins(showMessage, nodeHelpers).extend({
return null; return null;
} }
const type = this.$store.getters['credentials/getCredentialTypeByName']( const type = this.credentialsStore.getCredentialTypeByName(this.credentialTypeName);
this.credentialTypeName,
);
if (!type) { if (!type) {
return null; return null;
@ -333,9 +330,7 @@ export default mixins(showMessage, nodeHelpers).extend({
}, },
nodesWithAccess(): INodeTypeDescription[] { nodesWithAccess(): INodeTypeDescription[] {
if (this.credentialTypeName) { if (this.credentialTypeName) {
return this.$store.getters['credentials/getNodesWithAccess']( return this.credentialsStore.getNodesWithAccess(this.credentialTypeName);
this.credentialTypeName,
);
} }
return []; return [];
@ -408,7 +403,7 @@ export default mixins(showMessage, nodeHelpers).extend({
return {}; return {};
} }
return getCredentialPermissions(this.currentUser, (this.credentialId ? this.currentCredential : this.credentialData) as ICredentialsResponse, this.$store); return getCredentialPermissions(this.currentUser, (this.credentialId ? this.currentCredential : this.credentialData) as ICredentialsResponse);
}, },
sidebarItems(): IMenuItem[] { sidebarItems(): IMenuItem[] {
const items: IMenuItem[] = [ const items: IMenuItem[] = [
@ -502,8 +497,7 @@ export default mixins(showMessage, nodeHelpers).extend({
); );
}, },
getCredentialProperties(name: string): INodeProperties[] { getCredentialProperties(name: string): INodeProperties[] {
const credentialTypeData = const credentialTypeData = this.credentialsStore.getCredentialTypeByName(name);
this.$store.getters['credentials/getCredentialTypeByName'](name);
if (!credentialTypeData) { if (!credentialTypeData) {
return []; return [];
@ -536,9 +530,7 @@ export default mixins(showMessage, nodeHelpers).extend({
this.credentialId = this.activeId; this.credentialId = this.activeId;
try { try {
const currentCredentials: ICredentialsDecryptedResponse = await this.$store.dispatch('credentials/getCredentialData', { const currentCredentials = await this.credentialsStore.getCredentialData({ id: this.credentialId });
id: this.credentialId,
});
if (!currentCredentials) { if (!currentCredentials) {
throw new Error( throw new Error(
@ -622,8 +614,7 @@ export default mixins(showMessage, nodeHelpers).extend({
}, },
getParentTypes(name: string): string[] { getParentTypes(name: string): string[] {
const credentialType = const credentialType = this.credentialsStore.getCredentialTypeByName(name);
this.$store.getters['credentials/getCredentialTypeByName'](name);
if ( if (
credentialType === undefined || credentialType === undefined ||
@ -691,7 +682,7 @@ export default mixins(showMessage, nodeHelpers).extend({
}, },
async testCredential(credentialDetails: ICredentialsDecrypted) { async testCredential(credentialDetails: ICredentialsDecrypted) {
const result: INodeCredentialTestResult = await this.$store.dispatch('credentials/testCredential', credentialDetails); const result = await this.credentialsStore.testCredential(credentialDetails);
if (result.status === 'Error') { if (result.status === 'Error') {
this.authError = result.message; this.authError = result.message;
this.testedSuccessfully = false; this.testedSuccessfully = false;
@ -812,10 +803,7 @@ export default mixins(showMessage, nodeHelpers).extend({
let credential; let credential;
try { try {
credential = (await this.$store.dispatch( credential = await this.credentialsStore.createNewCredential(credentialDetails);
'credentials/createNewCredential',
credentialDetails,
)) as ICredentialsResponse;
this.hasUnsavedChanges = false; this.hasUnsavedChanges = false;
} catch (error) { } catch (error) {
this.$showError( this.$showError(
@ -846,10 +834,7 @@ export default mixins(showMessage, nodeHelpers).extend({
): Promise<ICredentialsResponse | null> { ): Promise<ICredentialsResponse | null> {
let credential; let credential;
try { try {
credential = (await this.$store.dispatch( credential = await this.credentialsStore.updateCredential({ id: this.credentialId, data: credentialDetails });
'credentials/updateCredential',
{ id: this.credentialId, data: credentialDetails },
)) as ICredentialsResponse;
this.hasUnsavedChanges = false; this.hasUnsavedChanges = false;
} catch (error) { } catch (error) {
this.$showError( this.$showError(
@ -893,9 +878,7 @@ export default mixins(showMessage, nodeHelpers).extend({
try { try {
this.isDeleting = true; this.isDeleting = true;
await this.$store.dispatch('credentials/deleteCredential', { this.credentialsStore.deleteCredential({ id: this.credentialId });
id: this.credentialId,
});
this.hasUnsavedChanges = false; this.hasUnsavedChanges = false;
} catch (error) { } catch (error) {
this.$showError( this.$showError(
@ -929,22 +912,21 @@ export default mixins(showMessage, nodeHelpers).extend({
const types = this.parentTypes; const types = this.parentTypes;
try { try {
const credData = { id: credential.id, ...this.credentialData };
if ( if (
this.credentialTypeName === 'oAuth2Api' || this.credentialTypeName === 'oAuth2Api' ||
types.includes('oAuth2Api') types.includes('oAuth2Api')
) { ) {
url = (await this.$store.dispatch('credentials/oAuth2Authorize', { if (isValidCredentialResponse(credData)) {
...this.credentialData, url = await this.credentialsStore.oAuth2Authorize(credData);
id: credential.id, }
})) as string;
} else if ( } else if (
this.credentialTypeName === 'oAuth1Api' || this.credentialTypeName === 'oAuth1Api' ||
types.includes('oAuth1Api') types.includes('oAuth1Api')
) { ) {
url = (await this.$store.dispatch('credentials/oAuth1Authorize', { if (isValidCredentialResponse(credData)) {
...this.credentialData, url = await this.credentialsStore.oAuth1Authorize(credData);
id: credential.id, }
})) as string;
} }
} catch (error) { } catch (error) {
this.$showError( this.$showError(
@ -971,7 +953,7 @@ export default mixins(showMessage, nodeHelpers).extend({
// Set some kind of data that status changes. // Set some kind of data that status changes.
// As data does not get displayed directly it does not matter what data. // As data does not get displayed directly it does not matter what data.
Vue.set(this.credentialData, 'oauthTokenData', {}); Vue.set(this.credentialData, 'oauthTokenData', {});
this.$store.commit('credentials/enableOAuthCredential', credential); this.credentialsStore.enableOAuthCredential(credential);
// Close the window // Close the window
if (oauthPopup) { if (oauthPopup) {

View file

@ -39,6 +39,7 @@ import mixins from "vue-typed-mixins";
import {showMessage} from "@/components/mixins/showMessage"; import {showMessage} from "@/components/mixins/showMessage";
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
import { useUsersStore } from '@/stores/users'; import { useUsersStore } from '@/stores/users';
import { useCredentialsStore } from "@/stores/credentials";
export default mixins( export default mixins(
showMessage, showMessage,
@ -46,7 +47,10 @@ export default mixins(
name: 'CredentialSharing', name: 'CredentialSharing',
props: ['credential', 'credentialId', 'credentialData', 'sharedWith', 'credentialPermissions'], props: ['credential', 'credentialId', 'credentialData', 'sharedWith', 'credentialPermissions'],
computed: { computed: {
...mapStores(useUsersStore), ...mapStores(
useCredentialsStore,
useUsersStore,
),
usersList(): IUser[] { usersList(): IUser[] {
return this.usersStore.allUsers.filter((user: IUser) => { return this.usersStore.allUsers.filter((user: IUser) => {
const isCurrentUser = user.id === this.usersStore.currentUser?.id; const isCurrentUser = user.id === this.usersStore.currentUser?.id;
@ -64,7 +68,7 @@ export default mixins(
].concat(this.credentialData.sharedWith || []); ].concat(this.credentialData.sharedWith || []);
}, },
credentialOwnerName(): string { credentialOwnerName(): string {
return this.$store.getters['credentials/getCredentialOwnerName'](this.credentialId); return this.credentialsStore.getCredentialOwnerName(this.credentialId);
}, },
}, },
methods: { methods: {

View file

@ -7,6 +7,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { useCredentialsStore } from '@/stores/credentials';
import { useRootStore } from '@/stores/n8nRootStore'; import { useRootStore } from '@/stores/n8nRootStore';
import { useNodeTypesStore } from '@/stores/nodeTypes'; import { useNodeTypesStore } from '@/stores/nodeTypes';
import { ICredentialType, INodeTypeDescription } from 'n8n-workflow'; import { ICredentialType, INodeTypeDescription } from 'n8n-workflow';
@ -21,6 +22,7 @@ export default Vue.extend({
}, },
computed: { computed: {
...mapStores( ...mapStores(
useCredentialsStore,
useNodeTypesStore, useNodeTypesStore,
useRootStore, useRootStore,
), ),
@ -42,8 +44,7 @@ export default Vue.extend({
const nodeType = this.credentialWithIcon.icon.replace('node:', ''); const nodeType = this.credentialWithIcon.icon.replace('node:', '');
return this.nodeTypesStore.getNodeType(nodeType); return this.nodeTypesStore.getNodeType(nodeType);
} }
const nodesWithAccess = this.credentialsStore.getNodesWithAccess(this.credentialTypeName);
const nodesWithAccess = this.$store.getters['credentials/getNodesWithAccess'](this.credentialTypeName);
if (nodesWithAccess.length) { if (nodesWithAccess.length) {
return nodesWithAccess[0]; return nodesWithAccess[0];
@ -53,8 +54,12 @@ export default Vue.extend({
}, },
}, },
methods: { methods: {
getCredentialWithIcon(name: string): ICredentialType | null { getCredentialWithIcon(name: string | null): ICredentialType | null {
const type = this.$store.getters['credentials/getCredentialTypeByName'](name); if (!name) {
return null;
}
const type = this.credentialsStore.getCredentialTypeByName(name);
if (!type) { if (!type) {
return null; return null;
@ -65,9 +70,12 @@ export default Vue.extend({
} }
if (type.extends) { if (type.extends) {
return type.extends.reduce((accu: string | null, type: string) => { let parentCred = null;
return accu || this.getCredentialWithIcon(type); type.extends.forEach(name => {
}, null); parentCred = this.getCredentialWithIcon(name);
if (parentCred !== null) return;
});
return parentCred;
} }
return null; return null;

View file

@ -53,9 +53,10 @@
<script lang="ts"> <script lang="ts">
import { ICredentialType } from 'n8n-workflow'; import { ICredentialType } from 'n8n-workflow';
import Vue from 'vue'; import Vue from 'vue';
import { mapGetters } from 'vuex';
import ScopesNotice from '@/components/ScopesNotice.vue'; import ScopesNotice from '@/components/ScopesNotice.vue';
import NodeCredentials from '@/components/NodeCredentials.vue'; import NodeCredentials from '@/components/NodeCredentials.vue';
import { mapStores } from 'pinia';
import { useCredentialsStore } from '@/stores/credentials';
export default Vue.extend({ export default Vue.extend({
name: 'CredentialsSelect', name: 'CredentialsSelect',
@ -73,11 +74,16 @@ export default Vue.extend({
'displayTitle', 'displayTitle',
], ],
computed: { computed: {
...mapGetters('credentials', ['allCredentialTypes', 'getScopesByCredentialType']), ...mapStores(
useCredentialsStore,
),
allCredentialTypes(): ICredentialType[] {
return this.credentialsStore.allCredentialTypes;
},
scopes(): string[] { scopes(): string[] {
if (!this.activeCredentialType) return []; if (!this.activeCredentialType) return [];
return this.getScopesByCredentialType(this.activeCredentialType); return this.credentialsStore.getScopesByCredentialType(this.activeCredentialType);
}, },
supportedCredentialTypes(): ICredentialType[] { supportedCredentialTypes(): ICredentialType[] {
return this.allCredentialTypes.filter((c: ICredentialType) => this.isSupported(c.name)); return this.allCredentialTypes.filter((c: ICredentialType) => this.isSupported(c.name));
@ -97,10 +103,10 @@ export default Vue.extend({
isSupported(name: string): boolean { isSupported(name: string): boolean {
const supported = this.getSupportedSets(this.parameter.credentialTypes); const supported = this.getSupportedSets(this.parameter.credentialTypes);
const checkedCredType = this.$store.getters['credentials/getCredentialTypeByName'](name); const checkedCredType = this.credentialsStore.getCredentialTypeByName(name);
for (const property of supported.has) { for (const property of supported.has) {
if (checkedCredType[property] !== undefined) { if (checkedCredType[property as keyof ICredentialType] !== undefined) {
// edge case: `httpHeaderAuth` has `authenticate` auth but belongs to generic auth // edge case: `httpHeaderAuth` has `authenticate` auth but belongs to generic auth
if (name === 'httpHeaderAuth' && property === 'authenticate') continue; if (name === 'httpHeaderAuth' && property === 'authenticate') continue;

View file

@ -25,7 +25,7 @@
> >
<font-awesome-icon icon="search" slot="prefix" /> <font-awesome-icon icon="search" slot="prefix" />
<n8n-option <n8n-option
v-for="credential in allCredentialTypes" v-for="credential in credentialsStore.allCredentialTypes"
:value="credential.name" :value="credential.name"
:key="credential.name" :key="credential.name"
:label="credential.displayName" :label="credential.displayName"
@ -50,7 +50,6 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import { mapGetters } from "vuex";
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import Modal from './Modal.vue'; import Modal from './Modal.vue';
@ -59,6 +58,7 @@ import { externalHooks } from '@/components/mixins/externalHooks';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui'; import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows'; import { useWorkflowsStore } from '@/stores/workflows';
import { useCredentialsStore } from '@/stores/credentials';
export default mixins(externalHooks).extend({ export default mixins(externalHooks).extend({
name: 'CredentialsSelectModal', name: 'CredentialsSelectModal',
@ -67,7 +67,7 @@ export default mixins(externalHooks).extend({
}, },
async mounted() { async mounted() {
try { try {
await this.$store.dispatch('credentials/fetchCredentialTypes'); await this.credentialsStore.fetchCredentialTypes(false);
} catch (e) { } catch (e) {
} }
this.loading = false; this.loading = false;
@ -89,10 +89,10 @@ export default mixins(externalHooks).extend({
}, },
computed: { computed: {
...mapStores( ...mapStores(
useCredentialsStore,
useUIStore, useUIStore,
useWorkflowsStore, useWorkflowsStore,
), ),
...mapGetters('credentials', ['allCredentialTypes']),
}, },
methods: { methods: {
onSelect(type: string) { onSelect(type: string) {

View file

@ -42,6 +42,7 @@ import { useWorkflowsStore } from '@/stores/workflows';
import { useUIStore } from '@/stores/ui'; import { useUIStore } from '@/stores/ui';
import { useSettingsStore } from '@/stores/settings'; import { useSettingsStore } from '@/stores/settings';
import { useNodeTypesStore } from '@/stores/nodeTypes'; import { useNodeTypesStore } from '@/stores/nodeTypes';
import { useTagsStore } from '@/stores/tags';
export default mixins(restApi, showMessage, executionHelpers, debounceHelper, workflowHelpers).extend({ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, workflowHelpers).extend({
name: 'executions-page', name: 'executions-page',
@ -57,6 +58,7 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
}, },
computed: { computed: {
...mapStores( ...mapStores(
useTagsStore,
useNodeTypesStore, useNodeTypesStore,
useSettingsStore, useSettingsStore,
useUIStore, useUIStore,
@ -374,7 +376,7 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
this.workflowsStore.setWorkflowTagIds(tagIds || []); this.workflowsStore.setWorkflowTagIds(tagIds || []);
this.workflowsStore.setWorkflowHash(data.hash); this.workflowsStore.setWorkflowHash(data.hash);
this.$store.commit('tags/upsertTags', tags); this.tagsStore.upsertTags(tags);
this.$externalHooks().run('workflow.open', { workflowId, workflowName: data.name }); this.$externalHooks().run('workflow.open', { workflowId, workflowName: data.name });
this.uiStore.stateIsDirty = false; this.uiStore.stateIsDirty = false;

View file

@ -31,7 +31,7 @@ import Modal from "./Modal.vue";
import Vue from "vue"; import Vue from "vue";
import { IFormInputs, IInviteResponse } from "@/Interface"; import { IFormInputs, IInviteResponse } from "@/Interface";
import { VALID_EMAIL_REGEX, INVITE_USER_MODAL_KEY } from "@/constants"; import { VALID_EMAIL_REGEX, INVITE_USER_MODAL_KEY } from "@/constants";
import { ROLE } from "@/modules/userHelpers"; import { ROLE } from "@/stores/userHelpers";
import { mapStores } from "pinia"; import { mapStores } from "pinia";
import { useUsersStore } from "@/stores/users"; import { useUsersStore } from "@/stores/users";

View file

@ -82,7 +82,6 @@
<script lang="ts"> <script lang="ts">
import Vue from "vue"; import Vue from "vue";
import mixins from "vue-typed-mixins"; import mixins from "vue-typed-mixins";
import { mapGetters } from "vuex";
import { import {
DUPLICATE_MODAL_KEY, DUPLICATE_MODAL_KEY,
MAX_WORKFLOW_NAME_LENGTH, MAX_WORKFLOW_NAME_LENGTH,
@ -110,6 +109,7 @@ import { useUIStore } from "@/stores/ui";
import { useSettingsStore } from "@/stores/settings"; import { useSettingsStore } from "@/stores/settings";
import { useWorkflowsStore } from "@/stores/workflows"; import { useWorkflowsStore } from "@/stores/workflows";
import { useRootStore } from "@/stores/n8nRootStore"; import { useRootStore } from "@/stores/n8nRootStore";
import { useTagsStore } from "@/stores/tags";
const hasChanged = (prev: string[], curr: string[]) => { const hasChanged = (prev: string[], curr: string[]) => {
if (prev.length !== curr.length) { if (prev.length !== curr.length) {
@ -144,6 +144,7 @@ export default mixins(workflowHelpers, titleChange).extend({
}, },
computed: { computed: {
...mapStores( ...mapStores(
useTagsStore,
useRootStore, useRootStore,
useSettingsStore, useSettingsStore,
useUIStore, useUIStore,
@ -350,7 +351,7 @@ export default mixins(workflowHelpers, titleChange).extend({
instanceId: this.rootStore.instanceId, instanceId: this.rootStore.instanceId,
}, },
tags: (tags || []).map(tagId => { tags: (tags || []).map(tagId => {
const {usageCount, ...tag} = this.$store.getters["tags/getTagById"](tagId); const {usageCount, ...tag} = this.tagsStore.getTagById(tagId);
return tag; return tag;
}), }),

View file

@ -55,6 +55,7 @@
import { import {
IExecutionResponse, IExecutionResponse,
IMenuItem, IMenuItem,
IVersion,
} from '../Interface'; } from '../Interface';
import ExecutionsList from '@/components/ExecutionsList.vue'; import ExecutionsList from '@/components/ExecutionsList.vue';
@ -69,7 +70,6 @@ import { workflowHelpers } from '@/components/mixins/workflowHelpers';
import { workflowRun } from '@/components/mixins/workflowRun'; import { workflowRun } from '@/components/mixins/workflowRun';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { mapGetters } from 'vuex';
import { import {
MODAL_CANCEL, MODAL_CANCEL,
MODAL_CLOSE, MODAL_CLOSE,
@ -89,6 +89,7 @@ import { useSettingsStore } from '@/stores/settings';
import { useUsersStore } from '@/stores/users'; import { useUsersStore } from '@/stores/users';
import { useWorkflowsStore } from '@/stores/workflows'; import { useWorkflowsStore } from '@/stores/workflows';
import { useRootStore } from '@/stores/n8nRootStore'; import { useRootStore } from '@/stores/n8nRootStore';
import { useVersionsStore } from '@/stores/versions';
export default mixins( export default mixins(
genericHelpers, genericHelpers,
@ -120,12 +121,15 @@ export default mixins(
useSettingsStore, useSettingsStore,
useUIStore, useUIStore,
useUsersStore, useUsersStore,
useVersionsStore,
useWorkflowsStore, useWorkflowsStore,
), ),
...mapGetters('versions', [ hasVersionUpdates(): boolean {
'hasVersionUpdates', return this.versionsStore.hasVersionUpdates;
'nextVersions', },
]), nextVersions(): IVersion[] {
return this.versionsStore.nextVersions;
},
isCollapsed(): boolean { isCollapsed(): boolean {
return this.uiStore.sidebarMenuCollapsed; return this.uiStore.sidebarMenuCollapsed;
}, },

View file

@ -442,7 +442,7 @@ export default mixins(
}, },
onClick(event: MouseEvent) { onClick(event: MouseEvent) {
this.callDebounced('onClickDebounced', { debounceTime: 300, trailing: true }, event); this.callDebounced('onClickDebounced', { debounceTime: 50, trailing: true }, event);
}, },
onClickDebounced(event: MouseEvent) { onClickDebounced(event: MouseEvent) {

View file

@ -96,6 +96,7 @@ import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows'; import { useWorkflowsStore } from '@/stores/workflows';
import { useRootStore } from '@/stores/n8nRootStore'; import { useRootStore } from '@/stores/n8nRootStore';
import { useNodeTypesStore } from '@/stores/nodeTypes'; import { useNodeTypesStore } from '@/stores/nodeTypes';
import { useNodeCreatorStore } from '@/stores/nodeCreator';
export default mixins(externalHooks, globalLinkActions).extend({ export default mixins(externalHooks, globalLinkActions).extend({
name: 'CategorizedItems', name: 'CategorizedItems',
@ -146,11 +147,12 @@ export default mixins(externalHooks, globalLinkActions).extend({
this.registerCustomAction('showAllNodeCreatorNodes', this.switchToAllTabAndFilter); this.registerCustomAction('showAllNodeCreatorNodes', this.switchToAllTabAndFilter);
}, },
destroyed() { destroyed() {
this.$store.commit('nodeCreator/setFilter', ''); this.nodeCreatorStore.itemsFilter = '';
this.unregisterCustomAction('showAllNodeCreatorNodes'); this.unregisterCustomAction('showAllNodeCreatorNodes');
}, },
computed: { computed: {
...mapStores( ...mapStores(
useNodeCreatorStore,
useNodeTypesStore, useNodeTypesStore,
useRootStore, useRootStore,
useWorkflowsStore, useWorkflowsStore,
@ -159,10 +161,10 @@ export default mixins(externalHooks, globalLinkActions).extend({
return this.activeSubcategoryHistory[this.activeSubcategoryHistory.length - 1] || null; return this.activeSubcategoryHistory[this.activeSubcategoryHistory.length - 1] || null;
}, },
nodeFilter(): string { nodeFilter(): string {
return this.$store.getters['nodeCreator/itemsFilter']; return this.nodeCreatorStore.itemsFilter;
}, },
selectedType(): INodeFilterType { selectedType(): INodeFilterType {
return this.$store.getters['nodeCreator/selectedType']; return this.nodeCreatorStore.selectedType;
}, },
categoriesWithNodes(): ICategoriesWithNodes { categoriesWithNodes(): ICategoriesWithNodes {
return this.nodeTypesStore.categoriesWithNodes; return this.nodeTypesStore.categoriesWithNodes;
@ -363,14 +365,14 @@ export default mixins(externalHooks, globalLinkActions).extend({
}, },
switchToAllTabAndFilter() { switchToAllTabAndFilter() {
const currentFilter = this.nodeFilter; const currentFilter = this.nodeFilter;
this.$store.commit('nodeCreator/setShowTabs', true); this.nodeCreatorStore.showTabs = true;
this.$store.commit('nodeCreator/setSelectedType', ALL_NODE_FILTER); this.nodeCreatorStore.selectedType = ALL_NODE_FILTER;
this.activeSubcategoryHistory = []; this.activeSubcategoryHistory = [];
this.$nextTick(() => this.$store.commit('nodeCreator/setFilter', currentFilter)); this.$nextTick(() => this.nodeCreatorStore.itemsFilter = currentFilter);
}, },
onNodeFilterChange(filter: string) { onNodeFilterChange(filter: string) {
this.$store.commit('nodeCreator/setFilter', filter); this.nodeCreatorStore.itemsFilter = filter;
}, },
selectWebhook() { selectWebhook() {
this.$emit('nodeTypeSelected', WEBHOOK_NODE_TYPE); this.$emit('nodeTypeSelected', WEBHOOK_NODE_TYPE);
@ -462,7 +464,7 @@ export default mixins(externalHooks, globalLinkActions).extend({
}, },
onSubcategorySelected(selected: INodeCreateElement) { onSubcategorySelected(selected: INodeCreateElement) {
this.$emit('onSubcategorySelected', selected); this.$emit('onSubcategorySelected', selected);
this.$store.commit('nodeCreator/setShowTabs', false); this.nodeCreatorStore.showTabs = false;
this.activeSubcategoryIndex = 0; this.activeSubcategoryIndex = 0;
this.activeSubcategoryHistory.push(selected); this.activeSubcategoryHistory.push(selected);
this.$telemetry.trackNodesPanel('nodeCreateList.onSubcategorySelected', { selected, workflow_id: this.workflowsStore.workflowId }); this.$telemetry.trackNodesPanel('nodeCreateList.onSubcategorySelected', { selected, workflow_id: this.workflowsStore.workflowId });
@ -472,10 +474,10 @@ export default mixins(externalHooks, globalLinkActions).extend({
this.$emit('subcategoryClose', this.activeSubcategory); this.$emit('subcategoryClose', this.activeSubcategory);
this.activeSubcategoryHistory.pop(); this.activeSubcategoryHistory.pop();
this.activeSubcategoryIndex = 0; this.activeSubcategoryIndex = 0;
this.$store.commit('nodeCreator/setFilter', ''); this.nodeCreatorStore.itemsFilter = '';
if(!this.$store.getters['nodeCreator/showScrim']) { if (!this.nodeCreatorStore.showScrim) {
this.$store.commit('nodeCreator/setShowTabs', true); this.nodeCreatorStore.showTabs = true;
} }
}, },

View file

@ -20,6 +20,7 @@ import { INodeCreateElement, ICategoriesWithNodes } from '@/Interface';
import { NODE_TYPE_COUNT_MAPPER } from '@/constants'; import { NODE_TYPE_COUNT_MAPPER } from '@/constants';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
import { useNodeTypesStore } from '@/stores/nodeTypes'; import { useNodeTypesStore } from '@/stores/nodeTypes';
import { useNodeCreatorStore } from '@/stores/nodeCreator';
export default Vue.extend({ export default Vue.extend({
@ -30,10 +31,11 @@ export default Vue.extend({
}, },
computed: { computed: {
...mapStores( ...mapStores(
useNodeCreatorStore,
useNodeTypesStore, useNodeTypesStore,
), ),
selectedType(): "Regular" | "Trigger" | "All" { selectedType(): "Regular" | "Trigger" | "All" {
return this.$store.getters['nodeCreator/selectedType']; return this.nodeCreatorStore.selectedType;
}, },
categoriesWithNodes(): ICategoriesWithNodes { categoriesWithNodes(): ICategoriesWithNodes {
return this.nodeTypesStore.categoriesWithNodes; return this.nodeTypesStore.categoriesWithNodes;

View file

@ -35,6 +35,7 @@ import TypeSelector from './TypeSelector.vue';
import { INodeCreateElement } from '@/Interface'; import { INodeCreateElement } from '@/Interface';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows'; import { useWorkflowsStore } from '@/stores/workflows';
import { useNodeCreatorStore } from '@/stores/nodeCreator';
export default mixins(externalHooks).extend({ export default mixins(externalHooks).extend({
name: 'NodeCreateList', name: 'NodeCreateList',
@ -58,10 +59,11 @@ export default mixins(externalHooks).extend({
}, },
computed: { computed: {
...mapStores( ...mapStores(
useNodeCreatorStore,
useWorkflowsStore, useWorkflowsStore,
), ),
selectedType(): string { selectedType(): string {
return this.$store.getters['nodeCreator/selectedType']; return this.nodeCreatorStore.selectedType;
}, },
}, },
watch: { watch: {
@ -80,10 +82,10 @@ export default mixins(externalHooks).extend({
mounted() { mounted() {
this.$externalHooks().run('nodeCreateList.mounted'); this.$externalHooks().run('nodeCreateList.mounted');
// Make sure tabs are visible on mount // Make sure tabs are visible on mount
this.$store.commit('nodeCreator/setShowTabs', true); this.nodeCreatorStore.showTabs = true;
}, },
destroyed() { destroyed() {
this.$store.commit('nodeCreator/setSelectedType', ALL_NODE_FILTER); this.nodeCreatorStore.selectedType = ALL_NODE_FILTER;
this.$externalHooks().run('nodeCreateList.destroyed'); this.$externalHooks().run('nodeCreateList.destroyed');
this.$telemetry.trackNodesPanel('nodeCreateList.destroyed', { workflow_id: this.workflowsStore.workflowId }); this.$telemetry.trackNodesPanel('nodeCreateList.destroyed', { workflow_id: this.workflowsStore.workflowId });
}, },

View file

@ -32,6 +32,7 @@ import MainPanel from './MainPanel.vue';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui'; import { useUIStore } from '@/stores/ui';
import { useNodeTypesStore } from '@/stores/nodeTypes'; import { useNodeTypesStore } from '@/stores/nodeTypes';
import { useNodeCreatorStore } from '@/stores/nodeCreator';
export default Vue.extend({ export default Vue.extend({
name: 'NodeCreator', name: 'NodeCreator',
@ -46,11 +47,12 @@ export default Vue.extend({
}, },
computed: { computed: {
...mapStores( ...mapStores(
useNodeCreatorStore,
useNodeTypesStore, useNodeTypesStore,
useUIStore, useUIStore,
), ),
showScrim(): boolean { showScrim(): boolean {
return this.$store.getters['nodeCreator/showScrim']; return this.nodeCreatorStore.showScrim;
}, },
visibleNodeTypes(): INodeTypeDescription[] { visibleNodeTypes(): INodeTypeDescription[] {
return this.nodeTypesStore.visibleNodeTypes; return this.nodeTypesStore.visibleNodeTypes;
@ -104,7 +106,7 @@ export default Vue.extend({
}, },
watch: { watch: {
active(isActive) { active(isActive) {
if(isActive === false) this.$store.commit('nodeCreator/setShowScrim', false); if(isActive === false) this.nodeCreatorStore.showScrim = false;
}, },
}, },
}); });

View file

@ -10,6 +10,9 @@
<script lang="ts"> <script lang="ts">
import { ALL_NODE_FILTER, REGULAR_NODE_FILTER, TRIGGER_NODE_FILTER } from '@/constants'; import { ALL_NODE_FILTER, REGULAR_NODE_FILTER, TRIGGER_NODE_FILTER } from '@/constants';
import { INodeFilterType } from '@/Interface';
import { useNodeCreatorStore } from '@/stores/nodeCreator';
import { mapStores } from 'pinia';
import Vue from 'vue'; import Vue from 'vue';
export default Vue.extend({ export default Vue.extend({
@ -22,16 +25,19 @@ export default Vue.extend({
}; };
}, },
methods: { methods: {
setType(type: string) { setType(type: INodeFilterType) {
this.$store.commit('nodeCreator/setSelectedType', type); this.nodeCreatorStore.selectedType = type;
}, },
}, },
computed: { computed: {
...mapStores(
useNodeCreatorStore,
),
showTabs(): boolean { showTabs(): boolean {
return this.$store.getters['nodeCreator/showTabs']; return this.nodeCreatorStore.showTabs;
}, },
selectedType(): string { selectedType(): string {
return this.$store.getters['nodeCreator/selectedType']; return this.nodeCreatorStore.selectedType;
}, },
}, },
}); });

View file

@ -69,7 +69,6 @@ import {
ICredentialType, ICredentialType,
INodeCredentialDescription, INodeCredentialDescription,
INodeCredentialsDetails, INodeCredentialsDetails,
INodeTypeDescription,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { genericHelpers } from '@/components/mixins/genericHelpers'; import { genericHelpers } from '@/components/mixins/genericHelpers';
@ -78,8 +77,6 @@ import { showMessage } from '@/components/mixins/showMessage';
import TitledList from '@/components/TitledList.vue'; import TitledList from '@/components/TitledList.vue';
import { mapGetters } from "vuex";
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import {getCredentialPermissions} from "@/permissions"; import {getCredentialPermissions} from "@/permissions";
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
@ -87,6 +84,7 @@ import { useUIStore } from '@/stores/ui';
import { useUsersStore } from '@/stores/users'; import { useUsersStore } from '@/stores/users';
import { useWorkflowsStore } from '@/stores/workflows'; import { useWorkflowsStore } from '@/stores/workflows';
import { useNodeTypesStore } from '@/stores/nodeTypes'; import { useNodeTypesStore } from '@/stores/nodeTypes';
import { useCredentialsStore } from '@/stores/credentials';
export default mixins( export default mixins(
genericHelpers, genericHelpers,
@ -105,20 +103,23 @@ export default mixins(
data () { data () {
return { return {
NEW_CREDENTIALS_TEXT: `- ${this.$locale.baseText('nodeCredentials.createNew')} -`, NEW_CREDENTIALS_TEXT: `- ${this.$locale.baseText('nodeCredentials.createNew')} -`,
newCredentialUnsubscribe: null as null | (() => void), subscribedToCredentialType: '',
}; };
}, },
mounted() {
this.listenForNewCredentials();
},
computed: { computed: {
...mapStores( ...mapStores(
useCredentialsStore,
useNodeTypesStore, useNodeTypesStore,
useUIStore, useUIStore,
useUsersStore, useUsersStore,
useWorkflowsStore, useWorkflowsStore,
), ),
...mapGetters('credentials', { allCredentialsByType(): {[type: string]: ICredentialsResponse[]} {
allCredentialsByType: 'allCredentialsByType', return this.credentialsStore.allCredentialsByType;
getCredentialTypeByName: 'getCredentialTypeByName', },
}),
currentUser (): IUser { currentUser (): IUser {
return this.usersStore.currentUser || {} as IUser; return this.usersStore.currentUser || {} as IUser;
}, },
@ -135,7 +136,7 @@ export default mixins(
credentialTypesNodeDescription (): INodeCredentialDescription[] { credentialTypesNodeDescription (): INodeCredentialDescription[] {
const node = this.node as INodeUi; const node = this.node as INodeUi;
const credType = this.getCredentialTypeByName(this.overrideCredType); const credType = this.credentialsStore.getCredentialTypeByName(this.overrideCredType);
if (credType) return [credType]; if (credType) return [credType];
@ -152,7 +153,7 @@ export default mixins(
} = {}; } = {};
let credentialType: ICredentialType | null; let credentialType: ICredentialType | null;
for (const credentialTypeName of this.credentialTypesNode) { for (const credentialTypeName of this.credentialTypesNode) {
credentialType = this.$store.getters['credentials/getCredentialTypeByName'](credentialTypeName); credentialType = this.credentialsStore.getCredentialTypeByName(credentialTypeName);
returnData[credentialTypeName] = credentialType !== null ? credentialType.displayName : credentialTypeName; returnData[credentialTypeName] = credentialType !== null ? credentialType.displayName : credentialTypeName;
} }
return returnData; return returnData;
@ -165,7 +166,7 @@ export default mixins(
methods: { methods: {
getCredentialOptions(type: string): ICredentialsResponse[] { getCredentialOptions(type: string): ICredentialsResponse[] {
return (this.allCredentialsByType as Record<string, ICredentialsResponse[]>)[type].filter((credential) => { return (this.allCredentialsByType as Record<string, ICredentialsResponse[]>)[type].filter((credential) => {
const permissions = getCredentialPermissions(this.currentUser, credential, this.$store); const permissions = getCredentialPermissions(this.currentUser, credential);
return permissions.use; return permissions.use;
}); });
@ -191,27 +192,31 @@ export default mixins(
return styles; return styles;
}, },
// TODO: Investigate if this can be solved using only the store data (storing selected flag in credentials objects, ...)
listenForNewCredentials(credentialType: string) { listenForNewCredentials() {
this.stopListeningForNewCredentials(); // Listen for credentials store changes so credential selection can be updated if creds are changed from the modal
this.credentialsStore.$subscribe((mutation, state) => {
this.newCredentialUnsubscribe = this.$store.subscribe((mutation, state) => { // This data pro stores credential type that the component is currently interested in
if (mutation.type === 'credentials/upsertCredential' || mutation.type === 'credentials/enableOAuthCredential'){ const credentialType = this.subscribedToCredentialType;
this.onCredentialSelected(credentialType, mutation.payload.id); const credentialsOfType = this.credentialsStore.allCredentialsByType[credentialType].sort((a, b) => (a.id < b.id ? -1 : 1));
} if (credentialsOfType.length > 0) {
if (mutation.type === 'credentials/deleteCredential') { // If nothing has been selected previously, select the first one (newly added)
this.clearSelectedCredential(credentialType); if (!this.selected[credentialType]) {
this.stopListeningForNewCredentials(); 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);
}
}
} }
this.subscribedToCredentialType = '';
}); });
}, },
stopListeningForNewCredentials() {
if (this.newCredentialUnsubscribe) {
this.newCredentialUnsubscribe();
}
},
clearSelectedCredential(credentialType: string) { clearSelectedCredential(credentialType: string) {
const node: INodeUi = this.node; const node: INodeUi = this.node;
@ -233,7 +238,10 @@ export default mixins(
onCredentialSelected (credentialType: string, credentialId: string | null | undefined) { onCredentialSelected (credentialType: string, credentialId: string | null | undefined) {
if (credentialId === this.NEW_CREDENTIALS_TEXT) { if (credentialId === this.NEW_CREDENTIALS_TEXT) {
this.listenForNewCredentials(credentialType); // this.listenForNewCredentials(credentialType);
this.subscribedToCredentialType = credentialType;
}
if (!credentialId || credentialId === this.NEW_CREDENTIALS_TEXT) {
this.uiStore.openNewCredential(credentialType); this.uiStore.openNewCredential(credentialType);
this.$telemetry.track('User opened Credential modal', { credential_type: credentialType, source: 'node', new_credential: true, workflow_id: this.workflowsStore.workflowId }); this.$telemetry.track('User opened Credential modal', { credential_type: credentialType, source: 'node', new_credential: true, workflow_id: this.workflowsStore.workflowId });
return; return;
@ -250,13 +258,13 @@ export default mixins(
}, },
); );
const selectedCredentials = this.$store.getters['credentials/getCredentialById'](credentialId); const selectedCredentials = this.credentialsStore.getCredentialById(credentialId);
const oldCredentials = this.node.credentials && this.node.credentials[credentialType] ? this.node.credentials[credentialType] : {}; const oldCredentials = this.node.credentials && this.node.credentials[credentialType] ? this.node.credentials[credentialType] : {};
const selected = { id: selectedCredentials.id, name: selectedCredentials.name }; const selected = { id: selectedCredentials.id, name: selectedCredentials.name };
// if credentials has been string or neither id matched nor name matched uniquely // if credentials has been string or neither id matched nor name matched uniquely
if (oldCredentials.id === null || (oldCredentials.id && !this.$store.getters['credentials/getCredentialByIdAndType'](oldCredentials.id, credentialType))) { if (oldCredentials.id === null || (oldCredentials.id && !this.credentialsStore.getCredentialByIdAndType(oldCredentials.id, credentialType))) {
// update all nodes in the workflow with the same old/invalid credentials // update all nodes in the workflow with the same old/invalid credentials
this.workflowsStore.replaceInvalidWorkflowCredentials({ this.workflowsStore.replaceInvalidWorkflowCredentials({
credentials: selected, credentials: selected,
@ -333,13 +341,9 @@ export default mixins(
this.uiStore.openExistingCredential(id); this.uiStore.openExistingCredential(id);
this.$telemetry.track('User opened Credential modal', { credential_type: credentialType, source: 'node', new_credential: false, workflow_id: this.workflowsStore.workflowId }); this.$telemetry.track('User opened Credential modal', { credential_type: credentialType, source: 'node', new_credential: false, workflow_id: this.workflowsStore.workflowId });
this.subscribedToCredentialType = credentialType;
this.listenForNewCredentials(credentialType);
}, },
}, },
beforeDestroy () {
this.stopListeningForNewCredentials();
},
}); });
</script> </script>

View file

@ -132,7 +132,6 @@ import Vue from 'vue';
import OutputPanel from './OutputPanel.vue'; import OutputPanel from './OutputPanel.vue';
import InputPanel from './InputPanel.vue'; import InputPanel from './InputPanel.vue';
import TriggerPanel from './TriggerPanel.vue'; import TriggerPanel from './TriggerPanel.vue';
import { mapGetters } from 'vuex';
import { import {
BASE_NODE_SURVEY_URL, BASE_NODE_SURVEY_URL,
START_NODE_TYPE, START_NODE_TYPE,

View file

@ -669,7 +669,7 @@ export default mixins(externalHooks, nodeHelpers).extend({
} }
// Update the data in vuex // Update the data in vuex
const updateInformation = { const updateInformation: IUpdateInformation = {
name: node.name, name: node.name,
value: nodeParameters, value: nodeParameters,
}; };

View file

@ -335,7 +335,6 @@ import { isResourceLocatorValue } from '@/typeGuards';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { CUSTOM_API_CALL_KEY } from '@/constants'; import { CUSTOM_API_CALL_KEY } from '@/constants';
import { mapGetters } from 'vuex';
import { CODE_NODE_TYPE } from '@/constants'; import { CODE_NODE_TYPE } from '@/constants';
import { PropType } from 'vue'; import { PropType } from 'vue';
import { debounceHelper } from './mixins/debounce'; import { debounceHelper } from './mixins/debounce';
@ -343,6 +342,7 @@ import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows'; import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv'; import { useNDVStore } from '@/stores/ndv';
import { useNodeTypesStore } from '@/stores/nodeTypes'; import { useNodeTypesStore } from '@/stores/nodeTypes';
import { useCredentialsStore } from '@/stores/credentials';
export default mixins( export default mixins(
externalHooks, externalHooks,
@ -477,11 +477,11 @@ export default mixins(
}, },
computed: { computed: {
...mapStores( ...mapStores(
useCredentialsStore,
useNodeTypesStore, useNodeTypesStore,
useNDVStore, useNDVStore,
useWorkflowsStore, useWorkflowsStore,
), ),
...mapGetters('credentials', ['allCredentialTypes']),
expressionDisplayValue(): string { expressionDisplayValue(): string {
if (this.activeDrop || this.forceShowExpression) { if (this.activeDrop || this.forceShowExpression) {
return ''; return '';
@ -563,14 +563,14 @@ export default mixins(
returnValue = this.expressionEvaluated; returnValue = this.expressionEvaluated;
} }
if (this.parameter.type === 'credentialsSelect') { if (this.parameter.type === 'credentialsSelect' && typeof this.value === 'string') {
const credType = this.$store.getters['credentials/getCredentialTypeByName'](this.value); const credType = this.credentialsStore.getCredentialTypeByName(this.value);
if (credType) { if (credType) {
returnValue = credType.displayName; returnValue = credType.displayName;
} }
} }
if (this.parameter.type === 'color' && this.getArgument('showAlpha') === true && returnValue.charAt(0) === '#') { if (Array.isArray(returnValue) && this.parameter.type === 'color' && this.getArgument('showAlpha') === true && returnValue.charAt(0) === '#') {
// Convert the value to rgba that el-color-picker can display it correctly // Convert the value to rgba that el-color-picker can display it correctly
const bigint = parseInt(returnValue.slice(1), 16); const bigint = parseInt(returnValue.slice(1), 16);
const h = []; const h = [];

View file

@ -118,7 +118,7 @@ import { showMessage } from '@/components/mixins/showMessage';
import Modal from './Modal.vue'; import Modal from './Modal.vue';
import { IFormInputs, IPersonalizationLatestVersion, IPersonalizationSurveyAnswersV3, IUser } from '@/Interface'; import { IFormInputs, IPersonalizationLatestVersion, IPersonalizationSurveyAnswersV3, IUser } from '@/Interface';
import Vue from 'vue'; import Vue from 'vue';
import { getAccountAge } from '@/modules/userHelpers'; import { getAccountAge } from '@/stores/userHelpers';
import { GenericValue } from 'n8n-workflow'; import { GenericValue } from 'n8n-workflow';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui'; import { useUIStore } from '@/stores/ui';

View file

@ -6,8 +6,9 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { useCredentialsStore } from '@/stores/credentials';
import { mapStores } from 'pinia';
import Vue from 'vue'; import Vue from 'vue';
import { mapGetters } from 'vuex';
export default Vue.extend({ export default Vue.extend({
name: 'ScopesNotice', name: 'ScopesNotice',
@ -16,7 +17,9 @@ export default Vue.extend({
'scopes', 'scopes',
], ],
computed: { computed: {
...mapGetters('credentials', ['getCredentialTypeByName']), ...mapStores(
useCredentialsStore,
),
scopesShortContent (): string { scopesShortContent (): string {
return this.$locale.baseText( return this.$locale.baseText(
'nodeSettings.scopes.notice', 'nodeSettings.scopes.notice',
@ -46,7 +49,7 @@ export default Vue.extend({
const oauth1Api = this.$locale.baseText('generic.oauth1Api'); const oauth1Api = this.$locale.baseText('generic.oauth1Api');
const oauth2Api = this.$locale.baseText('generic.oauth2Api'); const oauth2Api = this.$locale.baseText('generic.oauth2Api');
return this.getCredentialTypeByName(this.activeCredentialType).displayName return this.credentialsStore.getCredentialTypeByName(this.activeCredentialType).displayName
.replace(new RegExp(`${oauth1Api}|${oauth2Api}`), '') .replace(new RegExp(`${oauth1Api}|${oauth2Api}`), '')
.trim(); .trim();
}, },

View file

@ -44,6 +44,8 @@ import Vue from 'vue';
import { ITag } from '@/Interface'; import { ITag } from '@/Interface';
import IntersectionObserver from './IntersectionObserver.vue'; import IntersectionObserver from './IntersectionObserver.vue';
import IntersectionObserved from './IntersectionObserved.vue'; import IntersectionObserved from './IntersectionObserved.vue';
import { mapStores } from 'pinia';
import { useTagsStore } from '@/stores/tags';
// random upper limit if none is set to minimize performance impact of observers // random upper limit if none is set to minimize performance impact of observers
const DEFAULT_MAX_TAGS_LIMIT = 20; const DEFAULT_MAX_TAGS_LIMIT = 20;
@ -70,8 +72,11 @@ export default Vue.extend({
}; };
}, },
computed: { computed: {
...mapStores(
useTagsStore,
),
tags() { tags() {
const tags = this.$props.tagIds.map((tagId: string) => this.$store.getters['tags/getTagById'](tagId)) const tags = this.$props.tagIds.map((tagId: string) => this.tagsStore.getTagById(tagId))
.filter(Boolean); // if tag has been deleted from store .filter(Boolean); // if tag has been deleted from store
const limit = this.$props.limit || DEFAULT_MAX_TAGS_LIMIT; const limit = this.$props.limit || DEFAULT_MAX_TAGS_LIMIT;

View file

@ -3,7 +3,7 @@
<n8n-select <n8n-select
:popperAppendToBody="false" :popperAppendToBody="false"
:value="appliedTags" :value="appliedTags"
:loading="isLoading" :loading="tagsStore.isLoading"
:placeholder="placeholder" :placeholder="placeholder"
:filter-method="filterOptions" :filter-method="filterOptions"
@change="onTagsUpdated" @change="onTagsUpdated"
@ -54,7 +54,6 @@
<script lang="ts"> <script lang="ts">
import mixins from "vue-typed-mixins"; import mixins from "vue-typed-mixins";
import { mapGetters } from "vuex";
import { ITag } from "@/Interface"; import { ITag } from "@/Interface";
import { MAX_TAG_NAME_LENGTH, TAGS_MANAGER_MODAL_KEY } from "@/constants"; import { MAX_TAG_NAME_LENGTH, TAGS_MANAGER_MODAL_KEY } from "@/constants";
@ -62,6 +61,7 @@ import { MAX_TAG_NAME_LENGTH, TAGS_MANAGER_MODAL_KEY } from "@/constants";
import { showMessage } from "@/components/mixins/showMessage"; import { showMessage } from "@/components/mixins/showMessage";
import { mapStores } from "pinia"; import { mapStores } from "pinia";
import { useUIStore } from "@/stores/ui"; import { useUIStore } from "@/stores/ui";
import { useTagsStore } from "@/stores/tags";
const MANAGE_KEY = "__manage"; const MANAGE_KEY = "__manage";
const CREATE_KEY = "__create"; const CREATE_KEY = "__create";
@ -112,11 +112,19 @@ export default mixins(showMessage).extend({
}); });
} }
this.$store.dispatch("tags/fetchAll"); this.tagsStore.fetchAll();
}, },
computed: { computed: {
...mapStores(useUIStore), ...mapStores(
...mapGetters("tags", ["allTags", "isLoading", "hasTags"]), useTagsStore,
useUIStore,
),
allTags(): ITag[] {
return this.tagsStore.allTags;
},
hasTags(): boolean {
return this.tagsStore.hasTags;
},
options(): ITag[] { options(): ITag[] {
return this.allTags return this.allTags
.filter((tag: ITag) => .filter((tag: ITag) =>
@ -125,7 +133,7 @@ export default mixins(showMessage).extend({
}, },
appliedTags(): string[] { appliedTags(): string[] {
return this.$props.currentTagIds.filter((id: string) => return this.$props.currentTagIds.filter((id: string) =>
this.$store.getters['tags/getTagById'](id), this.tagsStore.getTagById(id),
); );
}, },
}, },
@ -137,7 +145,7 @@ export default mixins(showMessage).extend({
async onCreate() { async onCreate() {
const name = this.$data.filter; const name = this.$data.filter;
try { try {
const newTag = await this.$store.dispatch("tags/create", name); const newTag = await this.tagsStore.create(name);
this.$emit("update", [...this.$props.currentTagIds, newTag.id]); this.$emit("update", [...this.$props.currentTagIds, newTag.id]);
this.$nextTick(() => this.focusOnTag(newTag.id)); this.$nextTick(() => this.focusOnTag(newTag.id));

View file

@ -33,7 +33,6 @@
<script lang="ts"> <script lang="ts">
import Vue from "vue"; import Vue from "vue";
import mixins from "vue-typed-mixins"; import mixins from "vue-typed-mixins";
import { mapGetters } from "vuex";
import { ITag } from "@/Interface"; import { ITag } from "@/Interface";
@ -42,16 +41,16 @@ import TagsView from "@/components/TagsManager/TagsView/TagsView.vue";
import NoTagsView from "@/components/TagsManager/NoTagsView.vue"; import NoTagsView from "@/components/TagsManager/NoTagsView.vue";
import Modal from "@/components/Modal.vue"; import Modal from "@/components/Modal.vue";
import { TAGS_MANAGER_MODAL_KEY } from '../../constants'; import { TAGS_MANAGER_MODAL_KEY } from '../../constants';
import { mapStores } from "pinia";
import { useTagsStore } from "@/stores/tags";
export default mixins(showMessage).extend({ export default mixins(showMessage).extend({
name: "TagsManager", name: "TagsManager",
created() { created() {
this.$store.dispatch("tags/fetchAll", {force: true, withUsageCount: true}); this.tagsStore.fetchAll({force: true, withUsageCount: true});
}, },
data() { data() {
const tagIds = (this.$store.getters['tags/allTags'] as ITag[]) const tagIds = useTagsStore().allTags.map((tag) => tag.id);
.map((tag) => tag.id);
return { return {
tagIds, tagIds,
isCreating: false, isCreating: false,
@ -65,9 +64,14 @@ export default mixins(showMessage).extend({
Modal, Modal,
}, },
computed: { computed: {
...mapGetters("tags", ["isLoading"]), ...mapStores(
useTagsStore,
),
isLoading(): boolean {
return this.tagsStore.isLoading;
},
tags(): ITag[] { tags(): ITag[] {
return this.$data.tagIds.map((tagId: string) => this.$store.getters['tags/getTagById'](tagId)) return this.$data.tagIds.map((tagId: string) => this.tagsStore.getTagById(tagId))
.filter(Boolean); // if tag is deleted from store .filter(Boolean); // if tag is deleted from store
}, },
hasTags(): boolean { hasTags(): boolean {
@ -91,7 +95,7 @@ export default mixins(showMessage).extend({
); );
} }
const newTag = await this.$store.dispatch("tags/create", name); const newTag = await this.tagsStore.create(name);
this.$data.tagIds = [newTag.id].concat(this.$data.tagIds); this.$data.tagIds = [newTag.id].concat(this.$data.tagIds);
cb(newTag); cb(newTag);
} catch (error) { } catch (error) {
@ -109,7 +113,7 @@ export default mixins(showMessage).extend({
}, },
async onUpdate(id: string, name: string, cb: (tag: boolean, error?: Error) => void) { async onUpdate(id: string, name: string, cb: (tag: boolean, error?: Error) => void) {
const tag = this.$store.getters['tags/getTagById'](id); const tag = this.tagsStore.getTagById(id);
const oldName = tag.name; const oldName = tag.name;
try { try {
@ -124,7 +128,7 @@ export default mixins(showMessage).extend({
return; return;
} }
const updatedTag = await this.$store.dispatch("tags/rename", { id, name }); const updatedTag = await this.tagsStore.rename({ id, name });
cb(!!updatedTag); cb(!!updatedTag);
this.$showMessage({ this.$showMessage({
@ -146,11 +150,11 @@ export default mixins(showMessage).extend({
}, },
async onDelete(id: string, cb: (deleted: boolean, error?: Error) => void) { async onDelete(id: string, cb: (deleted: boolean, error?: Error) => void) {
const tag = this.$store.getters['tags/getTagById'](id); const tag = this.tagsStore.getTagById(id);
const name = tag.name; const name = tag.name;
try { try {
const deleted = await this.$store.dispatch("tags/delete", id); const deleted = await this.tagsStore.delete(id);
if (!deleted) { if (!deleted) {
throw new Error( throw new Error(
this.$locale.baseText('tagsManager.couldNotDeleteTag'), this.$locale.baseText('tagsManager.couldNotDeleteTag'),

View file

@ -31,7 +31,6 @@ import Vue from "vue";
import { ITag, ITagRow } from "@/Interface"; import { ITag, ITagRow } from "@/Interface";
import TagsTableHeader from "@/components/TagsManager/TagsView/TagsTableHeader.vue"; import TagsTableHeader from "@/components/TagsManager/TagsView/TagsTableHeader.vue";
import TagsTable from "@/components/TagsManager/TagsView/TagsTable.vue"; import TagsTable from "@/components/TagsManager/TagsView/TagsTable.vue";
import { mapGetters } from 'vuex';
import { mapStores } from "pinia"; import { mapStores } from "pinia";
import { useUsersStore } from "@/stores/users"; import { useUsersStore } from "@/stores/users";

View file

@ -52,7 +52,6 @@ export default mixins(externalHooks).extend({
{ {
instanceId: this.rootStore.instanceId, instanceId: this.rootStore.instanceId,
userId: this.currentUserId, userId: this.currentUserId,
store: this.$store,
versionCli: this.rootStore.versionCli, versionCli: this.rootStore.versionCli,
}, },
); );

View file

@ -8,7 +8,6 @@
import { format, LocaleFunc, register } from 'timeago.js'; import { format, LocaleFunc, register } from 'timeago.js';
import { convertToHumanReadableDate } from './helpers'; import { convertToHumanReadableDate } from './helpers';
import Vue from 'vue'; import Vue from 'vue';
import { mapGetters } from 'vuex';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
import { useRootStore } from '@/stores/n8nRootStore'; import { useRootStore } from '@/stores/n8nRootStore';

View file

@ -54,12 +54,14 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import { mapGetters } from 'vuex';
import ModalDrawer from './ModalDrawer.vue'; import ModalDrawer from './ModalDrawer.vue';
import TimeAgo from './TimeAgo.vue'; import TimeAgo from './TimeAgo.vue';
import VersionCard from './VersionCard.vue'; import VersionCard from './VersionCard.vue';
import { VERSIONS_MODAL_KEY } from '../constants'; import { VERSIONS_MODAL_KEY } from '../constants';
import { mapStores } from 'pinia';
import { useVersionsStore } from '@/stores/versions';
import { IVersion } from '@/Interface';
export default Vue.extend({ export default Vue.extend({
name: 'UpdatesPanel', name: 'UpdatesPanel',
@ -69,7 +71,18 @@ export default Vue.extend({
TimeAgo, TimeAgo,
}, },
computed: { computed: {
...mapGetters('versions', ['nextVersions', 'currentVersion', 'infoUrl']), ...mapStores(
useVersionsStore,
),
nextVersions(): IVersion[] {
return this.versionsStore.nextVersions;
},
currentVersion(): IVersion | undefined {
return this.versionsStore.currentVersion;
},
infoUrl(): string {
return this.versionsStore.infoUrl;
},
}, },
data() { data() {
return { return {

View file

@ -119,7 +119,7 @@ export default mixins(
return this.usersStore.currentUser || {} as IUser; return this.usersStore.currentUser || {} as IUser;
}, },
credentialPermissions(): IPermissions { credentialPermissions(): IPermissions {
return getWorkflowPermissions(this.currentUser, this.data, this.$store); return getWorkflowPermissions(this.currentUser, this.data);
}, },
actions(): Array<{ label: string; value: string; }> { actions(): Array<{ label: string; value: string; }> {
return [ return [

View file

@ -220,7 +220,6 @@ import { restApi } from '@/components/mixins/restApi';
import { genericHelpers } from '@/components/mixins/genericHelpers'; import { genericHelpers } from '@/components/mixins/genericHelpers';
import { showMessage } from '@/components/mixins/showMessage'; import { showMessage } from '@/components/mixins/showMessage';
import { import {
IN8nUISettings,
ITimeoutHMS, ITimeoutHMS,
IWorkflowDataUpdate, IWorkflowDataUpdate,
IWorkflowSettings, IWorkflowSettings,
@ -232,7 +231,6 @@ import { PLACEHOLDER_EMPTY_WORKFLOW_ID, WORKFLOW_SETTINGS_MODAL_KEY } from '../c
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { mapGetters } from "vuex";
import { deepCopy } from "n8n-workflow"; import { deepCopy } from "n8n-workflow";
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows'; import { useWorkflowsStore } from '@/stores/workflows';

View file

@ -1,11 +1,13 @@
import { IExternalHooks, IRootState } from '@/Interface'; import { IExternalHooks, IRootState } from '@/Interface';
import { store } from '@/store';
import { useWebhooksStore } from '@/stores/webhooks';
import { IDataObject } from 'n8n-workflow'; import { IDataObject } from 'n8n-workflow';
import { Store } from 'pinia';
import Vue from 'vue'; import Vue from 'vue';
import { Store } from 'vuex';
export async function runExternalHook( export async function runExternalHook(
eventName: string, eventName: string,
store: Store<IRootState>, store: Store,
metadata?: IDataObject, metadata?: IDataObject,
) { ) {
// @ts-ignore // @ts-ignore
@ -31,7 +33,7 @@ export const externalHooks = Vue.extend({
$externalHooks(): IExternalHooks { $externalHooks(): IExternalHooks {
return { return {
run: async (eventName: string, metadata?: IDataObject): Promise<void> => { run: async (eventName: string, metadata?: IDataObject): Promise<void> => {
await runExternalHook.call(this, eventName, this.$store, metadata); await runExternalHook.call(this, eventName, useWebhooksStore(), metadata);
}, },
}; };
}, },

View file

@ -1,29 +1,30 @@
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { showMessage } from './showMessage'; import { showMessage } from './showMessage';
import {
IVersion,
} from '../../Interface';
import { VERSIONS_MODAL_KEY } from '@/constants'; import { VERSIONS_MODAL_KEY } from '@/constants';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui'; import { useUIStore } from '@/stores/ui';
import { useVersionsStore } from '@/stores/versions';
export const newVersions = mixins( export const newVersions = mixins(
showMessage, showMessage,
).extend({ ).extend({
computed: { computed: {
...mapStores(useUIStore), ...mapStores(
useUIStore,
useVersionsStore,
),
}, },
methods: { methods: {
async checkForNewVersions() { async checkForNewVersions() {
const enabled = this.$store.getters['versions/areNotificationsEnabled']; const enabled = this.versionsStore.areNotificationsEnabled;
if (!enabled) { if (!enabled) {
return; return;
} }
await this.$store.dispatch('versions/fetchVersions'); await this.versionsStore.fetchVersions();
const currentVersion: IVersion | undefined = this.$store.getters['versions/currentVersion']; const currentVersion = this.versionsStore.currentVersion;
const nextVersions: IVersion[] = this.$store.getters['versions/nextVersions']; const nextVersions = this.versionsStore.nextVersions;
if (currentVersion && currentVersion.hasSecurityIssue && nextVersions.length) { if (currentVersion && currentVersion.hasSecurityIssue && nextVersions.length) {
const fixVersion = currentVersion.securityIssueFixVersion; const fixVersion = currentVersion.securityIssueFixVersion;
let message = `Please update to latest version.`; let message = `Please update to latest version.`;

View file

@ -12,13 +12,11 @@ import {
INodeCredentialsDetails, INodeCredentialsDetails,
INodeExecutionData, INodeExecutionData,
INodeIssues, INodeIssues,
INodeIssueData,
INodeIssueObjectProperty, INodeIssueObjectProperty,
INodeParameters, INodeParameters,
INodeProperties, INodeProperties,
INodeTypeDescription, INodeTypeDescription,
IRunData, IRunData,
IRunExecutionData,
ITaskDataConnections, ITaskDataConnections,
INode, INode,
INodePropertyOptions, INodePropertyOptions,
@ -37,7 +35,6 @@ import { restApi } from '@/components/mixins/restApi';
import { get } from 'lodash'; import { get } from 'lodash';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { mapGetters } from 'vuex';
import { isObjectLiteral } from '@/utils'; import { isObjectLiteral } from '@/utils';
import {getCredentialPermissions} from "@/permissions"; import {getCredentialPermissions} from "@/permissions";
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
@ -45,6 +42,7 @@ import { useSettingsStore } from '@/stores/settings';
import { useUsersStore } from '@/stores/users'; import { useUsersStore } from '@/stores/users';
import { useWorkflowsStore } from '@/stores/workflows'; import { useWorkflowsStore } from '@/stores/workflows';
import { useNodeTypesStore } from '@/stores/nodeTypes'; import { useNodeTypesStore } from '@/stores/nodeTypes';
import { useCredentialsStore } from '@/stores/credentials';
export const nodeHelpers = mixins( export const nodeHelpers = mixins(
restApi, restApi,
@ -52,11 +50,11 @@ export const nodeHelpers = mixins(
.extend({ .extend({
computed: { computed: {
...mapStores( ...mapStores(
useCredentialsStore,
useNodeTypesStore, useNodeTypesStore,
useSettingsStore, useSettingsStore,
useWorkflowsStore, useWorkflowsStore,
), ),
...mapGetters('credentials', [ 'getCredentialTypeByName', 'getCredentialsByType' ]),
}, },
methods: { methods: {
hasProxyAuth (node: INodeUi): boolean { hasProxyAuth (node: INodeUi): boolean {
@ -138,7 +136,7 @@ export const nodeHelpers = mixins(
// Set the status on all the nodes which produced an error so that it can be // Set the status on all the nodes which produced an error so that it can be
// displayed in the node-view // displayed in the node-view
hasNodeExecutionIssues (node: INodeUi): boolean { hasNodeExecutionIssues (node: INodeUi): boolean {
const workflowResultData: IRunData = this.workflowsStore.getWorkflowRunData; const workflowResultData = this.workflowsStore.getWorkflowRunData;
if (workflowResultData === null || !workflowResultData.hasOwnProperty(node.name)) { if (workflowResultData === null || !workflowResultData.hasOwnProperty(node.name)) {
return false; return false;
@ -247,7 +245,7 @@ export const nodeHelpers = mixins(
let credentialType: ICredentialType | null; let credentialType: ICredentialType | null;
let credentialDisplayName: string; let credentialDisplayName: string;
let selectedCredentials: INodeCredentialsDetails; let selectedCredentials: INodeCredentialsDetails;
const foreignCredentials = this.$store.getters['credentials/allForeignCredentials']; const foreignCredentials = this.credentialsStore.allForeignCredentials;
// TODO: Check if any of the node credentials is found in foreign credentials // TODO: Check if any of the node credentials is found in foreign credentials
if(foreignCredentials?.some(() => true)){ if(foreignCredentials?.some(() => true)){
@ -269,7 +267,7 @@ export const nodeHelpers = mixins(
genericAuthType !== '' && genericAuthType !== '' &&
selectedCredsAreUnusable(node, genericAuthType) selectedCredsAreUnusable(node, genericAuthType)
) { ) {
const credential = this.getCredentialTypeByName(genericAuthType); const credential = this.credentialsStore.getCredentialTypeByName(genericAuthType);
return this.reportUnsetCredential(credential); return this.reportUnsetCredential(credential);
} }
@ -279,10 +277,10 @@ export const nodeHelpers = mixins(
nodeCredentialType !== '' && nodeCredentialType !== '' &&
node.credentials !== undefined node.credentials !== undefined
) { ) {
const stored = this.getCredentialsByType(nodeCredentialType); const stored = this.credentialsStore.getCredentialsByType(nodeCredentialType);
if (selectedCredsDoNotExist(node, nodeCredentialType, stored)) { if (selectedCredsDoNotExist(node, nodeCredentialType, stored)) {
const credential = this.getCredentialTypeByName(nodeCredentialType); const credential = this.credentialsStore.getCredentialTypeByName(nodeCredentialType);
return this.reportUnsetCredential(credential); return this.reportUnsetCredential(credential);
} }
} }
@ -293,7 +291,7 @@ export const nodeHelpers = mixins(
nodeCredentialType !== '' && nodeCredentialType !== '' &&
selectedCredsAreUnusable(node, nodeCredentialType) selectedCredsAreUnusable(node, nodeCredentialType)
) { ) {
const credential = this.getCredentialTypeByName(nodeCredentialType); const credential = this.credentialsStore.getCredentialTypeByName(nodeCredentialType);
return this.reportUnsetCredential(credential); return this.reportUnsetCredential(credential);
} }
@ -304,7 +302,7 @@ export const nodeHelpers = mixins(
} }
// Get the display name of the credential type // Get the display name of the credential type
credentialType = this.$store.getters['credentials/getCredentialTypeByName'](credentialTypeDescription.name); credentialType = this.credentialsStore.getCredentialTypeByName(credentialTypeDescription.name);
if (credentialType === null) { if (credentialType === null) {
credentialDisplayName = credentialTypeDescription.name; credentialDisplayName = credentialTypeDescription.name;
} else { } else {
@ -328,9 +326,9 @@ export const nodeHelpers = mixins(
const usersStore = useUsersStore(); const usersStore = useUsersStore();
const currentUser = usersStore.currentUser || {} as IUser; const currentUser = usersStore.currentUser || {} as IUser;
userCredentials = this.$store.getters['credentials/getCredentialsByType'](credentialTypeDescription.name) userCredentials = this.credentialsStore.getCredentialsByType(credentialTypeDescription.name)
.filter((credential: ICredentialsResponse) => { .filter((credential: ICredentialsResponse) => {
const permissions = getCredentialPermissions(currentUser, credential, this.$store); const permissions = getCredentialPermissions(currentUser, credential);
return permissions.use; return permissions.use;
}); });

View file

@ -27,6 +27,7 @@ import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui'; import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows'; import { useWorkflowsStore } from '@/stores/workflows';
import { useNodeTypesStore } from '@/stores/nodeTypes'; import { useNodeTypesStore } from '@/stores/nodeTypes';
import { useCredentialsStore } from '@/stores/credentials';
export const pushConnection = mixins( export const pushConnection = mixins(
externalHooks, externalHooks,
@ -46,6 +47,7 @@ export const pushConnection = mixins(
}, },
computed: { computed: {
...mapStores( ...mapStores(
useCredentialsStore,
useNodeTypesStore, useNodeTypesStore,
useUIStore, useUIStore,
useWorkflowsStore, useWorkflowsStore,
@ -443,7 +445,7 @@ export const pushConnection = mixins(
const nodesToBeRemoved: INodeTypeNameVersion[] = [pushData]; const nodesToBeRemoved: INodeTypeNameVersion[] = [pushData];
// Force reload of all credential types // Force reload of all credential types
this.$store.dispatch('credentials/fetchCredentialTypes') this.credentialsStore.fetchCredentialTypes()
.then(() => { .then(() => {
this.nodeTypesStore.removeNodeTypes(nodesToBeRemoved); this.nodeTypesStore.removeNodeTypes(nodesToBeRemoved);
}); });

View file

@ -1,5 +1,5 @@
import { IPermissions, IUser } from '@/Interface'; import { IPermissions, IUser } from '@/Interface';
import { isAuthorized } from '@/modules/userHelpers'; import { isAuthorized } from '@/stores/userHelpers';
import { useUsersStore } from '@/stores/users'; import { useUsersStore } from '@/stores/users';
import Vue from 'vue'; import Vue from 'vue';
import { Route } from 'vue-router'; import { Route } from 'vue-router';

View file

@ -421,4 +421,9 @@ export enum STORES {
NDV = 'ndv', NDV = 'ndv',
TEMPLATES = 'templates', TEMPLATES = 'templates',
NODE_TYPES = 'nodeTypes', NODE_TYPES = 'nodeTypes',
CREDENTIALS = 'credentials',
TAGS = 'tags',
VERSIONS = 'versions',
NODE_CREATOR = 'nodeCreator',
WEBHOOKS = 'webhooks',
} }

View file

@ -24,15 +24,12 @@ import { I18nPlugin, i18nInstance } from './plugins/i18n';
import { createPinia, PiniaVuePlugin } from 'pinia'; import { createPinia, PiniaVuePlugin } from 'pinia';
import { store } from './store'; import { useWebhooksStore } from './stores/webhooks';
Vue.config.productionTip = false; Vue.config.productionTip = false;
router.afterEach((to, from) => {
runExternalHook('main.routeChange', store, { from, to });
});
Vue.use(TelemetryPlugin); Vue.use(TelemetryPlugin);
Vue.use((vue) => I18nPlugin(vue, store)); Vue.use((vue) => I18nPlugin(vue));
Vue.use(PiniaVuePlugin); Vue.use(PiniaVuePlugin);
const pinia = createPinia(); const pinia = createPinia();
@ -40,11 +37,14 @@ const pinia = createPinia();
new Vue({ new Vue({
i18n: i18nInstance, i18n: i18nInstance,
router, router,
store,
pinia, pinia,
render: h => h(App), render: h => h(App),
}).$mount('#app'); }).$mount('#app');
router.afterEach((to, from) => {
runExternalHook('main.routeChange', useWebhooksStore(), { from, to });
});
if (import.meta.env.NODE_ENV !== 'production') { if (import.meta.env.NODE_ENV !== 'production') {
// Make sure that we get all error messages properly displayed // Make sure that we get all error messages properly displayed
// as long as we are not in production mode // as long as we are not in production mode

View file

@ -1,54 +0,0 @@
import Vue from 'vue';
import {ActionContext, Module} from 'vuex';
import {
ICredentialsState,
IRootState, IUser,
} from '../Interface';
import {setCredentialSharedWith} from "@/api/credentials.ee";
import {EnterpriseEditionFeature} from "@/constants";
import { useSettingsStore } from '@/stores/settings';
import { useRootStore } from '@/stores/n8nRootStore';
export const credentialsEEModule: Module<ICredentialsState, IRootState> = {
mutations: {
setCredentialOwnedBy(state: ICredentialsState, payload: { credentialId: string, ownedBy: Partial<IUser> }) {
Vue.set(state.credentials[payload.credentialId], 'ownedBy', payload.ownedBy);
},
setCredentialSharedWith(state: ICredentialsState, payload: { credentialId: string, sharedWith: Array<Partial<IUser>> }) {
Vue.set(state.credentials[payload.credentialId], 'sharedWith', payload.sharedWith);
},
addCredentialSharee(state: ICredentialsState, payload: { credentialId: string, sharee: Partial<IUser> }) {
Vue.set(
state.credentials[payload.credentialId],
'sharedWith',
(state.credentials[payload.credentialId].sharedWith || []).concat([payload.sharee]),
);
},
removeCredentialSharee(state: ICredentialsState, payload: { credentialId: string, sharee: Partial<IUser> }) {
Vue.set(
state.credentials[payload.credentialId],
'sharedWith',
(state.credentials[payload.credentialId].sharedWith || [])
.filter((sharee) => sharee.id !== payload.sharee.id),
);
},
},
actions: {
setCredentialSharedWith: async (context: ActionContext<ICredentialsState, IRootState>, payload: { sharedWith: IUser[]; credentialId: string; }) => {
if(useSettingsStore().isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) {
await setCredentialSharedWith(
useRootStore().getRestApiContext,
payload.credentialId,
{
shareWithIds: payload.sharedWith.map((sharee) => sharee.id),
},
);
context.commit('setCredentialSharedWith', {
credentialId: payload.credentialId,
sharedWith: payload.sharedWith,
});
}
},
},
};

View file

@ -1,313 +0,0 @@
import {
getCredentialTypes,
getCredentialsNewName,
getAllCredentials,
deleteCredential,
getCredentialData,
createNewCredential,
updateCredential,
oAuth2CredentialAuthorize,
oAuth1CredentialAuthorize,
testCredential,
getForeignCredentials,
} from '@/api/credentials';
import Vue from 'vue';
import { ActionContext, Module } from 'vuex';
import {
ICredentialMap,
ICredentialsResponse,
ICredentialsState,
ICredentialTypeMap,
IRootState,
} from '@/Interface';
import {
ICredentialType,
ICredentialsDecrypted,
INodeCredentialTestResult,
INodeTypeDescription,
INodeProperties,
} from 'n8n-workflow';
import { getAppNameFromCredType } from '@/components/helpers';
import {i18n} from "@/plugins/i18n";
import {credentialsEEModule} from "@/modules/credentials.ee";
import {EnterpriseEditionFeature} from "@/constants";
import { useUsersStore } from '@/stores/users';
import { useNodeTypesStore } from '@/stores/nodeTypes';
import { useRootStore } from '@/stores/n8nRootStore';
import { useSettingsStore } from '@/stores/settings';
const DEFAULT_CREDENTIAL_NAME = 'Unnamed credential';
const DEFAULT_CREDENTIAL_POSTFIX = 'account';
const TYPES_WITH_DEFAULT_NAME = ['httpBasicAuth', 'oAuth2Api', 'httpDigestAuth', 'oAuth1Api'];
const module: Module<ICredentialsState, IRootState> = {
namespaced: true,
state: {
credentialTypes: {},
credentials: {},
...credentialsEEModule.state,
},
mutations: {
setCredentialTypes: (state: ICredentialsState, credentialTypes: ICredentialType[]) => {
state.credentialTypes = credentialTypes.reduce((accu: ICredentialTypeMap, cred: ICredentialType) => {
accu[cred.name] = cred;
return accu;
}, {});
},
setCredentials: (state: ICredentialsState, credentials: ICredentialsResponse[]) => {
state.credentials = credentials.reduce((accu: ICredentialMap, cred: ICredentialsResponse) => {
if (cred.id) {
accu[cred.id] = cred;
}
return accu;
}, {});
},
setForeignCredentials: (state: ICredentialsState, credentials: ICredentialsResponse[]) => {
state.foreignCredentials = credentials.reduce((accu: ICredentialMap, cred: ICredentialsResponse) => {
if (cred.id) {
accu[cred.id] = cred;
}
return accu;
}, {});
},
upsertCredential(state: ICredentialsState, credential: ICredentialsResponse) {
if (credential.id) {
Vue.set(state.credentials, credential.id, { ...state.credentials[credential.id], ...credential });
}
},
deleteCredential(state: ICredentialsState, id: string) {
Vue.delete(state.credentials, id);
},
enableOAuthCredential(state: ICredentialsState, credential: ICredentialsResponse) {
// enable oauth event to track change between modals
},
...credentialsEEModule.mutations,
},
getters: {
credentialTypesById(state: ICredentialsState): Record<ICredentialType['name'], ICredentialType> {
return state.credentialTypes;
},
allCredentialTypes(state: ICredentialsState): ICredentialType[] {
return Object.values(state.credentialTypes)
.sort((a, b) => a.displayName.localeCompare(b.displayName));
},
allCredentials(state: ICredentialsState): ICredentialsResponse[] {
return Object.values(state.credentials)
.sort((a, b) => a.name.localeCompare(b.name));
},
allForeignCredentials(state: ICredentialsState): ICredentialsResponse[] {
return Object.values(state.foreignCredentials || {})
.sort((a, b) => a.name.localeCompare(b.name));
},
allCredentialsByType(state: ICredentialsState, getters: any): {[type: string]: ICredentialsResponse[]} { // tslint:disable-line:no-any
const credentials = getters.allCredentials as ICredentialsResponse[];
const types = getters.allCredentialTypes as ICredentialType[];
return types.reduce((accu: {[type: string]: ICredentialsResponse[]}, type: ICredentialType) => {
accu[type.name] = credentials.filter((cred: ICredentialsResponse) => cred.type === type.name);
return accu;
}, {});
},
getCredentialTypeByName: (state: ICredentialsState) => {
return (type: string) => state.credentialTypes[type];
},
getCredentialById: (state: ICredentialsState) => {
return (id: string) => state.credentials[id];
},
getCredentialByIdAndType: (state: ICredentialsState) => {
return (id: string, type: string) => {
const credential = state.credentials[id];
return !credential || credential.type !== type ? undefined : credential;
};
},
getCredentialsByType: (state: ICredentialsState, getters: any) => { // tslint:disable-line:no-any
return (credentialType: string): ICredentialsResponse[] => {
return (getters.allCredentialsByType[credentialType] || []);
};
},
getNodesWithAccess (state: ICredentialsState, getters: any, rootState: IRootState, rootGetters: any) { // tslint:disable-line:no-any
return (credentialTypeName: string) => {
const nodeTypesStore = useNodeTypesStore();
const allLatestNodeTypes: INodeTypeDescription[] = nodeTypesStore.allLatestNodeTypes;
return allLatestNodeTypes.filter((nodeType: INodeTypeDescription) => {
if (!nodeType.credentials) {
return false;
}
for (const credentialTypeDescription of nodeType.credentials) {
if (credentialTypeDescription.name === credentialTypeName ) {
return true;
}
}
return false;
});
};
},
getScopesByCredentialType (_: ICredentialsState, getters: any) { // tslint:disable-line:no-any
return (credentialTypeName: string) => {
const credentialType = getters.getCredentialTypeByName(credentialTypeName) as {
properties: INodeProperties[];
};
const scopeProperty = credentialType.properties.find((p) => p.name === 'scope');
if (
!scopeProperty ||
!scopeProperty.default ||
typeof scopeProperty.default !== 'string' ||
scopeProperty.default === ''
) {
return [];
}
let { default: scopeDefault } = scopeProperty;
// disregard expressions for display
scopeDefault = scopeDefault.replace(/^=/, '').replace(/\{\{.*\}\}/, '');
if (/ /.test(scopeDefault)) return scopeDefault.split(' ');
if (/,/.test(scopeDefault)) return scopeDefault.split(',');
return [scopeDefault];
};
},
getCredentialOwnerName: (state: ICredentialsState, getters: any) => // tslint:disable-line:no-any
(credentialId: string): string => {
const credential = getters.getCredentialById(credentialId);
return credential && credential.ownedBy && credential.ownedBy.firstName
? `${credential.ownedBy.firstName} ${credential.ownedBy.lastName} (${credential.ownedBy.email})`
: i18n.baseText('credentialEdit.credentialSharing.info.sharee.fallback');
},
...credentialsEEModule.getters,
},
actions: {
fetchCredentialTypes: async (context: ActionContext<ICredentialsState, IRootState>, forceFetch: boolean) => {
if (context.getters.allCredentialTypes.length > 0 && forceFetch !== true) {
return;
}
const rootStore = useRootStore();
const credentialTypes = await getCredentialTypes(rootStore.getRestApiContext);
context.commit('setCredentialTypes', credentialTypes);
},
fetchAllCredentials: async (context: ActionContext<ICredentialsState, IRootState>): Promise<ICredentialsResponse[]> => {
const rootStore = useRootStore();
const credentials = await getAllCredentials(rootStore.getRestApiContext);
context.commit('setCredentials', credentials);
return credentials;
},
fetchForeignCredentials: async (context: ActionContext<ICredentialsState, IRootState>): Promise<ICredentialsResponse[]> => {
const rootStore = useRootStore();
const credentials = await getForeignCredentials(rootStore.getRestApiContext);
context.commit('setForeignCredentials', credentials);
return credentials;
},
getCredentialData: async (context: ActionContext<ICredentialsState, IRootState>, { id }: {id: string}) => {
const rootStore = useRootStore();
return await getCredentialData(rootStore.getRestApiContext, id);
},
createNewCredential: async (context: ActionContext<ICredentialsState, IRootState>, data: ICredentialsDecrypted) => {
const rootStore = useRootStore();
const settingsStore = useSettingsStore();
const credential = await createNewCredential(rootStore.getRestApiContext, data);
if (settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) {
context.commit('upsertCredential', credential);
if (data.ownedBy) {
context.commit('setCredentialOwnedBy', {
credentialId: credential.id,
ownedBy: data.ownedBy,
});
const usersStore = useUsersStore();
if (data.sharedWith && data.ownedBy.id === usersStore.currentUserId) {
await context.dispatch('setCredentialSharedWith', {
credentialId: credential.id,
sharedWith: data.sharedWith,
});
}
}
} else {
context.commit('upsertCredential', credential);
}
return credential;
},
updateCredential: async (context: ActionContext<ICredentialsState, IRootState>, params: {data: ICredentialsDecrypted, id: string}) => {
const { id, data } = params;
const rootStore = useRootStore();
const settingsStore = useSettingsStore();
const credential = await updateCredential(rootStore.getRestApiContext, id, data);
if (settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) {
context.commit('upsertCredential', credential);
if (data.ownedBy) {
context.commit('setCredentialOwnedBy', {
credentialId: credential.id,
ownedBy: data.ownedBy,
});
const usersStore = useUsersStore();
if (data.sharedWith && data.ownedBy.id === usersStore.currentUserId) {
await context.dispatch('setCredentialSharedWith', {
credentialId: credential.id,
sharedWith: data.sharedWith,
});
}
}
} else {
context.commit('upsertCredential', credential);
}
return credential;
},
deleteCredential: async (context: ActionContext<ICredentialsState, IRootState>, { id }: {id: string}) => {
const rootStore = useRootStore();
const deleted = await deleteCredential(rootStore.getRestApiContext, id);
if (deleted) {
context.commit('deleteCredential', id);
}
},
oAuth2Authorize: async (context: ActionContext<ICredentialsState, IRootState>, data: ICredentialsResponse) => {
const rootStore = useRootStore();
return oAuth2CredentialAuthorize(rootStore.getRestApiContext, data);
},
oAuth1Authorize: async (context: ActionContext<ICredentialsState, IRootState>, data: ICredentialsResponse) => {
const rootStore = useRootStore();
return oAuth1CredentialAuthorize(rootStore.getRestApiContext, data);
},
testCredential: async (context: ActionContext<ICredentialsState, IRootState>, data: ICredentialsDecrypted): Promise<INodeCredentialTestResult> => {
const rootStore = useRootStore();
return testCredential(rootStore.getRestApiContext, { credentials: data });
},
getNewCredentialName: async (context: ActionContext<ICredentialsState, IRootState>, params: { credentialTypeName: string }) => {
try {
const { credentialTypeName } = params;
let newName = DEFAULT_CREDENTIAL_NAME;
if (!TYPES_WITH_DEFAULT_NAME.includes(credentialTypeName)) {
const { displayName } = context.getters.getCredentialTypeByName(credentialTypeName);
newName = getAppNameFromCredType(displayName);
newName = newName.length > 0 ? `${newName} ${DEFAULT_CREDENTIAL_POSTFIX}` : DEFAULT_CREDENTIAL_NAME;
}
const rootStore = useRootStore();
const res = await getCredentialsNewName(rootStore.getRestApiContext, newName);
return res.name;
} catch (e) {
return DEFAULT_CREDENTIAL_NAME;
}
},
...credentialsEEModule.actions,
},
};
export default module;

View file

@ -1,39 +0,0 @@
import { ALL_NODE_FILTER } from '@/constants';
import { Module } from 'vuex';
import {
IRootState,
INodeCreatorState,
INodeFilterType,
} from '@/Interface';
const module: Module<INodeCreatorState, IRootState> = {
namespaced: true,
state: {
itemsFilter: '',
showTabs: true,
showScrim: false,
selectedType: ALL_NODE_FILTER,
},
getters: {
showTabs: (state: INodeCreatorState) => state.showTabs,
showScrim: (state: INodeCreatorState) => state.showScrim,
selectedType: (state: INodeCreatorState) => state.selectedType,
itemsFilter: (state: INodeCreatorState) => state.itemsFilter,
},
mutations: {
setShowTabs(state: INodeCreatorState, isVisible: boolean) {
state.showTabs = isVisible;
},
setShowScrim(state: INodeCreatorState, isVisible: boolean) {
state.showScrim = isVisible;
},
setSelectedType(state: INodeCreatorState, selectedNodeType: INodeFilterType) {
state.selectedType = selectedNodeType;
},
setFilter(state: INodeCreatorState, search: INodeFilterType) {
state.itemsFilter = search;
},
},
};
export default module;

View file

@ -1,112 +0,0 @@
import { ActionContext, Module } from 'vuex';
import {
ITag,
ITagsState,
IRootState,
} from '../Interface';
import { createTag, deleteTag, getTags, updateTag } from '../api/tags';
import Vue from 'vue';
import { useWorkflowsStore } from '@/stores/workflows';
import { useRootStore } from '@/stores/n8nRootStore';
const module: Module<ITagsState, IRootState> = {
namespaced: true,
state: {
tags: {},
isLoading: false,
fetchedAll: false,
fetchedUsageCount: false,
},
mutations: {
setLoading: (state: ITagsState, isLoading: boolean) => {
state.isLoading = isLoading;
},
setAllTags: (state: ITagsState, tags: ITag[]) => {
state.tags = tags
.reduce((accu: { [id: string]: ITag }, tag: ITag) => {
accu[tag.id] = tag;
return accu;
}, {});
state.fetchedAll = true;
},
upsertTags(state: ITagsState, tags: ITag[]) {
tags.forEach((tag) => {
const tagId = tag.id;
const currentTag = state.tags[tagId];
if (currentTag) {
const newTag = {
...currentTag,
...tag,
};
Vue.set(state.tags, tagId, newTag);
}
else {
Vue.set(state.tags, tagId, tag);
}
});
},
deleteTag(state: ITagsState, id: string) {
Vue.delete(state.tags, id);
},
},
getters: {
allTags(state: ITagsState): ITag[] {
return Object.values(state.tags)
.sort((a, b) => a.name.localeCompare(b.name));
},
isLoading: (state: ITagsState): boolean => {
return state.isLoading;
},
hasTags: (state: ITagsState): boolean => {
return Object.keys(state.tags).length > 0;
},
getTagById: (state: ITagsState) => {
return (id: string) => state.tags[id];
},
},
actions: {
fetchAll: async (context: ActionContext<ITagsState, IRootState>, params?: { force?: boolean, withUsageCount?: boolean }): Promise<ITag[]> => {
const { force = false, withUsageCount = false } = params || {};
if (!force && context.state.fetchedAll && context.state.fetchedUsageCount === withUsageCount) {
return Object.values(context.state.tags);
}
context.commit('setLoading', true);
const rootStore = useRootStore();
const tags = await getTags(rootStore.getRestApiContext, Boolean(withUsageCount));
context.commit('setAllTags', tags);
context.commit('setLoading', false);
return tags;
},
create: async (context: ActionContext<ITagsState, IRootState>, name: string): Promise<ITag> => {
const rootStore = useRootStore();
const tag = await createTag(rootStore.getRestApiContext, { name });
context.commit('upsertTags', [tag]);
return tag;
},
rename: async (context: ActionContext<ITagsState, IRootState>, { id, name }: { id: string, name: string }) => {
const rootStore = useRootStore();
const tag = await updateTag(rootStore.getRestApiContext, id, { name });
context.commit('upsertTags', [tag]);
return tag;
},
delete: async (context: ActionContext<ITagsState, IRootState>, id: string) => {
const rootStore = useRootStore();
const deleted = await deleteTag(rootStore.getRestApiContext, id);
if (deleted) {
context.commit('deleteTag', id);
const workflowsStore = useWorkflowsStore();
workflowsStore.removeWorkflowTagId(id);
}
return deleted;
},
},
};
export default module;

View file

@ -1,64 +0,0 @@
import { getNextVersions } from '@/api/versions';
import { useRootStore } from '@/stores/n8nRootStore';
import { ActionContext, Module } from 'vuex';
import {
IRootState,
IVersion,
IVersionsState,
} from '../Interface';
const module: Module<IVersionsState, IRootState> = {
namespaced: true,
state: {
versionNotificationSettings: {
enabled: false,
endpoint: '',
infoUrl: '',
},
nextVersions: [],
currentVersion: undefined,
},
getters: {
hasVersionUpdates(state: IVersionsState) {
return state.nextVersions.length > 0;
},
nextVersions(state: IVersionsState) {
return state.nextVersions;
},
currentVersion(state: IVersionsState) {
return state.currentVersion;
},
areNotificationsEnabled(state: IVersionsState) {
return state.versionNotificationSettings.enabled;
},
infoUrl(state: IVersionsState) {
return state.versionNotificationSettings.infoUrl;
},
},
mutations: {
setVersions(state: IVersionsState, {versions, currentVersion}: {versions: IVersion[], currentVersion: string}) {
state.nextVersions = versions.filter((version) => version.name !== currentVersion);
state.currentVersion = versions.find((version) => version.name === currentVersion);
},
setVersionNotificationSettings(state: IVersionsState, settings: {enabled: true, endpoint: string, infoUrl: string}) {
state.versionNotificationSettings = settings;
},
},
actions: {
async fetchVersions(context: ActionContext<IVersionsState, IRootState>) {
try {
const { enabled, endpoint } = context.state.versionNotificationSettings;
if (enabled && endpoint) {
const rootStore = useRootStore();
const currentVersion = rootStore.versionCli;
const instanceId = rootStore.instanceId;
const versions = await getNextVersions(endpoint, currentVersion, instanceId);
context.commit('setVersions', {versions, currentVersion});
}
} catch (e) {
}
},
},
};
export default module;

View file

@ -5,7 +5,6 @@
*/ */
import {IUser, ICredentialsResponse, IRootState, IWorkflowDb} from "@/Interface"; import {IUser, ICredentialsResponse, IRootState, IWorkflowDb} from "@/Interface";
import {Store} from "vuex";
import {EnterpriseEditionFeature} from "@/constants"; import {EnterpriseEditionFeature} from "@/constants";
import { useSettingsStore } from "./stores/settings"; import { useSettingsStore } from "./stores/settings";
@ -54,7 +53,7 @@ export const parsePermissionsTable = (user: IUser, table: IPermissionsTable): IP
* User permissions definition * User permissions definition
*/ */
export const getCredentialPermissions = (user: IUser, credential: ICredentialsResponse, store: Store<IRootState>) => { export const getCredentialPermissions = (user: IUser, credential: ICredentialsResponse) => {
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
const table: IPermissionsTable = [ const table: IPermissionsTable = [
{ name: UserRole.ResourceOwner, test: () => !!(credential && credential.ownedBy && credential.ownedBy.id === user.id) || !settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing) }, { name: UserRole.ResourceOwner, test: () => !!(credential && credential.ownedBy && credential.ownedBy.id === user.id) || !settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing) },
@ -72,7 +71,7 @@ export const getCredentialPermissions = (user: IUser, credential: ICredentialsRe
return parsePermissionsTable(user, table); return parsePermissionsTable(user, table);
}; };
export const getWorkflowPermissions = (user: IUser, workflow: IWorkflowDb, store: Store<IRootState>) => { export const getWorkflowPermissions = (user: IUser, workflow: IWorkflowDb) => {
const table: IPermissionsTable = [ const table: IPermissionsTable = [
// { name: UserRole.ResourceOwner, test: () => !!(workflow && workflow.ownedBy && workflow.ownedBy.id === user.id) || !useSettingsStore().isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing) }, // { name: UserRole.ResourceOwner, test: () => !!(workflow && workflow.ownedBy && workflow.ownedBy.id === user.id) || !useSettingsStore().isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing) },
{ name: UserRole.ResourceOwner, test: () => true }, { name: UserRole.ResourceOwner, test: () => true },

View file

@ -1,7 +1,6 @@
import Vue from 'vue'; import Vue from 'vue';
import axios from 'axios'; import axios from 'axios';
import VueI18n from 'vue-i18n'; import VueI18n from 'vue-i18n';
import { Store } from "vuex";
import { INodeTranslationHeaders, IRootState } from '@/Interface'; import { INodeTranslationHeaders, IRootState } from '@/Interface';
import { import {
deriveMiddleKey, deriveMiddleKey,
@ -23,7 +22,7 @@ locale.use('en');
export let i18n: I18nClass; export let i18n: I18nClass;
export function I18nPlugin(vue: typeof Vue, store: Store<IRootState>): void { export function I18nPlugin(vue: typeof Vue): void {
i18n = new I18nClass(); i18n = new I18nClass();
Object.defineProperty(vue, '$locale', { Object.defineProperty(vue, '$locale', {

View file

@ -7,9 +7,9 @@ import {
import { Route } from "vue-router"; import { Route } from "vue-router";
import type { INodeCreateElement, IRootState } from "@/Interface"; import type { INodeCreateElement, IRootState } from "@/Interface";
import type { Store } from "vuex";
import type { IUserNodesPanelSession } from "./telemetry.types"; import type { IUserNodesPanelSession } from "./telemetry.types";
import { useSettingsStore } from "@/stores/settings"; import { useSettingsStore } from "@/stores/settings";
import { useRootStore } from "@/stores/n8nRootStore";
export function TelemetryPlugin(vue: typeof _Vue): void { export function TelemetryPlugin(vue: typeof _Vue): void {
const telemetry = new Telemetry(); const telemetry = new Telemetry();
@ -26,7 +26,6 @@ export class Telemetry {
private pageEventQueue: Array<{route: Route}>; private pageEventQueue: Array<{route: Route}>;
private previousPath: string; private previousPath: string;
private store: Store<IRootState> | null;
private get rudderStack() { private get rudderStack() {
return window.rudderanalytics; return window.rudderanalytics;
@ -44,15 +43,13 @@ export class Telemetry {
constructor() { constructor() {
this.pageEventQueue = []; this.pageEventQueue = [];
this.previousPath = ''; this.previousPath = '';
this.store = null;
} }
init( init(
telemetrySettings: ITelemetrySettings, telemetrySettings: ITelemetrySettings,
{ instanceId, userId, store, versionCli }: { { instanceId, userId, versionCli }: {
instanceId: string; instanceId: string;
userId?: string; userId?: string;
store: Store<IRootState>;
versionCli: string versionCli: string
}, },
) { ) {
@ -60,9 +57,8 @@ export class Telemetry {
const { config: { key, url } } = telemetrySettings; const { config: { key, url } } = telemetrySettings;
// TODO: Remove this once migration to pinia is done
this.store = store;
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
const rootStore = useRootStore();
const logLevel = settingsStore.logLevel; const logLevel = settingsStore.logLevel;
@ -81,7 +77,7 @@ export class Telemetry {
this.identify(instanceId, userId, versionCli); this.identify(instanceId, userId, versionCli);
this.flushPageEvents(); this.flushPageEvents();
this.track('Session started', { session_id: store.getters.sessionId }); this.track('Session started', { session_id: rootStore.sessionId });
} }
identify(instanceId: string, userId?: string, versionCli?: string) { identify(instanceId: string, userId?: string, versionCli?: string) {
@ -100,7 +96,7 @@ export class Telemetry {
const updatedProperties = { const updatedProperties = {
...properties, ...properties,
version_cli: this.store && this.store.getters.versionCli, version_cli: useRootStore().versionCli,
}; };
this.rudderStack.track(event, updatedProperties); this.rudderStack.track(event, updatedProperties);
@ -115,7 +111,7 @@ export class Telemetry {
const pageName = route.name; const pageName = route.name;
let properties: {[key: string]: string} = {}; let properties: {[key: string]: string} = {};
if (this.store && route.meta && route.meta.telemetry && typeof route.meta.telemetry.getProperties === 'function') { if (route.meta && route.meta.telemetry && typeof route.meta.telemetry.getProperties === 'function') {
properties = route.meta.telemetry.getProperties(route); properties = route.meta.telemetry.getProperties(route);
} }

View file

@ -26,7 +26,7 @@ import TemplatesSearchView from '@/views/TemplatesSearchView.vue';
import CredentialsView from '@/views/CredentialsView.vue'; import CredentialsView from '@/views/CredentialsView.vue';
import WorkflowsView from '@/views/WorkflowsView.vue'; import WorkflowsView from '@/views/WorkflowsView.vue';
import { IPermissions } from './Interface'; import { IPermissions } from './Interface';
import { LOGIN_STATUS, ROLE } from './modules/userHelpers'; import { LOGIN_STATUS, ROLE } from './stores/userHelpers';
import { RouteConfigSingleView } from 'vue-router/types/router'; import { RouteConfigSingleView } from 'vue-router/types/router';
import { VIEWS } from './constants'; import { VIEWS } from './constants';
import { useSettingsStore } from './stores/settings'; import { useSettingsStore } from './stores/settings';

View file

@ -1,89 +0,0 @@
import Vue from 'vue';
import Vuex from 'vuex';
import credentials from './modules/credentials';
import tags from './modules/tags';
import nodeCreator from './modules/nodeCreator';
import versions from './modules/versions';
import { IMenuItem } from 'n8n-design-system';
import { useUIStore } from './stores/ui';
import { IFakeDoor, INodeUi, IRootState } from './Interface';
import { useSettingsStore } from './stores/settings';
import { useUsersStore } from './stores/users';
import { useRootStore } from './stores/n8nRootStore';
import { useWorkflowsStore } from './stores/workflows';
import { useNDVStore } from './stores/ndv';
import { IWorkflowSettings } from 'n8n-workflow';
Vue.use(Vuex);
// Everything here is kept just to be used by front-end web hooks
// as long as we have instances that use vuex store
const modules = {
credentials,
tags,
versions,
nodeCreator,
users: {
namespaced: true,
getters: { globalRoleName () { return useUsersStore().globalRoleName; } },
},
ui: {
namespaced: true,
getters: { getFakeDoorFeatures () { return useUIStore().fakeDoorFeatures; } },
},
settings: {
namespaced: true,
getters: { isUserManagementEnabled () { return useSettingsStore().isUserManagementEnabled; } },
},
};
export const store = new Vuex.Store({
strict: import.meta.env.NODE_ENV !== 'production',
modules,
mutations: {
addSidebarMenuItems(state: IRootState, menuItems: IMenuItem[]) {
const uiStore = useUIStore();
const updated = uiStore.sidebarMenuItems.concat(menuItems);
uiStore.sidebarMenuItems = updated;
},
setFakeDoorFeatures(state: IRootState, fakeDoors: IFakeDoor[]): void {
useUIStore().fakeDoorFeatures = fakeDoors;
},
},
getters: {
getFakeDoorItems(): IFakeDoor[] {
return useUIStore().fakeDoorFeatures;
},
isUserManagementEnabled(): boolean {
return useSettingsStore().isUserManagementEnabled;
},
n8nMetadata(): IRootState['n8nMetadata'] {
return useRootStore().n8nMetadata;
},
instanceId(): string {
return useRootStore().instanceId;
},
workflowId(): string {
return useWorkflowsStore().workflowId;
},
workflowName(): string {
return useWorkflowsStore().workflowName;
},
activeNode(): INodeUi | null {
return useNDVStore().activeNode;
},
workflowSettings(): IWorkflowSettings {
return useWorkflowsStore().workflowSettings;
},
activeExecutionId(): string {
return useWorkflowsStore().activeExecutionId || '';
},
nodeByName: (state: IRootState) => (nodeName: string): INodeUi | null => {
return useWorkflowsStore().getNodeByName(nodeName);
},
allNodes(): INodeUi[] {
return useWorkflowsStore().allNodes;
},
},
});

View file

@ -0,0 +1,305 @@
import { createNewCredential, deleteCredential, getAllCredentials, getCredentialData, getCredentialsNewName, getCredentialTypes, getForeignCredentials, oAuth1CredentialAuthorize, oAuth2CredentialAuthorize, testCredential, updateCredential } from "@/api/credentials";
import { setCredentialSharedWith } from "@/api/credentials.ee";
import { getAppNameFromCredType } from "@/components/helpers";
import { EnterpriseEditionFeature, STORES } from "@/constants";
import { ICredentialMap, ICredentialsDecryptedResponse, ICredentialsResponse, ICredentialsState, ICredentialTypeMap } from "@/Interface";
import { i18n } from "@/plugins/i18n";
import { ICredentialsDecrypted, ICredentialType, INodeCredentialTestResult, INodeProperties, INodeTypeDescription, IUser } from "n8n-workflow";
import { defineStore } from "pinia";
import Vue from "vue";
import { useRootStore } from "./n8nRootStore";
import { useNodeTypesStore } from "./nodeTypes";
import { useSettingsStore } from "./settings";
import { useUsersStore } from "./users";
const DEFAULT_CREDENTIAL_NAME = 'Unnamed credential';
const DEFAULT_CREDENTIAL_POSTFIX = 'account';
const TYPES_WITH_DEFAULT_NAME = ['httpBasicAuth', 'oAuth2Api', 'httpDigestAuth', 'oAuth1Api'];
export const useCredentialsStore = defineStore(STORES.CREDENTIALS, {
state: (): ICredentialsState => ({
credentialTypes: {},
credentials: {},
}),
getters: {
credentialTypesById(): Record<ICredentialType['name'], ICredentialType> {
return this.credentialTypes;
},
allCredentialTypes(): ICredentialType[] {
return Object.values(this.credentialTypes)
.sort((a, b) => a.displayName.localeCompare(b.displayName));
},
allCredentials(): ICredentialsResponse[] {
return Object.values(this.credentials)
.sort((a, b) => a.name.localeCompare(b.name));
},
allForeignCredentials(): ICredentialsResponse[] {
return Object.values(this.foreignCredentials || {})
.sort((a, b) => a.name.localeCompare(b.name));
},
allCredentialsByType(): {[type: string]: ICredentialsResponse[]} {
const credentials = this.allCredentials;
const types = this.allCredentialTypes;
return types.reduce((accu: {[type: string]: ICredentialsResponse[]}, type: ICredentialType) => {
accu[type.name] = credentials.filter((cred: ICredentialsResponse) => cred.type === type.name);
return accu;
}, {});
},
getCredentialTypeByName() {
return (type: string): ICredentialType => this.credentialTypes[type];
},
getCredentialById() {
return (id: string): ICredentialsResponse => this.credentials[id];
},
getCredentialByIdAndType() {
return (id: string, type: string): ICredentialsResponse | undefined => {
const credential = this.credentials[id];
return !credential || credential.type !== type ? undefined : credential;
};
},
getCredentialsByType() {
return (credentialType: string): ICredentialsResponse[] => {
return (this.allCredentialsByType[credentialType] || []);
};
},
getNodesWithAccess() {
return (credentialTypeName: string) => {
const nodeTypesStore = useNodeTypesStore();
const allLatestNodeTypes: INodeTypeDescription[] = nodeTypesStore.allLatestNodeTypes;
return allLatestNodeTypes.filter((nodeType: INodeTypeDescription) => {
if (!nodeType.credentials) {
return false;
}
for (const credentialTypeDescription of nodeType.credentials) {
if (credentialTypeDescription.name === credentialTypeName ) {
return true;
}
}
return false;
});
};
},
getScopesByCredentialType() {
return (credentialTypeName: string) => {
const credentialType = this.getCredentialTypeByName(credentialTypeName) as {
properties: INodeProperties[];
};
const scopeProperty = credentialType.properties.find((p) => p.name === 'scope');
if (
!scopeProperty ||
!scopeProperty.default ||
typeof scopeProperty.default !== 'string' ||
scopeProperty.default === ''
) {
return [];
}
let { default: scopeDefault } = scopeProperty;
// disregard expressions for display
scopeDefault = scopeDefault.replace(/^=/, '').replace(/\{\{.*\}\}/, '');
if (/ /.test(scopeDefault)) return scopeDefault.split(' ');
if (/,/.test(scopeDefault)) return scopeDefault.split(',');
return [scopeDefault];
};
},
getCredentialOwnerName() {
return (credentialId: string): string => {
const credential = this.getCredentialById(credentialId);
return credential && credential.ownedBy && credential.ownedBy.firstName
? `${credential.ownedBy.firstName} ${credential.ownedBy.lastName} (${credential.ownedBy.email})`
: i18n.baseText('credentialEdit.credentialSharing.info.sharee.fallback');
};
},
},
actions: {
setCredentialTypes(credentialTypes: ICredentialType[]): void {
this.credentialTypes = credentialTypes.reduce((accu: ICredentialTypeMap, cred: ICredentialType) => {
accu[cred.name] = cred;
return accu;
}, {});
},
setCredentials(credentials: ICredentialsResponse[]): void {
this.credentials = credentials.reduce((accu: ICredentialMap, cred: ICredentialsResponse) => {
if (cred.id) {
accu[cred.id] = cred;
}
return accu;
}, {});
},
setForeignCredentials(credentials: ICredentialsResponse[]): void {
this.foreignCredentials = credentials.reduce((accu: ICredentialMap, cred: ICredentialsResponse) => {
if (cred.id) {
accu[cred.id] = cred;
}
return accu;
}, {});
},
upsertCredential(credential: ICredentialsResponse): void {
if (credential.id) {
Vue.set(this.credentials, credential.id, { ...this.credentials[credential.id], ...credential });
}
},
enableOAuthCredential(credential: ICredentialsResponse): void {
// enable oauth event to track change between modals
},
async fetchCredentialTypes(forceFetch: boolean): Promise<void> {
if (this.allCredentialTypes.length > 0 && forceFetch !== true) {
return;
}
const rootStore = useRootStore();
const credentialTypes = await getCredentialTypes(rootStore.getRestApiContext);
this.setCredentialTypes(credentialTypes);
},
async fetchAllCredentials(): Promise<ICredentialsResponse[]> {
const rootStore = useRootStore();
const credentials = await getAllCredentials(rootStore.getRestApiContext);
this.setCredentials(credentials);
return credentials;
},
async fetchForeignCredentials(): Promise<ICredentialsResponse[]> {
const rootStore = useRootStore();
const credentials = await getForeignCredentials(rootStore.getRestApiContext);
this.setForeignCredentials(credentials);
return credentials;
},
async getCredentialData({ id }: {id: string}): Promise<ICredentialsResponse | ICredentialsDecryptedResponse | undefined> {
const rootStore = useRootStore();
return await getCredentialData(rootStore.getRestApiContext, id);
},
async createNewCredential(data: ICredentialsDecrypted): Promise<ICredentialsResponse> {
const rootStore = useRootStore();
const settingsStore = useSettingsStore();
const credential = await createNewCredential(rootStore.getRestApiContext, data);
if (settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) {
this.upsertCredential(credential);
if (data.ownedBy) {
this.setCredentialOwnedBy({
credentialId: credential.id,
ownedBy: data.ownedBy,
});
const usersStore = useUsersStore();
if (data.sharedWith && data.ownedBy.id === usersStore.currentUserId) {
this.setCredentialSharedWith({
credentialId: credential.id,
sharedWith: data.sharedWith,
});
}
}
} else {
this.upsertCredential(credential);
}
return credential;
},
async updateCredential(params: {data: ICredentialsDecrypted, id: string}): Promise<ICredentialsResponse> {
const { id, data } = params;
const rootStore = useRootStore();
const settingsStore = useSettingsStore();
const credential = await updateCredential(rootStore.getRestApiContext, id, data);
if (settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) {
this.upsertCredential(credential);
if (data.ownedBy) {
this.setCredentialOwnedBy({
credentialId: credential.id,
ownedBy: data.ownedBy,
});
const usersStore = useUsersStore();
if (data.sharedWith && data.ownedBy.id === usersStore.currentUserId) {
this.setCredentialSharedWith({
credentialId: credential.id,
sharedWith: data.sharedWith,
});
}
}
} else {
this.upsertCredential(credential);
}
return credential;
},
async deleteCredential({ id }: {id: string}): void {
const rootStore = useRootStore();
const deleted = await deleteCredential(rootStore.getRestApiContext, id);
if (deleted) {
Vue.delete(this.credentials, id);
}
},
async oAuth2Authorize(data: ICredentialsResponse): Promise<string> {
const rootStore = useRootStore();
return oAuth2CredentialAuthorize(rootStore.getRestApiContext, data);
},
async oAuth1Authorize(data: ICredentialsResponse): Promise<string> {
const rootStore = useRootStore();
return oAuth1CredentialAuthorize(rootStore.getRestApiContext, data);
},
async testCredential(data: ICredentialsDecrypted): Promise<INodeCredentialTestResult> {
const rootStore = useRootStore();
return testCredential(rootStore.getRestApiContext, { credentials: data });
},
async getNewCredentialName(params: { credentialTypeName: string }): Promise<string> {
try {
const { credentialTypeName } = params;
let newName = DEFAULT_CREDENTIAL_NAME;
if (!TYPES_WITH_DEFAULT_NAME.includes(credentialTypeName)) {
const { displayName } = this.getCredentialTypeByName(credentialTypeName);
newName = getAppNameFromCredType(displayName);
newName = newName.length > 0 ? `${newName} ${DEFAULT_CREDENTIAL_POSTFIX}` : DEFAULT_CREDENTIAL_NAME;
}
const rootStore = useRootStore();
const res = await getCredentialsNewName(rootStore.getRestApiContext, newName);
return res.name;
} catch (e) {
return DEFAULT_CREDENTIAL_NAME;
}
},
// Enterprise edition actions
setCredentialOwnedBy(payload: { credentialId: string, ownedBy: Partial<IUser> }): void {
Vue.set(this.credentials[payload.credentialId], 'ownedBy', payload.ownedBy);
},
async setCredentialSharedWith(payload: { sharedWith: IUser[]; credentialId: string; }): void {
if(useSettingsStore().isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) {
await setCredentialSharedWith(
useRootStore().getRestApiContext,
payload.credentialId,
{
shareWithIds: payload.sharedWith.map((sharee) => sharee.id),
},
);
Vue.set(this.credentials[payload.credentialId], 'sharedWith', payload.sharedWith);
}
},
addCredentialSharee(payload: { credentialId: string, sharee: Partial<IUser> }): void {
Vue.set(
this.credentials[payload.credentialId],
'sharedWith',
(this.credentials[payload.credentialId].sharedWith || []).concat([payload.sharee]),
);
},
removeCredentialSharee(payload: { credentialId: string, sharee: Partial<IUser> }): void {
Vue.set(
this.credentials[payload.credentialId],
'sharedWith',
(this.credentials[payload.credentialId].sharedWith || [])
.filter((sharee) => sharee.id !== payload.sharee.id),
);
},
},
});

View file

@ -0,0 +1,12 @@
import { ALL_NODE_FILTER, STORES } from "@/constants";
import { INodeCreatorState } from "@/Interface";
import { defineStore } from "pinia";
export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, {
state: (): INodeCreatorState => ({
itemsFilter: '',
showTabs: true,
showScrim: false,
selectedType: ALL_NODE_FILTER,
}),
});

View file

@ -1,13 +1,13 @@
import { getNodeParameterOptions, getNodesInformation, getNodeTranslationHeaders, getNodeTypes, getResourceLocatorResults } from "@/api/nodeTypes"; import { getNodeParameterOptions, getNodesInformation, getNodeTranslationHeaders, getNodeTypes, getResourceLocatorResults } from "@/api/nodeTypes";
import { DEFAULT_NODETYPE_VERSION, STORES } from "@/constants"; import { DEFAULT_NODETYPE_VERSION, STORES } from "@/constants";
import { ICategoriesWithNodes, INodeCreateElement, INodeTypesState, IResourceLocatorReqParams } from "@/Interface"; import { ICategoriesWithNodes, INodeCreateElement, INodeTypesState, IResourceLocatorReqParams } from "@/Interface";
import { getCategoriesWithNodes, getCategorizedList } from "@/modules/nodeTypesHelpers"; import { getCategoriesWithNodes, getCategorizedList } from "@/stores/nodeTypesHelpers";
import { addHeaders, addNodeTranslation } from "@/plugins/i18n"; import { addHeaders, addNodeTranslation } from "@/plugins/i18n";
import { store } from "@/store";
import { omit } from "@/utils"; import { omit } from "@/utils";
import { ILoadOptions, INodeCredentials, INodeListSearchResult, INodeParameters, INodePropertyOptions, INodeTypeDescription, INodeTypeNameVersion } from 'n8n-workflow'; import { ILoadOptions, INodeCredentials, INodeListSearchResult, INodeParameters, INodePropertyOptions, INodeTypeDescription, INodeTypeNameVersion } from 'n8n-workflow';
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import Vue from "vue"; import Vue from "vue";
import { useCredentialsStore } from "./credentials";
import { useRootStore } from "./n8nRootStore"; import { useRootStore } from "./n8nRootStore";
import { useUsersStore } from "./users"; import { useUsersStore } from "./users";
@ -115,8 +115,8 @@ export const useNodeTypesStore = defineStore(STORES.NODE_TYPES, {
this.setNodeTypes(nodesInformation); this.setNodeTypes(nodesInformation);
}, },
async getFullNodesProperties(nodesToBeFetched: INodeTypeNameVersion[]): Promise<void> { async getFullNodesProperties(nodesToBeFetched: INodeTypeNameVersion[]): Promise<void> {
const vuexStore = store; const credentialsStore = useCredentialsStore();
vuexStore.dispatch('credentials/fetchCredentialTypes', true); credentialsStore.fetchCredentialTypes(true);
await this.getNodesInformation(nodesToBeFetched); await this.getNodesInformation(nodesToBeFetched);
}, },
async getNodeTypes(): Promise<void> { async getNodeTypes(): Promise<void> {

View file

@ -10,6 +10,7 @@ import Vue from "vue";
import { useRootStore } from "./n8nRootStore"; import { useRootStore } from "./n8nRootStore";
import { useUIStore } from "./ui"; import { useUIStore } from "./ui";
import { useUsersStore } from "./users"; import { useUsersStore } from "./users";
import { useVersionsStore } from "./versions";
export const useSettingsStore = defineStore(STORES.SETTINGS, { export const useSettingsStore = defineStore(STORES.SETTINGS, {
state: (): ISettingsState => ({ state: (): ISettingsState => ({
@ -129,7 +130,6 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, {
async getSettings(): Promise<void> { async getSettings(): Promise<void> {
const rootStore = useRootStore(); const rootStore = useRootStore();
const settings = await getSettings(rootStore.getRestApiContext); const settings = await getSettings(rootStore.getRestApiContext);
const vuexStore = store;
this.setSettings(settings); this.setSettings(settings);
this.settings.communityNodesEnabled = settings.communityNodesEnabled; this.settings.communityNodesEnabled = settings.communityNodesEnabled;
@ -151,7 +151,7 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, {
rootStore.setN8nMetadata(settings.n8nMetadata || {}); rootStore.setN8nMetadata(settings.n8nMetadata || {});
rootStore.setDefaultLocale(settings.defaultLocale); rootStore.setDefaultLocale(settings.defaultLocale);
rootStore.setIsNpmAvailable(settings.isNpmAvailable); rootStore.setIsNpmAvailable(settings.isNpmAvailable);
vuexStore.commit('versions/setVersionNotificationSettings', settings.versionNotifications, {root: true}); useVersionsStore().setVersionNotificationSettings(settings.versionNotifications);
}, },
stopShowingSetupPage(): void { stopShowingSetupPage(): void {
Vue.set(this.userManagement, 'showSetupOnFirstLoad', false); Vue.set(this.userManagement, 'showSetupOnFirstLoad', false);

View file

@ -0,0 +1,102 @@
import { createTag, deleteTag, getTags, updateTag } from "@/api/tags";
import { STORES } from "@/constants";
import { ITag, ITagsState } from "@/Interface";
import { defineStore } from "pinia";
import Vue from "vue";
import { useRootStore } from "./n8nRootStore";
import { useWorkflowsStore } from "./workflows";
export const useTagsStore = defineStore(STORES.TAGS, {
state: (): ITagsState => ({
tags: {},
loading: false,
fetchedAll: false,
fetchedUsageCount: false,
}),
getters: {
allTags(): ITag[] {
return Object.values(this.tags)
.sort((a, b) => a.name.localeCompare(b.name));
},
isLoading(): boolean {
return this.loading;
},
hasTags(): boolean {
return Object.keys(this.tags).length > 0;
},
getTagById() {
return (id: string) => this.tags[id];
},
},
actions: {
setAllTags(tags: ITag[]): void {
this.tags = tags
.reduce((accu: { [id: string]: ITag }, tag: ITag) => {
accu[tag.id] = tag;
return accu;
}, {});
this.fetchedAll = true;
},
upsertTags(tags: ITag[]): void {
tags.forEach((tag) => {
const tagId = tag.id;
const currentTag = this.tags[tagId];
if (currentTag) {
const newTag = {
...currentTag,
...tag,
};
Vue.set(this.tags, tagId, newTag);
}
else {
Vue.set(this.tags, tagId, tag);
}
});
},
deleteTag(id: string): void {
Vue.delete(this.tags, id);
},
async fetchAll(params?: { force?: boolean, withUsageCount?: boolean }): Promise<ITag[]> {
const { force = false, withUsageCount = false } = params || {};
if (!force && this.fetchedAll && this.fetchedUsageCount === withUsageCount) {
return Object.values(this.tags);
}
this.loading = true;
const rootStore = useRootStore();
const tags = await getTags(rootStore.getRestApiContext, Boolean(withUsageCount));
this.setAllTags(tags);
this.loading = false;
return tags;
},
async create(name: string): Promise<ITag> {
const rootStore = useRootStore();
const tag = await createTag(rootStore.getRestApiContext, { name });
this.upsertTags([tag]);
return tag;
},
async rename({ id, name }: { id: string, name: string }) {
const rootStore = useRootStore();
const tag = await updateTag(rootStore.getRestApiContext, id, { name });
this.upsertTags([tag]);
return tag;
},
async delete(id: string) {
const rootStore = useRootStore();
const deleted = await deleteTag(rootStore.getRestApiContext, id);
if (deleted) {
this.deleteTag(id);
const workflowsStore = useWorkflowsStore();
workflowsStore.removeWorkflowTagId(id);
}
return deleted;
},
},
});

View file

@ -1,7 +1,7 @@
import { changePassword, deleteUser, getCurrentUser, getUsers, inviteUsers, login, loginCurrentUser, logout, reinvite, sendForgotPasswordEmail, setupOwner, signup, skipOwnerSetup, submitPersonalizationSurvey, updateCurrentUser, updateCurrentUserPassword, validatePasswordToken, validateSignupToken } from "@/api/users"; import { changePassword, deleteUser, getCurrentUser, getUsers, inviteUsers, login, loginCurrentUser, logout, reinvite, sendForgotPasswordEmail, setupOwner, signup, skipOwnerSetup, submitPersonalizationSurvey, updateCurrentUser, updateCurrentUserPassword, validatePasswordToken, validateSignupToken } from "@/api/users";
import { PERSONALIZATION_MODAL_KEY, STORES } from "@/constants"; import { PERSONALIZATION_MODAL_KEY, STORES } from "@/constants";
import { IInviteResponse, IPersonalizationLatestVersion, IUser, IUserResponse, IUsersState } from "@/Interface"; import { IInviteResponse, IPersonalizationLatestVersion, IUser, IUserResponse, IUsersState } from "@/Interface";
import { getPersonalizedNodeTypes, isAuthorized, PERMISSIONS, ROLE } from "@/modules/userHelpers"; import { getPersonalizedNodeTypes, isAuthorized, PERMISSIONS, ROLE } from "@/stores/userHelpers";
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import Vue from "vue"; import Vue from "vue";
import { useRootStore } from "./n8nRootStore"; import { useRootStore } from "./n8nRootStore";

View file

@ -0,0 +1,50 @@
import { getNextVersions } from "@/api/versions";
import { STORES } from "@/constants";
import { IVersion, IVersionNotificationSettings, IVersionsState } from "@/Interface";
import { defineStore } from "pinia";
import { useRootStore } from "./n8nRootStore";
export const useVersionsStore = defineStore(STORES.VERSIONS, {
state: (): IVersionsState => ({
versionNotificationSettings: {
enabled: false,
endpoint: '',
infoUrl: '',
},
nextVersions: [],
currentVersion: undefined,
}),
getters: {
hasVersionUpdates(): boolean {
return this.nextVersions.length > 0;
},
areNotificationsEnabled(): boolean {
return this.versionNotificationSettings.enabled;
},
infoUrl(): string {
return this.versionNotificationSettings.infoUrl;
},
},
actions: {
setVersions({versions, currentVersion}: {versions: IVersion[], currentVersion: string}) {
this.nextVersions = versions.filter((version) => version.name !== currentVersion);
this.currentVersion = versions.find((version) => version.name === currentVersion);
},
setVersionNotificationSettings(settings: IVersionNotificationSettings) {
this.versionNotificationSettings = settings;
},
async fetchVersions() {
try {
const { enabled, endpoint } = this.versionNotificationSettings;
if (enabled && endpoint) {
const rootStore = useRootStore();
const currentVersion = rootStore.versionCli;
const instanceId = rootStore.instanceId;
const versions = await getNextVersions(endpoint, currentVersion, instanceId);
this.setVersions({ versions, currentVersion });
}
} catch (e) {
}
},
},
});

View file

@ -0,0 +1,65 @@
import { STORES } from "@/constants";
import { IFakeDoor, INodeUi, IRootState } from "@/Interface";
import { IMenuItem } from "n8n-design-system";
import { IWorkflowSettings } from "n8n-workflow";
import { defineStore } from "pinia";
import { useRootStore } from "./n8nRootStore";
import { useNDVStore } from "./ndv";
import { useSettingsStore } from "./settings";
import { useUIStore } from "./ui";
import { useUsersStore } from "./users";
import { useWorkflowsStore } from "./workflows";
export const useWebhooksStore = defineStore(STORES.WEBHOOKS, {
getters: {
globalRoleName(): string {
return useUsersStore().globalRoleName;
},
getFakeDoorFeatures () {
return useUIStore().fakeDoorFeatures;
},
isUserManagementEnabled () {
return useSettingsStore().isUserManagementEnabled;
},
getFakeDoorItems(): IFakeDoor[] {
return useUIStore().fakeDoorFeatures;
},
n8nMetadata(): IRootState['n8nMetadata'] {
return useRootStore().n8nMetadata;
},
instanceId(): string {
return useRootStore().instanceId;
},
workflowId(): string {
return useWorkflowsStore().workflowId;
},
workflowName(): string {
return useWorkflowsStore().workflowName;
},
activeNode(): INodeUi | null {
return useNDVStore().activeNode;
},
workflowSettings(): IWorkflowSettings {
return useWorkflowsStore().workflowSettings;
},
activeExecutionId(): string {
return useWorkflowsStore().activeExecutionId || '';
},
nodeByName: (state: IRootState) => (nodeName: string): INodeUi | null => {
return useWorkflowsStore().getNodeByName(nodeName);
},
allNodes(): INodeUi[] {
return useWorkflowsStore().allNodes;
},
},
actions: {
addSidebarMenuItems(menuItems: IMenuItem[]) {
const uiStore = useUIStore();
const updated = uiStore.sidebarMenuItems.concat(menuItems);
uiStore.sidebarMenuItems = updated;
},
setFakeDoorFeatures(fakeDoors: IFakeDoor[]): void {
useUIStore().fakeDoorFeatures = fakeDoors;
},
},
});

View file

@ -649,7 +649,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
return node.name === updateInformation.name; return node.name === updateInformation.name;
}); });
if (node === undefined || node === null) { if (node === undefined || node === null || !updateInformation.key) {
throw new Error(`Node with the name "${updateInformation.name}" could not be found to set parameter.`); throw new Error(`Node with the name "${updateInformation.name}" could not be found to set parameter.`);
} }

View file

@ -1,4 +1,5 @@
import { INodeParameterResourceLocator } from "n8n-workflow"; import { INodeParameterResourceLocator } from "n8n-workflow";
import { ICredentialsResponse } from "./Interface";
export function isResourceLocatorValue(value: unknown): value is INodeParameterResourceLocator { export function isResourceLocatorValue(value: unknown): value is INodeParameterResourceLocator {
return Boolean(typeof value === 'object' && value && 'mode' in value && 'value' in value); return Boolean(typeof value === 'object' && value && 'mode' in value && 'value' in value);
@ -7,3 +8,7 @@ export function isResourceLocatorValue(value: unknown): value is INodeParameterR
export function isNotNull<T>(value: T | null): value is T { export function isNotNull<T>(value: T | null): value is T {
return value !== null; return value !== null;
} }
export function isValidCredentialResponse(value: unknown): value is ICredentialsResponse {
return typeof value === 'object' && value !== null && 'id' in value && 'createdAt' in value && 'updatedAt' in value;
}

View file

@ -15,6 +15,8 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import { XYPosition } from '@/Interface'; import { XYPosition } from '@/Interface';
import { mapStores } from 'pinia';
import { useNodeCreatorStore } from '@/stores/nodeCreator';
export default Vue.extend({ export default Vue.extend({
name: 'CanvasAddButton', name: 'CanvasAddButton',
@ -27,6 +29,9 @@ export default Vue.extend({
}, },
}, },
computed: { computed: {
...mapStores(
useNodeCreatorStore,
),
containerCssVars(): Record<string, string> { containerCssVars(): Record<string, string> {
const position = this.position as XYPosition; const position = this.position as XYPosition;
return { return {
@ -35,7 +40,7 @@ export default Vue.extend({
}; };
}, },
isScrimActive(): boolean { isScrimActive(): boolean {
return this.$store.getters['nodeCreator/showScrim']; return this.nodeCreatorStore.showScrim;
}, },
}, },
}); });

View file

@ -44,7 +44,7 @@
<script lang="ts"> <script lang="ts">
import {showMessage} from '@/components/mixins/showMessage'; import {showMessage} from '@/components/mixins/showMessage';
import {ICredentialsResponse, IUser} from '@/Interface'; import {ICredentialsResponse, ICredentialTypeMap, IUser} from '@/Interface';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import SettingsView from './SettingsView.vue'; import SettingsView from './SettingsView.vue';
@ -63,6 +63,7 @@ import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui'; import { useUIStore } from '@/stores/ui';
import { useUsersStore } from '@/stores/users'; import { useUsersStore } from '@/stores/users';
import { useNodeTypesStore } from '@/stores/nodeTypes'; import { useNodeTypesStore } from '@/stores/nodeTypes';
import { useCredentialsStore } from '@/stores/credentials';
type IResourcesListLayoutInstance = Vue & { sendFiltersTelemetry: (source: string) => void }; type IResourcesListLayoutInstance = Vue & { sendFiltersTelemetry: (source: string) => void };
@ -93,18 +94,19 @@ export default mixins(
}, },
computed: { computed: {
...mapStores( ...mapStores(
useCredentialsStore,
useNodeTypesStore, useNodeTypesStore,
useUIStore, useUIStore,
useUsersStore, useUsersStore,
), ),
allCredentials(): ICredentialsResponse[] { allCredentials(): ICredentialsResponse[] {
return this.$store.getters['credentials/allCredentials']; return this.credentialsStore.allCredentials;
}, },
allCredentialTypes(): ICredentialType[] { allCredentialTypes(): ICredentialType[] {
return this.$store.getters['credentials/allCredentialTypes']; return this.credentialsStore.allCredentialTypes;
}, },
credentialTypesById(): Record<ICredentialType['name'], ICredentialType> { credentialTypesById(): ICredentialTypeMap {
return this.$store.getters['credentials/credentialTypesById']; return this.credentialsStore.credentialTypesById;
}, },
}, },
methods: { methods: {
@ -116,9 +118,10 @@ export default mixins(
}); });
}, },
async initialize() { async initialize() {
const loadPromises = [ const loadPromises = [
this.$store.dispatch('credentials/fetchAllCredentials'), this.credentialsStore.fetchAllCredentials(),
this.$store.dispatch('credentials/fetchCredentialTypes'), this.credentialsStore.fetchCredentialTypes(false),
]; ];
if (this.nodeTypesStore.allNodeTypes.length === 0) { if (this.nodeTypesStore.allNodeTypes.length === 0) {

View file

@ -12,7 +12,6 @@ import { showMessage } from '@/components/mixins/showMessage';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { IFormBoxConfig } from '@/Interface'; import { IFormBoxConfig } from '@/Interface';
import { mapGetters } from 'vuex';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
import { useSettingsStore } from '@/stores/settings'; import { useSettingsStore } from '@/stores/settings';
import { useUsersStore } from '@/stores/users'; import { useUsersStore } from '@/stores/users';

View file

@ -128,9 +128,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
import { import type { OnConnectionBindInfo, Connection, Endpoint, N8nPlusEndpoint, jsPlumbInstance } from 'jsplumb';
OnConnectionBindInfo, Connection, Endpoint, N8nPlusEndpoint, jsPlumbInstance,
} from 'jsplumb';
import type { MessageBoxInputData } from 'element-ui/types/message-box'; import type { MessageBoxInputData } from 'element-ui/types/message-box';
import once from 'lodash/once'; import once from 'lodash/once';
@ -213,8 +211,8 @@ import {
IUser, IUser,
INodeUpdatePropertiesInformation, INodeUpdatePropertiesInformation,
} from '@/Interface'; } from '@/Interface';
import { getAccountAge } from '@/modules/userHelpers';
import { dataPinningEventBus } from "@/event-bus/data-pinning-event-bus"; import { getAccountAge } from '@/stores/userHelpers';
import { debounceHelper } from '@/components/mixins/debounce'; import { debounceHelper } from '@/components/mixins/debounce';
import { useUIStore } from '@/stores/ui'; import { useUIStore } from '@/stores/ui';
import { useSettingsStore } from '@/stores/settings'; import { useSettingsStore } from '@/stores/settings';
@ -226,6 +224,10 @@ import { useRootStore } from '@/stores/n8nRootStore';
import { useNDVStore } from '@/stores/ndv'; import { useNDVStore } from '@/stores/ndv';
import { useTemplatesStore } from '@/stores/templates'; import { useTemplatesStore } from '@/stores/templates';
import { useNodeTypesStore } from '@/stores/nodeTypes'; import { useNodeTypesStore } from '@/stores/nodeTypes';
import { useCredentialsStore } from '@/stores/credentials';
import { useTagsStore } from '@/stores/tags';
import { useNodeCreatorStore } from '@/stores/nodeCreator';
import { dataPinningEventBus } from '@/event-bus/data-pinning-event-bus';
import { useCanvasStore } from '@/stores/canvas'; import { useCanvasStore } from '@/stores/canvas';
interface AddNodeOptions { interface AddNodeOptions {
@ -379,6 +381,10 @@ export default mixins(
}, },
computed: { computed: {
...mapStores( ...mapStores(
useCanvasStore,
useTagsStore,
useCredentialsStore,
useNodeCreatorStore,
useNodeTypesStore, useNodeTypesStore,
useNDVStore, useNDVStore,
useRootStore, useRootStore,
@ -497,7 +503,6 @@ export default mixins(
getNodeViewOffsetPosition(): XYPosition { getNodeViewOffsetPosition(): XYPosition {
return this.uiStore.nodeViewOffsetPosition; return this.uiStore.nodeViewOffsetPosition;
}, },
...mapStores(useCanvasStore),
nodeViewScale(): number { nodeViewScale(): number {
return this.canvasStore.nodeViewScale; return this.canvasStore.nodeViewScale;
}, },
@ -662,10 +667,10 @@ export default mixins(
}, },
showTriggerCreator(source: string) { showTriggerCreator(source: string) {
if(this.createNodeActive) return; if(this.createNodeActive) return;
this.$store.commit('nodeCreator/setSelectedType', TRIGGER_NODE_FILTER); this.nodeCreatorStore.selectedType = TRIGGER_NODE_FILTER;
this.$store.commit('nodeCreator/setShowScrim', true); this.nodeCreatorStore.showScrim = true;
this.onToggleNodeCreator({ source, createNodeActive: true }); this.onToggleNodeCreator({ source, createNodeActive: true });
this.$nextTick(() => this.$store.commit('nodeCreator/setShowTabs', false)); this.nodeCreatorStore.showTabs = false;
}, },
async openExecution(executionId: string) { async openExecution(executionId: string) {
this.startLoading(); this.startLoading();
@ -832,7 +837,7 @@ export default mixins(
const tags = (data.tags || []) as ITag[]; const tags = (data.tags || []) as ITag[];
const tagIds = tags.map((tag) => tag.id); const tagIds = tags.map((tag) => tag.id);
this.workflowsStore.setWorkflowTagIds(tagIds || []); this.workflowsStore.setWorkflowTagIds(tagIds || []);
this.$store.commit('tags/upsertTags', tags); this.tagsStore.upsertTags(tags);
await this.addNodes(data.nodes, data.connections); await this.addNodes(data.nodes, data.connections);
@ -1149,6 +1154,7 @@ export default mixins(
const childNodes = workflow.getChildNodes(sourceNodeName); const childNodes = workflow.getChildNodes(sourceNodeName);
for (const nodeName of childNodes) { for (const nodeName of childNodes) {
const node = this.workflowsStore.nodesByName[nodeName] as INodeUi; const node = this.workflowsStore.nodesByName[nodeName] as INodeUi;
if (node.position[0] < sourceNode.position[0]) { if (node.position[0] < sourceNode.position[0]) {
continue; continue;
} }
@ -1156,7 +1162,7 @@ export default mixins(
const updateInformation: INodeUpdatePropertiesInformation = { const updateInformation: INodeUpdatePropertiesInformation = {
name: nodeName, name: nodeName,
properties: { properties: {
position: { position: [node.position[0] + margin, node.position[1]] }, position: [node.position[0] + margin, node.position[1]],
}, },
}; };
@ -1413,7 +1419,7 @@ export default mixins(
const tagsEnabled = this.settingsStore.areTagsEnabled; const tagsEnabled = this.settingsStore.areTagsEnabled;
if (importTags && tagsEnabled && Array.isArray(workflowData.tags)) { if (importTags && tagsEnabled && Array.isArray(workflowData.tags)) {
const allTags: ITag[] = await this.$store.dispatch('tags/fetchAll'); const allTags = await this.tagsStore.fetchAll();
const tagNames = new Set(allTags.map((tag) => tag.name)); const tagNames = new Set(allTags.map((tag) => tag.name));
const workflowTags = workflowData.tags as ITag[]; const workflowTags = workflowData.tags as ITag[];
@ -1421,7 +1427,7 @@ export default mixins(
const creatingTagPromises: Array<Promise<ITag>> = []; const creatingTagPromises: Array<Promise<ITag>> = [];
for (const tag of notFound) { for (const tag of notFound) {
const creationPromise = this.$store.dispatch('tags/create', tag.name) const creationPromise = this.tagsStore.create(tag.name)
.then((tag: ITag) => { .then((tag: ITag) => {
allTags.push(tag); allTags.push(tag);
return tag; return tag;
@ -1528,13 +1534,13 @@ export default mixins(
}; };
const credentialPerType = nodeTypeData.credentials && nodeTypeData.credentials const credentialPerType = nodeTypeData.credentials && nodeTypeData.credentials
.map(type => this.$store.getters['credentials/getCredentialsByType'](type.name)) .map(type => this.credentialsStore.getCredentialsByType(type.name))
.flat(); .flat();
if (credentialPerType && credentialPerType.length === 1) { if (credentialPerType && credentialPerType.length === 1) {
const defaultCredential = credentialPerType[0]; const defaultCredential = credentialPerType[0];
const selectedCredentials = this.$store.getters['credentials/getCredentialById'](defaultCredential.id); const selectedCredentials = this.credentialsStore.getCredentialById(defaultCredential.id);
const selected = { id: selectedCredentials.id, name: selectedCredentials.name }; const selected = { id: selectedCredentials.id, name: selectedCredentials.name };
const credentials = { const credentials = {
[defaultCredential.type]: selected, [defaultCredential.type]: selected,
@ -2712,7 +2718,7 @@ export default mixins(
return; return;
} }
Object.entries(node.credentials).forEach(([nodeCredentialType, nodeCredentials]: [string, INodeCredentialsDetails]) => { Object.entries(node.credentials).forEach(([nodeCredentialType, nodeCredentials]: [string, INodeCredentialsDetails]) => {
const credentialOptions = this.$store.getters['credentials/getCredentialsByType'](nodeCredentialType) as ICredentialsResponse[]; const credentialOptions = this.credentialsStore.getCredentialsByType(nodeCredentialType);
// Check if workflows applies old credentials style // Check if workflows applies old credentials style
if (typeof nodeCredentials === 'string') { if (typeof nodeCredentials === 'string') {
@ -3081,11 +3087,11 @@ export default mixins(
await this.nodeTypesStore.getNodeTypes(); await this.nodeTypesStore.getNodeTypes();
}, },
async loadCredentialTypes(): Promise<void> { async loadCredentialTypes(): Promise<void> {
await this.$store.dispatch('credentials/fetchCredentialTypes', true); await this.credentialsStore.fetchCredentialTypes(true);
}, },
async loadCredentials(): Promise<void> { async loadCredentials(): Promise<void> {
await this.$store.dispatch('credentials/fetchAllCredentials'); await this.credentialsStore.fetchAllCredentials();
await this.$store.dispatch('credentials/fetchForeignCredentials'); await this.credentialsStore.fetchForeignCredentials();
}, },
async loadNodesProperties(nodeInfos: INodeTypeNameVersion[]): Promise<void> { async loadNodesProperties(nodeInfos: INodeTypeNameVersion[]): Promise<void> {
const allNodes: INodeTypeDescription[] = this.nodeTypesStore.allNodeTypes; const allNodes: INodeTypeDescription[] = this.nodeTypesStore.allNodeTypes;
@ -3193,7 +3199,9 @@ export default mixins(
if (createNodeActive === this.createNodeActive) return; if (createNodeActive === this.createNodeActive) return;
// Default to the trigger tab in node creator if there's no trigger node yet // Default to the trigger tab in node creator if there's no trigger node yet
if (!this.containsTrigger) this.$store.commit('nodeCreator/setSelectedType', TRIGGER_NODE_FILTER); if (!this.containsTrigger) {
this.nodeCreatorStore.selectedType = TRIGGER_NODE_FILTER;
}
this.createNodeActive = createNodeActive; this.createNodeActive = createNodeActive;
this.$externalHooks().run('nodeView.createNodeActiveChanged', { source, createNodeActive }); this.$externalHooks().run('nodeView.createNodeActiveChanged', { source, createNodeActive });

View file

@ -20,6 +20,7 @@ import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui'; import { useUIStore } from '@/stores/ui';
import { useSettingsStore } from '@/stores/settings'; import { useSettingsStore } from '@/stores/settings';
import { useUsersStore } from '@/stores/users'; import { useUsersStore } from '@/stores/users';
import { useCredentialsStore } from '@/stores/credentials';
export default mixins( export default mixins(
@ -103,6 +104,7 @@ export default mixins(
}, },
computed: { computed: {
...mapStores( ...mapStores(
useCredentialsStore,
useSettingsStore, useSettingsStore,
useUIStore, useUIStore,
useUsersStore, useUsersStore,
@ -110,7 +112,7 @@ export default mixins(
}, },
methods: { methods: {
async getAllCredentials() { async getAllCredentials() {
const credentials = await this.$store.dispatch('credentials/fetchAllCredentials'); const credentials = await this.credentialsStore.fetchAllCredentials();
this.credentialsCount = credentials.length; this.credentialsCount = credentials.length;
}, },
async getAllWorkflows() { async getAllWorkflows() {

View file

@ -7,7 +7,7 @@ import {defineConfig, mergeConfig, PluginOption} from "vite";
import { defineConfig as defineVitestConfig } from 'vitest/config'; import { defineConfig as defineVitestConfig } from 'vitest/config';
import packageJSON from './package.json'; import packageJSON from './package.json';
const vendorChunks = ['vue', 'vue-router', 'vuex']; const vendorChunks = ['vue', 'vue-router'];
const ignoreChunks = ['vue2-boring-avatars', 'vue-template-compiler', 'jquery', '@fontsource/open-sans']; const ignoreChunks = ['vue2-boring-avatars', 'vue-template-compiler', 'jquery', '@fontsource/open-sans'];
const isScopedPackageToIgnore = (str: string) => /@codemirror\//.test(str); const isScopedPackageToIgnore = (str: string) => /@codemirror\//.test(str);