mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-27 13:39:44 -08:00
fix(editor): Fix workflow back button navigation (#4546)
* 🐛 Fix back button navigation from recetly saved workflow * 🐛 Fix coming-soon routes
This commit is contained in:
parent
740df0c1e5
commit
825637f02a
|
@ -34,7 +34,6 @@ import { HIRING_BANNER, LOCAL_STORAGE_THEME, VIEWS } from './constants';
|
||||||
|
|
||||||
import mixins from 'vue-typed-mixins';
|
import mixins from 'vue-typed-mixins';
|
||||||
import { showMessage } from './components/mixins/showMessage';
|
import { showMessage } from './components/mixins/showMessage';
|
||||||
import { IUser } from './Interface';
|
|
||||||
import { userHelpers } from './components/mixins/userHelpers';
|
import { userHelpers } from './components/mixins/userHelpers';
|
||||||
import { loadLanguage } from './plugins/i18n';
|
import { loadLanguage } from './plugins/i18n';
|
||||||
import { restApi } from '@/components/mixins/restApi';
|
import { restApi } from '@/components/mixins/restApi';
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<div :class="$style.container">
|
<div :class="$style.container">
|
||||||
<n8n-menu :items="sidebarMenuItems" @select="handleSelect">
|
<n8n-menu :items="sidebarMenuItems" @select="handleSelect">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div :class="$style.returnButton" @click="onReturn">
|
<div :class="$style.returnButton" @click="$emit('return')">
|
||||||
<i class="mr-xs">
|
<i class="mr-xs">
|
||||||
<font-awesome-icon icon="arrow-left" />
|
<font-awesome-icon icon="arrow-left" />
|
||||||
</i>
|
</i>
|
||||||
|
@ -22,7 +22,6 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import mixins from 'vue-typed-mixins';
|
import mixins from 'vue-typed-mixins';
|
||||||
import { mapGetters } from 'vuex';
|
|
||||||
import { ABOUT_MODAL_KEY, VERSIONS_MODAL_KEY, VIEWS } from '@/constants';
|
import { ABOUT_MODAL_KEY, VERSIONS_MODAL_KEY, VIEWS } from '@/constants';
|
||||||
import { userHelpers } from './mixins/userHelpers';
|
import { userHelpers } from './mixins/userHelpers';
|
||||||
import { pushConnection } from "@/components/mixins/pushConnection";
|
import { pushConnection } from "@/components/mixins/pushConnection";
|
||||||
|
@ -123,9 +122,6 @@ export default mixins(
|
||||||
onVersionClick() {
|
onVersionClick() {
|
||||||
this.uiStore.openModal(ABOUT_MODAL_KEY);
|
this.uiStore.openModal(ABOUT_MODAL_KEY);
|
||||||
},
|
},
|
||||||
onReturn() {
|
|
||||||
this.$router.push({name: VIEWS.HOMEPAGE});
|
|
||||||
},
|
|
||||||
openUpdatesPanel() {
|
openUpdatesPanel() {
|
||||||
this.uiStore.openModal(VERSIONS_MODAL_KEY);
|
this.uiStore.openModal(VERSIONS_MODAL_KEY);
|
||||||
},
|
},
|
||||||
|
|
|
@ -831,7 +831,7 @@ export const workflowHelpers = mixins(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (redirect) {
|
if (redirect) {
|
||||||
this.$router.push({
|
this.$router.replace({
|
||||||
name: VIEWS.WORKFLOW,
|
name: VIEWS.WORKFLOW,
|
||||||
params: { name: workflowData.id as string, action: 'workflowSave' },
|
params: { name: workflowData.id as string, action: 'workflowSave' },
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,6 +9,7 @@ import NodeView from '@/views/NodeView.vue';
|
||||||
import ExecutionsView from '@/components/ExecutionsView/ExecutionsView.vue';
|
import ExecutionsView from '@/components/ExecutionsView/ExecutionsView.vue';
|
||||||
import ExecutionsLandingPage from '@/components/ExecutionsView/ExecutionsLandingPage.vue';
|
import ExecutionsLandingPage from '@/components/ExecutionsView/ExecutionsLandingPage.vue';
|
||||||
import ExecutionPreview from '@/components/ExecutionsView/ExecutionPreview.vue';
|
import ExecutionPreview from '@/components/ExecutionsView/ExecutionPreview.vue';
|
||||||
|
import SettingsView from './views/SettingsView.vue';
|
||||||
import SettingsPersonalView from './views/SettingsPersonalView.vue';
|
import SettingsPersonalView from './views/SettingsPersonalView.vue';
|
||||||
import SettingsUsersView from './views/SettingsUsersView.vue';
|
import SettingsUsersView from './views/SettingsUsersView.vue';
|
||||||
import SettingsCommunityNodesView from './views/SettingsCommunityNodesView.vue';
|
import SettingsCommunityNodesView from './views/SettingsCommunityNodesView.vue';
|
||||||
|
@ -424,132 +425,136 @@ const router = new Router({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
redirect: '/settings/personal',
|
component: SettingsView,
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/settings/users',
|
|
||||||
name: VIEWS.USERS_SETTINGS,
|
|
||||||
components: {
|
|
||||||
default: SettingsUsersView,
|
|
||||||
},
|
|
||||||
meta: {
|
|
||||||
telemetry: {
|
|
||||||
pageCategory: 'settings',
|
|
||||||
getProperties(route: Route) {
|
|
||||||
return {
|
|
||||||
feature: 'users',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
permissions: {
|
|
||||||
allow: {
|
|
||||||
role: [ROLE.Default, ROLE.Owner],
|
|
||||||
},
|
|
||||||
deny: {
|
|
||||||
shouldDeny: () => {
|
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
return settingsStore.isUserManagementEnabled === false;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/settings/personal',
|
|
||||||
name: VIEWS.PERSONAL_SETTINGS,
|
|
||||||
components: {
|
|
||||||
default: SettingsPersonalView,
|
|
||||||
},
|
|
||||||
meta: {
|
|
||||||
telemetry: {
|
|
||||||
pageCategory: 'settings',
|
|
||||||
getProperties(route: Route) {
|
|
||||||
return {
|
|
||||||
feature: 'personal',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
permissions: {
|
|
||||||
allow: {
|
|
||||||
loginStatus: [LOGIN_STATUS.LoggedIn],
|
|
||||||
},
|
|
||||||
deny: {
|
|
||||||
role: [ROLE.Default],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/settings/api',
|
|
||||||
name: VIEWS.API_SETTINGS,
|
|
||||||
components: {
|
|
||||||
default: SettingsApiView,
|
|
||||||
},
|
|
||||||
meta: {
|
|
||||||
telemetry: {
|
|
||||||
pageCategory: 'settings',
|
|
||||||
getProperties(route: Route) {
|
|
||||||
return {
|
|
||||||
feature: 'api',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
permissions: {
|
|
||||||
allow: {
|
|
||||||
loginStatus: [LOGIN_STATUS.LoggedIn],
|
|
||||||
},
|
|
||||||
deny: {
|
|
||||||
shouldDeny: () => {
|
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
return settingsStore.isPublicApiEnabled === false;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/settings/community-nodes',
|
|
||||||
name: VIEWS.COMMUNITY_NODES,
|
|
||||||
components: {
|
|
||||||
default: SettingsCommunityNodesView,
|
|
||||||
},
|
|
||||||
meta: {
|
|
||||||
telemetry: {
|
|
||||||
pageCategory: 'settings',
|
|
||||||
},
|
|
||||||
permissions: {
|
|
||||||
allow: {
|
|
||||||
role: [ROLE.Default, ROLE.Owner],
|
|
||||||
},
|
|
||||||
deny: {
|
|
||||||
shouldDeny: () => {
|
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
return settingsStore.isCommunityNodesFeatureEnabled === false;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/settings/coming-soon/:featureId',
|
|
||||||
name: VIEWS.FAKE_DOOR,
|
|
||||||
component: SettingsFakeDoorView,
|
|
||||||
props: true,
|
props: true,
|
||||||
meta: {
|
children: [
|
||||||
telemetry: {
|
{
|
||||||
pageCategory: 'settings',
|
path: 'personal',
|
||||||
getProperties(route: Route) {
|
name: VIEWS.PERSONAL_SETTINGS,
|
||||||
return {
|
components: {
|
||||||
feature: route.params['featureId'],
|
settingsView: SettingsPersonalView,
|
||||||
};
|
},
|
||||||
|
meta: {
|
||||||
|
telemetry: {
|
||||||
|
pageCategory: 'settings',
|
||||||
|
getProperties(route: Route) {
|
||||||
|
return {
|
||||||
|
feature: 'personal',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
allow: {
|
||||||
|
loginStatus: [LOGIN_STATUS.LoggedIn],
|
||||||
|
},
|
||||||
|
deny: {
|
||||||
|
role: [ROLE.Default],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
permissions: {
|
{
|
||||||
allow: {
|
path: 'users',
|
||||||
loginStatus: [LOGIN_STATUS.LoggedIn],
|
name: VIEWS.USERS_SETTINGS,
|
||||||
|
components: {
|
||||||
|
settingsView: SettingsUsersView,
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
telemetry: {
|
||||||
|
pageCategory: 'settings',
|
||||||
|
getProperties(route: Route) {
|
||||||
|
return {
|
||||||
|
feature: 'users',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
allow: {
|
||||||
|
role: [ROLE.Default, ROLE.Owner],
|
||||||
|
},
|
||||||
|
deny: {
|
||||||
|
shouldDeny: () => {
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
return settingsStore.isUserManagementEnabled === false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
|
path: 'api',
|
||||||
|
name: VIEWS.API_SETTINGS,
|
||||||
|
components: {
|
||||||
|
settingsView: SettingsApiView,
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
telemetry: {
|
||||||
|
pageCategory: 'settings',
|
||||||
|
getProperties(route: Route) {
|
||||||
|
return {
|
||||||
|
feature: 'api',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
allow: {
|
||||||
|
loginStatus: [LOGIN_STATUS.LoggedIn],
|
||||||
|
},
|
||||||
|
deny: {
|
||||||
|
shouldDeny: () => {
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
return settingsStore.isPublicApiEnabled === false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'community-nodes',
|
||||||
|
name: VIEWS.COMMUNITY_NODES,
|
||||||
|
components: {
|
||||||
|
settingsView: SettingsCommunityNodesView,
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
telemetry: {
|
||||||
|
pageCategory: 'settings',
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
allow: {
|
||||||
|
role: [ROLE.Default, ROLE.Owner],
|
||||||
|
},
|
||||||
|
deny: {
|
||||||
|
shouldDeny: () => {
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
return settingsStore.isCommunityNodesFeatureEnabled === false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'coming-soon/:featureId',
|
||||||
|
name: VIEWS.FAKE_DOOR,
|
||||||
|
components: {
|
||||||
|
settingsView: SettingsFakeDoorView,
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
telemetry: {
|
||||||
|
pageCategory: 'settings',
|
||||||
|
getProperties(route: Route) {
|
||||||
|
return {
|
||||||
|
feature: route.params['featureId'],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
allow: {
|
||||||
|
loginStatus: [LOGIN_STATUS.LoggedIn],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '*',
|
path: '*',
|
||||||
|
|
|
@ -220,7 +220,7 @@ 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 { getNodeViewTab } from '@/components/helpers';
|
import { getNodeViewTab } from '@/components/helpers';
|
||||||
import { Route } from 'vue-router';
|
import { Route, RawLocation } from 'vue-router';
|
||||||
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';
|
||||||
|
@ -347,11 +347,25 @@ export default mixins(
|
||||||
|
|
||||||
if (confirmModal === MODAL_CONFIRMED) {
|
if (confirmModal === MODAL_CONFIRMED) {
|
||||||
const saved = await this.saveCurrentWorkflow({}, false);
|
const saved = await this.saveCurrentWorkflow({}, false);
|
||||||
if (saved) this.settingsStore.fetchPromptsData();
|
if (saved) await this.settingsStore.fetchPromptsData();
|
||||||
this.uiStore.stateIsDirty = false;
|
this.uiStore.stateIsDirty = false;
|
||||||
next();
|
|
||||||
|
if(from.name === VIEWS.NEW_WORKFLOW) {
|
||||||
|
// Replace the current route with the new workflow route
|
||||||
|
// before navigating to the new route when saving new workflow.
|
||||||
|
this.$router.replace({ name: VIEWS.WORKFLOW, params: { name: this.currentWorkflow } }, () => {
|
||||||
|
|
||||||
|
// We can't use next() here since vue-router
|
||||||
|
// would prevent the navigation with an error
|
||||||
|
this.$router.push(to as RawLocation);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
} else if (confirmModal === MODAL_CANCEL) {
|
} else if (confirmModal === MODAL_CANCEL) {
|
||||||
|
await this.resetWorkspace();
|
||||||
this.uiStore.stateIsDirty = false;
|
this.uiStore.stateIsDirty = false;
|
||||||
|
|
||||||
next();
|
next();
|
||||||
} else if (confirmModal === MODAL_CLOSE) {
|
} else if (confirmModal === MODAL_CLOSE) {
|
||||||
next(false);
|
next(false);
|
||||||
|
|
|
@ -1,69 +1,67 @@
|
||||||
<template>
|
<template>
|
||||||
<SettingsView>
|
<div :class="$style.container">
|
||||||
<div :class="$style.container">
|
<div :class="$style.header">
|
||||||
<div :class="$style.header">
|
<n8n-heading size="2xlarge">
|
||||||
<n8n-heading size="2xlarge">
|
{{ $locale.baseText('settings.api') }}
|
||||||
{{ $locale.baseText('settings.api') }}
|
<span :style="{ fontSize: 'var(--font-size-s)', color: 'var(--color-text-light)', }">
|
||||||
<span :style="{ fontSize: 'var(--font-size-s)', color: 'var(--color-text-light)', }">
|
({{ $locale.baseText('beta') }})
|
||||||
({{ $locale.baseText('beta') }})
|
</span>
|
||||||
</span>
|
</n8n-heading>
|
||||||
</n8n-heading>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="apiKey">
|
|
||||||
<p class="mb-s">
|
|
||||||
<n8n-info-tip :bold="false">
|
|
||||||
<i18n path="settings.api.view.info" tag="span">
|
|
||||||
<template #apiAction>
|
|
||||||
<a
|
|
||||||
href="https://docs.n8n.io/api"
|
|
||||||
target="_blank"
|
|
||||||
v-text="$locale.baseText('settings.api.view.info.api')"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<template #webhookAction>
|
|
||||||
<a
|
|
||||||
href="https://docs.n8n.io/integrations/core-nodes/n8n-nodes-base.webhook/"
|
|
||||||
target="_blank"
|
|
||||||
v-text="$locale.baseText('settings.api.view.info.webhook')"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</i18n>
|
|
||||||
</n8n-info-tip>
|
|
||||||
</p>
|
|
||||||
<n8n-card class="mb-4xs" :class="$style.card">
|
|
||||||
<span :class="$style.delete">
|
|
||||||
<n8n-link @click="showDeleteModal" :bold="true">
|
|
||||||
{{ $locale.baseText('generic.delete') }}
|
|
||||||
</n8n-link>
|
|
||||||
</span>
|
|
||||||
<div class="ph-no-capture">
|
|
||||||
<CopyInput
|
|
||||||
:label="$locale.baseText('settings.api.view.myKey')"
|
|
||||||
:value="apiKey"
|
|
||||||
:copy-button-text="$locale.baseText('generic.clickToCopy')"
|
|
||||||
:toast-title="$locale.baseText('settings.api.view.copy.toast')"
|
|
||||||
@copy="onCopy"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</n8n-card>
|
|
||||||
<div :class="$style.hint">
|
|
||||||
<n8n-text size="small">
|
|
||||||
{{ $locale.baseText('settings.api.view.tryapi') }}
|
|
||||||
</n8n-text>
|
|
||||||
<n8n-link :to="apiPlaygroundPath" :newWindow="true" size="small">
|
|
||||||
{{ $locale.baseText('settings.api.view.apiPlayground') }}
|
|
||||||
</n8n-link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<n8n-action-box
|
|
||||||
v-else-if="mounted"
|
|
||||||
:buttonText="$locale.baseText(loading ? 'settings.api.create.button.loading' : 'settings.api.create.button')"
|
|
||||||
:description="$locale.baseText('settings.api.create.description')"
|
|
||||||
@click="createApiKey"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</SettingsView>
|
|
||||||
|
<div v-if="apiKey">
|
||||||
|
<p class="mb-s">
|
||||||
|
<n8n-info-tip :bold="false">
|
||||||
|
<i18n path="settings.api.view.info" tag="span">
|
||||||
|
<template #apiAction>
|
||||||
|
<a
|
||||||
|
href="https://docs.n8n.io/api"
|
||||||
|
target="_blank"
|
||||||
|
v-text="$locale.baseText('settings.api.view.info.api')"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #webhookAction>
|
||||||
|
<a
|
||||||
|
href="https://docs.n8n.io/integrations/core-nodes/n8n-nodes-base.webhook/"
|
||||||
|
target="_blank"
|
||||||
|
v-text="$locale.baseText('settings.api.view.info.webhook')"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</i18n>
|
||||||
|
</n8n-info-tip>
|
||||||
|
</p>
|
||||||
|
<n8n-card class="mb-4xs" :class="$style.card">
|
||||||
|
<span :class="$style.delete">
|
||||||
|
<n8n-link @click="showDeleteModal" :bold="true">
|
||||||
|
{{ $locale.baseText('generic.delete') }}
|
||||||
|
</n8n-link>
|
||||||
|
</span>
|
||||||
|
<div class="ph-no-capture">
|
||||||
|
<CopyInput
|
||||||
|
:label="$locale.baseText('settings.api.view.myKey')"
|
||||||
|
:value="apiKey"
|
||||||
|
:copy-button-text="$locale.baseText('generic.clickToCopy')"
|
||||||
|
:toast-title="$locale.baseText('settings.api.view.copy.toast')"
|
||||||
|
@copy="onCopy"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</n8n-card>
|
||||||
|
<div :class="$style.hint">
|
||||||
|
<n8n-text size="small">
|
||||||
|
{{ $locale.baseText('settings.api.view.tryapi') }}
|
||||||
|
</n8n-text>
|
||||||
|
<n8n-link :to="apiPlaygroundPath" :newWindow="true" size="small">
|
||||||
|
{{ $locale.baseText('settings.api.view.apiPlayground') }}
|
||||||
|
</n8n-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<n8n-action-box
|
||||||
|
v-else-if="mounted"
|
||||||
|
:buttonText="$locale.baseText(loading ? 'settings.api.create.button.loading' : 'settings.api.create.button')"
|
||||||
|
:description="$locale.baseText('settings.api.create.description')"
|
||||||
|
@click="createApiKey"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -71,8 +69,7 @@ import { showMessage } from '@/components/mixins/showMessage';
|
||||||
import { IUser } from '@/Interface';
|
import { IUser } from '@/Interface';
|
||||||
import mixins from 'vue-typed-mixins';
|
import mixins from 'vue-typed-mixins';
|
||||||
|
|
||||||
import SettingsView from './SettingsView.vue';
|
import CopyInput from '@/components/CopyInput.vue';
|
||||||
import CopyInput from '../components/CopyInput.vue';
|
|
||||||
import { mapStores } from 'pinia';
|
import { mapStores } from 'pinia';
|
||||||
import { useSettingsStore } from '@/stores/settings';
|
import { useSettingsStore } from '@/stores/settings';
|
||||||
import { useRootStore } from '@/stores/n8nRootStore';
|
import { useRootStore } from '@/stores/n8nRootStore';
|
||||||
|
@ -83,7 +80,6 @@ export default mixins(
|
||||||
).extend({
|
).extend({
|
||||||
name: 'SettingsPersonalView',
|
name: 'SettingsPersonalView',
|
||||||
components: {
|
components: {
|
||||||
SettingsView,
|
|
||||||
CopyInput,
|
CopyInput,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
|
|
@ -1,62 +1,60 @@
|
||||||
<template>
|
<template>
|
||||||
<SettingsView>
|
<div :class="$style.container">
|
||||||
<div :class="$style.container">
|
<div :class="$style.headingContainer">
|
||||||
<div :class="$style.headingContainer">
|
<n8n-heading size="2xlarge">{{ $locale.baseText('settings.communityNodes') }}</n8n-heading>
|
||||||
<n8n-heading size="2xlarge">{{ $locale.baseText('settings.communityNodes') }}</n8n-heading>
|
<n8n-button
|
||||||
<n8n-button
|
v-if="!settingsStore.isQueueModeEnabled && communityNodesStore.getInstalledPackages.length > 0 && !loading"
|
||||||
v-if="!settingsStore.isQueueModeEnabled && communityNodesStore.getInstalledPackages.length > 0 && !loading"
|
:label="$locale.baseText('settings.communityNodes.installModal.installButton.label')"
|
||||||
:label="$locale.baseText('settings.communityNodes.installModal.installButton.label')"
|
size="large"
|
||||||
size="large"
|
@click="openInstallModal"
|
||||||
@click="openInstallModal"
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div v-if="settingsStore.isQueueModeEnabled" :class="$style.actionBoxContainer">
|
|
||||||
<n8n-action-box
|
|
||||||
:heading="$locale.baseText('settings.communityNodes.empty.title')"
|
|
||||||
:description="getEmptyStateDescription"
|
|
||||||
:calloutText="actionBoxConfig.calloutText"
|
|
||||||
:calloutTheme="actionBoxConfig.calloutTheme"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
:class="$style.cardsContainer"
|
|
||||||
v-else-if="loading"
|
|
||||||
>
|
|
||||||
<community-package-card
|
|
||||||
v-for="n in 2"
|
|
||||||
:key="'index-' + n"
|
|
||||||
:loading="true"
|
|
||||||
></community-package-card>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-else-if="communityNodesStore.getInstalledPackages.length === 0"
|
|
||||||
:class="$style.actionBoxContainer"
|
|
||||||
>
|
|
||||||
<n8n-action-box
|
|
||||||
:heading="$locale.baseText('settings.communityNodes.empty.title')"
|
|
||||||
:description="getEmptyStateDescription"
|
|
||||||
:buttonText="
|
|
||||||
shouldShowInstallButton
|
|
||||||
? $locale.baseText('settings.communityNodes.empty.installPackageLabel')
|
|
||||||
: ''
|
|
||||||
"
|
|
||||||
:calloutText="actionBoxConfig.calloutText"
|
|
||||||
:calloutTheme="actionBoxConfig.calloutTheme"
|
|
||||||
@click="openInstallModal"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
:class="$style.cardsContainer"
|
|
||||||
v-else
|
|
||||||
>
|
|
||||||
<community-package-card
|
|
||||||
v-for="communityPackage in communityNodesStore.getInstalledPackages"
|
|
||||||
:key="communityPackage.packageName"
|
|
||||||
:communityPackage="communityPackage"
|
|
||||||
></community-package-card>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</SettingsView>
|
<div v-if="settingsStore.isQueueModeEnabled" :class="$style.actionBoxContainer">
|
||||||
|
<n8n-action-box
|
||||||
|
:heading="$locale.baseText('settings.communityNodes.empty.title')"
|
||||||
|
:description="getEmptyStateDescription"
|
||||||
|
:calloutText="actionBoxConfig.calloutText"
|
||||||
|
:calloutTheme="actionBoxConfig.calloutTheme"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
:class="$style.cardsContainer"
|
||||||
|
v-else-if="loading"
|
||||||
|
>
|
||||||
|
<community-package-card
|
||||||
|
v-for="n in 2"
|
||||||
|
:key="'index-' + n"
|
||||||
|
:loading="true"
|
||||||
|
></community-package-card>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else-if="communityNodesStore.getInstalledPackages.length === 0"
|
||||||
|
:class="$style.actionBoxContainer"
|
||||||
|
>
|
||||||
|
<n8n-action-box
|
||||||
|
:heading="$locale.baseText('settings.communityNodes.empty.title')"
|
||||||
|
:description="getEmptyStateDescription"
|
||||||
|
:buttonText="
|
||||||
|
shouldShowInstallButton
|
||||||
|
? $locale.baseText('settings.communityNodes.empty.installPackageLabel')
|
||||||
|
: ''
|
||||||
|
"
|
||||||
|
:calloutText="actionBoxConfig.calloutText"
|
||||||
|
:calloutTheme="actionBoxConfig.calloutTheme"
|
||||||
|
@click="openInstallModal"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
:class="$style.cardsContainer"
|
||||||
|
v-else
|
||||||
|
>
|
||||||
|
<community-package-card
|
||||||
|
v-for="communityPackage in communityNodesStore.getInstalledPackages"
|
||||||
|
:key="communityPackage.packageName"
|
||||||
|
:communityPackage="communityPackage"
|
||||||
|
></community-package-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -64,10 +62,8 @@ import {
|
||||||
COMMUNITY_PACKAGE_INSTALL_MODAL_KEY,
|
COMMUNITY_PACKAGE_INSTALL_MODAL_KEY,
|
||||||
COMMUNITY_NODES_INSTALLATION_DOCS_URL,
|
COMMUNITY_NODES_INSTALLATION_DOCS_URL,
|
||||||
COMMUNITY_NODES_NPM_INSTALLATION_URL,
|
COMMUNITY_NODES_NPM_INSTALLATION_URL,
|
||||||
} from '../constants';
|
} from '@/constants';
|
||||||
import { mapGetters } from 'vuex';
|
import CommunityPackageCard from '@/components/CommunityPackageCard.vue';
|
||||||
import SettingsView from './SettingsView.vue';
|
|
||||||
import CommunityPackageCard from '../components/CommunityPackageCard.vue';
|
|
||||||
import { showMessage } from '@/components/mixins/showMessage';
|
import { showMessage } from '@/components/mixins/showMessage';
|
||||||
import mixins from 'vue-typed-mixins';
|
import mixins from 'vue-typed-mixins';
|
||||||
import { PublicInstalledPackage } from 'n8n-workflow';
|
import { PublicInstalledPackage } from 'n8n-workflow';
|
||||||
|
@ -84,7 +80,6 @@ export default mixins(
|
||||||
).extend({
|
).extend({
|
||||||
name: 'SettingsCommunityNodesView',
|
name: 'SettingsCommunityNodesView',
|
||||||
components: {
|
components: {
|
||||||
SettingsView,
|
|
||||||
CommunityPackageCard,
|
CommunityPackageCard,
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
|
|
|
@ -1,21 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<SettingsView>
|
<feature-coming-soon :featureId="featureId" showTitle />
|
||||||
<FeatureComingSoon :featureId="featureId" showTitle />
|
|
||||||
</SettingsView>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { IFakeDoor } from '@/Interface';
|
import { IFakeDoor } from '@/Interface';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import SettingsView from './SettingsView.vue';
|
import FeatureComingSoon from '@/components/FeatureComingSoon.vue';
|
||||||
import FeatureComingSoon from '../components/FeatureComingSoon.vue';
|
|
||||||
import { mapStores } from 'pinia';
|
import { mapStores } from 'pinia';
|
||||||
import { useUIStore } from '@/stores/ui';
|
import { useUIStore } from '@/stores/ui';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
name: 'SettingsFakeDoorView',
|
name: 'SettingsFakeDoorView',
|
||||||
components: {
|
components: {
|
||||||
SettingsView,
|
|
||||||
FeatureComingSoon,
|
FeatureComingSoon,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
|
|
@ -1,45 +1,44 @@
|
||||||
<template>
|
<template>
|
||||||
<SettingsView>
|
<div :class="$style.container">
|
||||||
<div :class="$style.container">
|
<div :class="$style.header">
|
||||||
<div :class="$style.header">
|
<n8n-heading size="2xlarge">{{ $locale.baseText('settings.personal.personalSettings') }}</n8n-heading>
|
||||||
<n8n-heading size="2xlarge">{{ $locale.baseText('settings.personal.personalSettings') }}</n8n-heading>
|
<div class="ph-no-capture" :class="$style.user">
|
||||||
<div class="ph-no-capture" :class="$style.user">
|
<span :class="$style.username">
|
||||||
<span :class="$style.username">
|
<n8n-text color="text-light">{{currentUser.fullName}}</n8n-text>
|
||||||
<n8n-text color="text-light">{{currentUser.fullName}}</n8n-text>
|
</span>
|
||||||
</span>
|
<n8n-avatar :firstName="currentUser.firstName" :lastName="currentUser.lastName" size="large" />
|
||||||
<n8n-avatar :firstName="currentUser.firstName" :lastName="currentUser.lastName" size="large" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div :class="$style.sectionHeader">
|
|
||||||
<n8n-heading size="large">{{ $locale.baseText('settings.personal.basicInformation') }}</n8n-heading>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<n8n-form-inputs
|
|
||||||
v-if="formInputs"
|
|
||||||
:inputs="formInputs"
|
|
||||||
:eventBus="formBus"
|
|
||||||
@input="onInput"
|
|
||||||
@ready="onReadyToSubmit"
|
|
||||||
@submit="onSubmit"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div :class="$style.sectionHeader">
|
|
||||||
<n8n-heading size="large">{{ $locale.baseText('settings.personal.security') }}</n8n-heading>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<n8n-input-label :label="$locale.baseText('auth.password')">
|
|
||||||
<n8n-link @click="openPasswordModal">{{ $locale.baseText('auth.changePassword') }}</n8n-link>
|
|
||||||
</n8n-input-label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<n8n-button float="right" :label="$locale.baseText('settings.personal.save')" size="large" :disabled="!hasAnyChanges || !readyToSubmit" @click="onSaveClick" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</SettingsView>
|
<div>
|
||||||
|
<div :class="$style.sectionHeader">
|
||||||
|
<n8n-heading size="large">{{ $locale.baseText('settings.personal.basicInformation') }}</n8n-heading>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<n8n-form-inputs
|
||||||
|
v-if="formInputs"
|
||||||
|
:inputs="formInputs"
|
||||||
|
:eventBus="formBus"
|
||||||
|
@input="onInput"
|
||||||
|
@ready="onReadyToSubmit"
|
||||||
|
@submit="onSubmit"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div :class="$style.sectionHeader">
|
||||||
|
<n8n-heading size="large">{{ $locale.baseText('settings.personal.security') }}</n8n-heading>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<n8n-input-label :label="$locale.baseText('auth.password')">
|
||||||
|
<n8n-link @click="openPasswordModal">{{ $locale.baseText('auth.changePassword') }}</n8n-link>
|
||||||
|
</n8n-input-label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<n8n-button float="right" :label="$locale.baseText('settings.personal.save')" size="large" :disabled="!hasAnyChanges || !readyToSubmit" @click="onSaveClick" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -52,15 +51,10 @@ import { mapStores } from 'pinia';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import mixins from 'vue-typed-mixins';
|
import mixins from 'vue-typed-mixins';
|
||||||
|
|
||||||
import SettingsView from './SettingsView.vue';
|
|
||||||
|
|
||||||
export default mixins(
|
export default mixins(
|
||||||
showMessage,
|
showMessage,
|
||||||
).extend({
|
).extend({
|
||||||
name: 'SettingsPersonalView',
|
name: 'SettingsPersonalView',
|
||||||
components: {
|
|
||||||
SettingsView,
|
|
||||||
},
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
hasAnyChanges: false,
|
hasAnyChanges: false,
|
||||||
|
|
|
@ -1,49 +1,46 @@
|
||||||
<template>
|
<template>
|
||||||
<SettingsView>
|
<div :class="$style.container">
|
||||||
<div :class="$style.container">
|
<div>
|
||||||
<div>
|
<n8n-heading size="2xlarge">{{ $locale.baseText('settings.users') }}</n8n-heading>
|
||||||
<n8n-heading size="2xlarge">{{ $locale.baseText('settings.users') }}</n8n-heading>
|
<div :class="$style.buttonContainer" v-if="!usersStore.showUMSetupWarning">
|
||||||
<div :class="$style.buttonContainer" v-if="!usersStore.showUMSetupWarning">
|
<n8n-tooltip :disabled="settingsStore.isSmtpSetup" placement="bottom">
|
||||||
<n8n-tooltip :disabled="settingsStore.isSmtpSetup" placement="bottom">
|
<i18n slot="content" path="settings.users.setupSMTPToInviteUsers" tag="span">
|
||||||
<i18n slot="content" path="settings.users.setupSMTPToInviteUsers" tag="span">
|
<template #action>
|
||||||
<template #action>
|
<a
|
||||||
<a
|
href="https://docs.n8n.io/reference/user-management.html#step-one-smtp"
|
||||||
href="https://docs.n8n.io/reference/user-management.html#step-one-smtp"
|
target="_blank"
|
||||||
target="_blank"
|
v-text="$locale.baseText('settings.users.setupSMTPToInviteUsers.instructions')"
|
||||||
v-text="$locale.baseText('settings.users.setupSMTPToInviteUsers.instructions')"
|
/>
|
||||||
/>
|
</template>
|
||||||
</template>
|
</i18n>
|
||||||
</i18n>
|
<div>
|
||||||
<div>
|
<n8n-button :label="$locale.baseText('settings.users.invite')" @click="onInvite" size="large" :disabled="!settingsStore.isSmtpSetup" />
|
||||||
<n8n-button :label="$locale.baseText('settings.users.invite')" @click="onInvite" size="large" :disabled="!settingsStore.isSmtpSetup" />
|
</div>
|
||||||
</div>
|
</n8n-tooltip>
|
||||||
</n8n-tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="usersStore.showUMSetupWarning" :class="$style.setupInfoContainer">
|
|
||||||
<n8n-action-box
|
|
||||||
:heading="$locale.baseText('settings.users.setupToInviteUsers')"
|
|
||||||
:buttonText="$locale.baseText('settings.users.setupMyAccount')"
|
|
||||||
:description="$locale.baseText('settings.users.setupToInviteUsersInfo')"
|
|
||||||
@click="redirectToSetup"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div :class="$style.usersContainer" v-else>
|
|
||||||
<PageAlert
|
|
||||||
v-if="!settingsStore.isSmtpSetup"
|
|
||||||
:message="$locale.baseText('settings.users.smtpToAddUsersWarning')"
|
|
||||||
:popupClass="$style.alert"
|
|
||||||
/>
|
|
||||||
<n8n-users-list :users="usersStore.allUsers" :currentUserId="usersStore.currentUserId" @delete="onDelete" @reinvite="onReinvite" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</SettingsView>
|
<div v-if="usersStore.showUMSetupWarning" :class="$style.setupInfoContainer">
|
||||||
|
<n8n-action-box
|
||||||
|
:heading="$locale.baseText('settings.users.setupToInviteUsers')"
|
||||||
|
:buttonText="$locale.baseText('settings.users.setupMyAccount')"
|
||||||
|
:description="$locale.baseText('settings.users.setupToInviteUsersInfo')"
|
||||||
|
@click="redirectToSetup"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div :class="$style.usersContainer" v-else>
|
||||||
|
<PageAlert
|
||||||
|
v-if="!settingsStore.isSmtpSetup"
|
||||||
|
:message="$locale.baseText('settings.users.smtpToAddUsersWarning')"
|
||||||
|
:popupClass="$style.alert"
|
||||||
|
/>
|
||||||
|
<n8n-users-list :users="usersStore.allUsers" :currentUserId="usersStore.currentUserId" @delete="onDelete" @reinvite="onReinvite" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { INVITE_USER_MODAL_KEY, VIEWS } from '@/constants';
|
import { INVITE_USER_MODAL_KEY, VIEWS } from '@/constants';
|
||||||
|
|
||||||
import SettingsView from './SettingsView.vue';
|
|
||||||
import PageAlert from '../components/PageAlert.vue';
|
import PageAlert from '../components/PageAlert.vue';
|
||||||
import { IUser } from '@/Interface';
|
import { IUser } from '@/Interface';
|
||||||
import mixins from 'vue-typed-mixins';
|
import mixins from 'vue-typed-mixins';
|
||||||
|
@ -56,7 +53,6 @@ import { useUsersStore } from '@/stores/users';
|
||||||
export default mixins(showMessage).extend({
|
export default mixins(showMessage).extend({
|
||||||
name: 'SettingsUsersView',
|
name: 'SettingsUsersView',
|
||||||
components: {
|
components: {
|
||||||
SettingsView,
|
|
||||||
PageAlert,
|
PageAlert,
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
|
|
@ -1,26 +1,48 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="$style.container">
|
<div :class="$style.container">
|
||||||
<SettingsSidebar />
|
<SettingsSidebar @return="onReturn" />
|
||||||
<div :class="$style.contentContainer">
|
<div :class="$style.contentContainer">
|
||||||
<div :class="$style.content">
|
<div :class="$style.content">
|
||||||
<slot>
|
<!--
|
||||||
</slot>
|
Because we're using nested routes the props are going to be bind to the top level route
|
||||||
|
so we need to pass them down to the child component
|
||||||
|
-->
|
||||||
|
<router-view name="settingsView" v-bind="$attrs" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
import { Route } from 'vue-router';
|
||||||
|
|
||||||
import SettingsSidebar from '../components/SettingsSidebar.vue';
|
import { VIEWS } from '@/constants';
|
||||||
|
import SettingsSidebar from '@/components/SettingsSidebar.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
const SettingsView = defineComponent({
|
||||||
name: 'SettingsView',
|
name: 'SettingsView',
|
||||||
components: {
|
components: {
|
||||||
SettingsSidebar,
|
SettingsSidebar,
|
||||||
},
|
},
|
||||||
|
beforeRouteEnter(to, from, next) {
|
||||||
|
next(vm => {
|
||||||
|
(vm as unknown as InstanceType<typeof SettingsView>).previousRoute = from;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
previousRoute: null as Route | null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onReturn() {
|
||||||
|
this.$router.push(this.previousRoute ? this.previousRoute.path : { name: VIEWS.HOMEPAGE });
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default SettingsView;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
Loading…
Reference in a new issue