mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 04:34:06 -08:00
fix(editor): Use web native <a> element in nav menus (#8385)
This commit is contained in:
parent
6fcf5ddcdd
commit
e606e841ee
|
@ -74,6 +74,7 @@
|
||||||
"sanitize-html": "2.10.0",
|
"sanitize-html": "2.10.0",
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
"vue-boring-avatars": "^1.3.0",
|
"vue-boring-avatars": "^1.3.0",
|
||||||
|
"vue-router": "^4.2.2",
|
||||||
"xss": "^1.0.14"
|
"xss": "^1.0.14"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* Component that renders either a RouterLink or a normal anchor tag or
|
||||||
|
* just the slot content based on whether the `to` or `href` prop is
|
||||||
|
* passed or not.
|
||||||
|
*/
|
||||||
|
import { useAttrs } from 'vue';
|
||||||
|
import type { RouterLinkProps } from 'vue-router';
|
||||||
|
import { RouterLink } from 'vue-router';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'ConditionalRouterLink',
|
||||||
|
inheritAttrs: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
// @ts-expect-error TS doesn't understand this but it works
|
||||||
|
...RouterLink.props,
|
||||||
|
// Make to optional
|
||||||
|
to: {
|
||||||
|
type: [String, Object] as unknown as () => string | RouterLinkProps['to'] | undefined,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
// <a> element "props" are passed as attributes
|
||||||
|
}) as Partial<RouterLinkProps>;
|
||||||
|
const attrs = useAttrs();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<RouterLink v-if="props.to" v-bind="props" :to="props.to">
|
||||||
|
<slot />
|
||||||
|
</RouterLink>
|
||||||
|
<a v-else-if="attrs.href" v-bind="attrs">
|
||||||
|
<slot />
|
||||||
|
</a>
|
||||||
|
<slot v-else />
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { render } from '@testing-library/vue';
|
||||||
|
import { beforeAll, describe } from 'vitest';
|
||||||
|
import { createRouter, createWebHistory } from 'vue-router';
|
||||||
|
import CondtionalRouterLink from '../CondtionalRouterLink.vue';
|
||||||
|
|
||||||
|
const slots = {
|
||||||
|
default: 'Button',
|
||||||
|
};
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(),
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'home',
|
||||||
|
redirect: '/home',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('CondtionalRouterLink', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
await router.push('/');
|
||||||
|
|
||||||
|
await router.isReady();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders router-link when 'to' prop is passed", () => {
|
||||||
|
const wrapper = render(CondtionalRouterLink, {
|
||||||
|
props: {
|
||||||
|
to: { name: 'home' },
|
||||||
|
},
|
||||||
|
slots,
|
||||||
|
global: {
|
||||||
|
plugins: [router],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.html()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders <a> when 'href' attr is passed", () => {
|
||||||
|
const wrapper = render(CondtionalRouterLink, {
|
||||||
|
attrs: {
|
||||||
|
href: 'https://n8n.io',
|
||||||
|
target: '_blank',
|
||||||
|
},
|
||||||
|
slots,
|
||||||
|
global: {
|
||||||
|
plugins: [router],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.html()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders only the slot when neither to nor href is given', () => {
|
||||||
|
const wrapper = render(CondtionalRouterLink, {
|
||||||
|
slots,
|
||||||
|
global: {
|
||||||
|
plugins: [router],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.html()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,7 @@
|
||||||
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
|
exports[`CondtionalRouterLink > renders <a> when 'href' attr is passed 1`] = `"<div><a href="https://n8n.io" target="_blank">Button</a></div>"`;
|
||||||
|
|
||||||
|
exports[`CondtionalRouterLink > renders only the slot when neither to nor href is given 1`] = `"<div>Button</div>"`;
|
||||||
|
|
||||||
|
exports[`CondtionalRouterLink > renders router-link when 'to' prop is passed 1`] = `"<div><a href="/" class="">Button</a></div>"`;
|
|
@ -0,0 +1,3 @@
|
||||||
|
import CondtionalRouterLink from './CondtionalRouterLink.vue';
|
||||||
|
|
||||||
|
export default CondtionalRouterLink;
|
|
@ -114,10 +114,9 @@ const menuItems = [
|
||||||
id: 'website',
|
id: 'website',
|
||||||
icon: 'globe',
|
icon: 'globe',
|
||||||
label: 'Website',
|
label: 'Website',
|
||||||
type: 'link',
|
link: {
|
||||||
properties: {
|
|
||||||
href: 'https://www.n8n.io',
|
href: 'https://www.n8n.io',
|
||||||
newWindow: true,
|
target: '_blank',
|
||||||
},
|
},
|
||||||
position: 'bottom',
|
position: 'bottom',
|
||||||
},
|
},
|
||||||
|
@ -140,10 +139,9 @@ const menuItems = [
|
||||||
id: 'quickstart',
|
id: 'quickstart',
|
||||||
icon: 'video',
|
icon: 'video',
|
||||||
label: 'Quickstart',
|
label: 'Quickstart',
|
||||||
type: 'link',
|
link: {
|
||||||
properties: {
|
|
||||||
href: 'https://www.youtube.com/watch?v=RpjQTGKm-ok',
|
href: 'https://www.youtube.com/watch?v=RpjQTGKm-ok',
|
||||||
newWindow: true,
|
target: '_blank',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -59,6 +59,7 @@ import N8nMenuItem from '../N8nMenuItem';
|
||||||
import type { PropType } from 'vue';
|
import type { PropType } from 'vue';
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import type { IMenuItem, RouteObject } from '../../types';
|
import type { IMenuItem, RouteObject } from '../../types';
|
||||||
|
import { doesMenuItemMatchCurrentRoute } from '../N8nMenuItem/routerUtil';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'N8nMenu',
|
name: 'N8nMenu',
|
||||||
|
@ -128,14 +129,10 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.mode === 'router') {
|
if (this.mode === 'router') {
|
||||||
const found = this.items.find((item) => {
|
const found = this.items.find((item) =>
|
||||||
return (
|
doesMenuItemMatchCurrentRoute(item, this.currentRoute),
|
||||||
(Array.isArray(item.activateOnRouteNames) &&
|
|
||||||
item.activateOnRouteNames.includes(this.currentRoute.name || '')) ||
|
|
||||||
(Array.isArray(item.activateOnRoutePaths) &&
|
|
||||||
item.activateOnRoutePaths.includes(this.currentRoute.path))
|
|
||||||
);
|
);
|
||||||
});
|
|
||||||
this.activeTab = found ? found.id : '';
|
this.activeTab = found ? found.id : '';
|
||||||
} else {
|
} else {
|
||||||
this.activeTab = this.items.length > 0 ? this.items[0].id : '';
|
this.activeTab = this.items.length > 0 ? this.items[0].id : '';
|
||||||
|
@ -145,19 +142,6 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onSelect(item: IMenuItem): void {
|
onSelect(item: IMenuItem): void {
|
||||||
if (item && item.type === 'link' && item.properties) {
|
|
||||||
const href: string = item.properties.href;
|
|
||||||
if (!href) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.properties.newWindow) {
|
|
||||||
window.open(href);
|
|
||||||
} else {
|
|
||||||
window.location.assign(item.properties.href);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.mode === 'tabs') {
|
if (this.mode === 'tabs') {
|
||||||
this.activeTab = item.id;
|
this.activeTab = item.id;
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,10 +75,9 @@ link.args = {
|
||||||
id: 'website',
|
id: 'website',
|
||||||
icon: 'globe',
|
icon: 'globe',
|
||||||
label: 'Website',
|
label: 'Website',
|
||||||
type: 'link',
|
link: {
|
||||||
properties: {
|
|
||||||
href: 'https://www.n8n.io',
|
href: 'https://www.n8n.io',
|
||||||
newWindow: true,
|
target: '_blank',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -96,10 +95,9 @@ withChildren.args = {
|
||||||
id: 'quickstart',
|
id: 'quickstart',
|
||||||
icon: 'video',
|
icon: 'video',
|
||||||
label: 'Quickstart',
|
label: 'Quickstart',
|
||||||
type: 'link',
|
link: {
|
||||||
properties: {
|
|
||||||
href: 'https://www.youtube.com/watch?v=RpjQTGKm-ok',
|
href: 'https://www.youtube.com/watch?v=RpjQTGKm-ok',
|
||||||
newWindow: true,
|
target: '_blank',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
:disabled="!compact"
|
:disabled="!compact"
|
||||||
:show-after="tooltipDelay"
|
:show-after="tooltipDelay"
|
||||||
>
|
>
|
||||||
|
<ConditionalRouterLink v-bind="item.route ?? item.link">
|
||||||
<ElMenuItem
|
<ElMenuItem
|
||||||
:id="item.id"
|
:id="item.id"
|
||||||
:class="{
|
:class="{
|
||||||
|
@ -71,6 +72,7 @@
|
||||||
<N8nIcon :icon="item.secondaryIcon.name" :size="item.secondaryIcon.size || 'small'" />
|
<N8nIcon :icon="item.secondaryIcon.name" :size="item.secondaryIcon.size || 'small'" />
|
||||||
</N8nTooltip>
|
</N8nTooltip>
|
||||||
</ElMenuItem>
|
</ElMenuItem>
|
||||||
|
</ConditionalRouterLink>
|
||||||
</N8nTooltip>
|
</N8nTooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -81,7 +83,9 @@ import N8nTooltip from '../N8nTooltip';
|
||||||
import N8nIcon from '../N8nIcon';
|
import N8nIcon from '../N8nIcon';
|
||||||
import type { PropType } from 'vue';
|
import type { PropType } from 'vue';
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
import ConditionalRouterLink from '../ConditionalRouterLink';
|
||||||
import type { IMenuItem, RouteObject } from '../../types';
|
import type { IMenuItem, RouteObject } from '../../types';
|
||||||
|
import { doesMenuItemMatchCurrentRoute } from './routerUtil';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'N8nMenuItem',
|
name: 'N8nMenuItem',
|
||||||
|
@ -90,6 +94,7 @@ export default defineComponent({
|
||||||
ElMenuItem,
|
ElMenuItem,
|
||||||
N8nIcon,
|
N8nIcon,
|
||||||
N8nTooltip,
|
N8nTooltip,
|
||||||
|
ConditionalRouterLink,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
|
@ -115,9 +120,11 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
activeTab: {
|
activeTab: {
|
||||||
type: String,
|
type: String,
|
||||||
|
default: undefined,
|
||||||
},
|
},
|
||||||
handleSelect: {
|
handleSelect: {
|
||||||
type: Function as PropType<(item: IMenuItem) => void>,
|
type: Function as PropType<(item: IMenuItem) => void>,
|
||||||
|
default: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -151,18 +158,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
isActive(item: IMenuItem): boolean {
|
isActive(item: IMenuItem): boolean {
|
||||||
if (this.mode === 'router') {
|
if (this.mode === 'router') {
|
||||||
if (item.activateOnRoutePaths) {
|
return doesMenuItemMatchCurrentRoute(item, this.currentRoute);
|
||||||
return (
|
|
||||||
Array.isArray(item.activateOnRoutePaths) &&
|
|
||||||
item.activateOnRoutePaths.includes(this.currentRoute.path)
|
|
||||||
);
|
|
||||||
} else if (item.activateOnRouteNames) {
|
|
||||||
return (
|
|
||||||
Array.isArray(item.activateOnRouteNames) &&
|
|
||||||
item.activateOnRouteNames.includes(this.currentRoute.name || '')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} else {
|
} else {
|
||||||
return item.id === this.activeTab;
|
return item.id === this.activeTab;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
import type { IMenuItem, RouteObject } from '@/types';
|
||||||
|
import type { RouteLocationRaw } from 'vue-router';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the given menu item matches the current route.
|
||||||
|
*/
|
||||||
|
export function doesMenuItemMatchCurrentRoute(item: IMenuItem, currentRoute: RouteObject) {
|
||||||
|
let activateOnRouteNames: string[] = [];
|
||||||
|
if (Array.isArray(item.activateOnRouteNames)) {
|
||||||
|
activateOnRouteNames = item.activateOnRouteNames;
|
||||||
|
} else if (item.route && isNamedRouteLocation(item.route.to)) {
|
||||||
|
activateOnRouteNames = [item.route.to.name];
|
||||||
|
}
|
||||||
|
|
||||||
|
let activateOnRoutePaths: string[] = [];
|
||||||
|
if (Array.isArray(item.activateOnRoutePaths)) {
|
||||||
|
activateOnRoutePaths = item.activateOnRoutePaths;
|
||||||
|
} else if (item.route && isPathRouteLocation(item.route.to)) {
|
||||||
|
activateOnRoutePaths = [item.route.to.path];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
activateOnRouteNames.includes(currentRoute.name ?? '') ||
|
||||||
|
activateOnRoutePaths.includes(currentRoute.path)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPathRouteLocation(routeLocation?: RouteLocationRaw): routeLocation is { path: string } {
|
||||||
|
return (
|
||||||
|
typeof routeLocation === 'object' &&
|
||||||
|
'path' in routeLocation &&
|
||||||
|
typeof routeLocation.path === 'string'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNamedRouteLocation(routeLocation?: RouteLocationRaw): routeLocation is { name: string } {
|
||||||
|
return (
|
||||||
|
typeof routeLocation === 'object' &&
|
||||||
|
'name' in routeLocation &&
|
||||||
|
typeof routeLocation.name === 'string'
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
import type { ElTooltipProps } from 'element-plus';
|
import type { ElTooltipProps } from 'element-plus';
|
||||||
|
import type { AnchorHTMLAttributes } from 'vue';
|
||||||
|
import type { RouteLocationRaw, RouterLinkProps } from 'vue-router';
|
||||||
|
|
||||||
export type IMenuItem = {
|
export type IMenuItem = {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -7,22 +9,32 @@ export type IMenuItem = {
|
||||||
secondaryIcon?: {
|
secondaryIcon?: {
|
||||||
name: string;
|
name: string;
|
||||||
size?: 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge';
|
size?: 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge';
|
||||||
tooltip?: ElTooltipProps;
|
tooltip?: Partial<ElTooltipProps>;
|
||||||
};
|
};
|
||||||
customIconSize?: 'medium' | 'small';
|
customIconSize?: 'medium' | 'small';
|
||||||
available?: boolean;
|
available?: boolean;
|
||||||
position?: 'top' | 'bottom';
|
position?: 'top' | 'bottom';
|
||||||
type?: 'default' | 'link';
|
|
||||||
properties?: ILinkMenuItemProperties;
|
/** Use this for external links */
|
||||||
// For router menus populate only one of those arrays:
|
link?: ILinkMenuItemProperties;
|
||||||
// If menu item can be activated on certain route names (easy mode)
|
/** Use this for defining a vue-router target */
|
||||||
|
route?: RouterLinkProps;
|
||||||
|
/**
|
||||||
|
* If given, item will be activated on these route names. Note that if
|
||||||
|
* route is provided, it will be highlighted automatically
|
||||||
|
*/
|
||||||
activateOnRouteNames?: string[];
|
activateOnRouteNames?: string[];
|
||||||
// For more specific matching, we can use paths
|
|
||||||
activateOnRoutePaths?: string[];
|
activateOnRoutePaths?: string[];
|
||||||
|
|
||||||
children?: IMenuItem[];
|
children?: IMenuItem[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type IRouteMenuItemProperties = {
|
||||||
|
route: RouteLocationRaw;
|
||||||
|
};
|
||||||
|
|
||||||
export type ILinkMenuItemProperties = {
|
export type ILinkMenuItemProperties = {
|
||||||
href: string;
|
href: string;
|
||||||
newWindow?: boolean;
|
target?: AnchorHTMLAttributes['target'];
|
||||||
|
rel?: AnchorHTMLAttributes['rel'];
|
||||||
};
|
};
|
||||||
|
|
|
@ -1270,7 +1270,6 @@ export interface UIState {
|
||||||
nodeViewOffsetPosition: XYPosition;
|
nodeViewOffsetPosition: XYPosition;
|
||||||
nodeViewMoveInProgress: boolean;
|
nodeViewMoveInProgress: boolean;
|
||||||
selectedNodes: INodeUi[];
|
selectedNodes: INodeUi[];
|
||||||
sidebarMenuItems: IMenuItem[];
|
|
||||||
nodeViewInitialized: boolean;
|
nodeViewInitialized: boolean;
|
||||||
addFirstStepOnLoad: boolean;
|
addFirstStepOnLoad: boolean;
|
||||||
executionSidebarAutoRefresh: boolean;
|
executionSidebarAutoRefresh: boolean;
|
||||||
|
|
|
@ -118,7 +118,6 @@ import { useUIStore } from '@/stores/ui.store';
|
||||||
import { useUsersStore } from '@/stores/users.store';
|
import { useUsersStore } from '@/stores/users.store';
|
||||||
import { useVersionsStore } from '@/stores/versions.store';
|
import { useVersionsStore } from '@/stores/versions.store';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
import { isNavigationFailure } from 'vue-router';
|
|
||||||
import ExecutionsUsage from '@/components/ExecutionsUsage.vue';
|
import ExecutionsUsage from '@/components/ExecutionsUsage.vue';
|
||||||
import BecomeTemplateCreatorCta from '@/components/BecomeTemplateCreatorCta/BecomeTemplateCreatorCta.vue';
|
import BecomeTemplateCreatorCta from '@/components/BecomeTemplateCreatorCta/BecomeTemplateCreatorCta.vue';
|
||||||
import MainSidebarSourceControl from '@/components/MainSidebarSourceControl.vue';
|
import MainSidebarSourceControl from '@/components/MainSidebarSourceControl.vue';
|
||||||
|
@ -205,38 +204,24 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
mainMenuItems(): IMenuItem[] {
|
mainMenuItems(): IMenuItem[] {
|
||||||
const items: IMenuItem[] = [];
|
const items: IMenuItem[] = [];
|
||||||
const injectedItems = this.uiStore.sidebarMenuItems;
|
|
||||||
|
|
||||||
const workflows: IMenuItem = {
|
const workflows: IMenuItem = {
|
||||||
id: 'workflows',
|
id: 'workflows',
|
||||||
icon: 'network-wired',
|
icon: 'network-wired',
|
||||||
label: this.$locale.baseText('mainSidebar.workflows'),
|
label: this.$locale.baseText('mainSidebar.workflows'),
|
||||||
position: 'top',
|
position: 'top',
|
||||||
activateOnRouteNames: [VIEWS.WORKFLOWS],
|
route: { to: { name: VIEWS.WORKFLOWS } },
|
||||||
};
|
secondaryIcon: this.sourceControlStore.preferences.branchReadOnly
|
||||||
|
? {
|
||||||
if (this.sourceControlStore.preferences.branchReadOnly) {
|
|
||||||
workflows.secondaryIcon = {
|
|
||||||
name: 'lock',
|
name: 'lock',
|
||||||
tooltip: {
|
tooltip: {
|
||||||
content: this.$locale.baseText('mainSidebar.workflows.readOnlyEnv.tooltip'),
|
content: this.$locale.baseText('mainSidebar.workflows.readOnlyEnv.tooltip'),
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
if (injectedItems && injectedItems.length > 0) {
|
|
||||||
for (const item of injectedItems) {
|
|
||||||
items.push({
|
|
||||||
id: item.id,
|
|
||||||
icon: item.icon || '',
|
|
||||||
label: item.label || '',
|
|
||||||
position: item.position,
|
|
||||||
type: item.properties?.href ? 'link' : 'regular',
|
|
||||||
properties: item.properties,
|
|
||||||
} as IMenuItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const defaultSettingsRoute = this.findFirstAccessibleSettingsRoute();
|
||||||
const regularItems: IMenuItem[] = [
|
const regularItems: IMenuItem[] = [
|
||||||
workflows,
|
workflows,
|
||||||
{
|
{
|
||||||
|
@ -245,7 +230,7 @@ export default defineComponent({
|
||||||
label: this.$locale.baseText('mainSidebar.templates'),
|
label: this.$locale.baseText('mainSidebar.templates'),
|
||||||
position: 'top',
|
position: 'top',
|
||||||
available: this.settingsStore.isTemplatesEnabled,
|
available: this.settingsStore.isTemplatesEnabled,
|
||||||
activateOnRouteNames: [VIEWS.TEMPLATES],
|
route: { to: { name: VIEWS.TEMPLATES } },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'credentials',
|
id: 'credentials',
|
||||||
|
@ -253,7 +238,7 @@ export default defineComponent({
|
||||||
label: this.$locale.baseText('mainSidebar.credentials'),
|
label: this.$locale.baseText('mainSidebar.credentials'),
|
||||||
customIconSize: 'medium',
|
customIconSize: 'medium',
|
||||||
position: 'top',
|
position: 'top',
|
||||||
activateOnRouteNames: [VIEWS.CREDENTIALS],
|
route: { to: { name: VIEWS.CREDENTIALS } },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'variables',
|
id: 'variables',
|
||||||
|
@ -261,18 +246,17 @@ export default defineComponent({
|
||||||
label: this.$locale.baseText('mainSidebar.variables'),
|
label: this.$locale.baseText('mainSidebar.variables'),
|
||||||
customIconSize: 'medium',
|
customIconSize: 'medium',
|
||||||
position: 'top',
|
position: 'top',
|
||||||
activateOnRouteNames: [VIEWS.VARIABLES],
|
route: { to: { name: VIEWS.VARIABLES } },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'executions',
|
id: 'executions',
|
||||||
icon: 'tasks',
|
icon: 'tasks',
|
||||||
label: this.$locale.baseText('mainSidebar.executions'),
|
label: this.$locale.baseText('mainSidebar.executions'),
|
||||||
position: 'top',
|
position: 'top',
|
||||||
activateOnRouteNames: [VIEWS.EXECUTIONS],
|
route: { to: { name: VIEWS.EXECUTIONS } },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'cloud-admin',
|
id: 'cloud-admin',
|
||||||
type: 'link',
|
|
||||||
position: 'bottom',
|
position: 'bottom',
|
||||||
label: 'Admin Panel',
|
label: 'Admin Panel',
|
||||||
icon: 'home',
|
icon: 'home',
|
||||||
|
@ -285,6 +269,7 @@ export default defineComponent({
|
||||||
position: 'bottom',
|
position: 'bottom',
|
||||||
available: this.canUserAccessSettings && this.usersStore.currentUser !== null,
|
available: this.canUserAccessSettings && this.usersStore.currentUser !== null,
|
||||||
activateOnRouteNames: [VIEWS.USERS_SETTINGS, VIEWS.API_SETTINGS, VIEWS.PERSONAL_SETTINGS],
|
activateOnRouteNames: [VIEWS.USERS_SETTINGS, VIEWS.API_SETTINGS, VIEWS.PERSONAL_SETTINGS],
|
||||||
|
route: { to: defaultSettingsRoute },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'help',
|
id: 'help',
|
||||||
|
@ -296,40 +281,36 @@ export default defineComponent({
|
||||||
id: 'quickstart',
|
id: 'quickstart',
|
||||||
icon: 'video',
|
icon: 'video',
|
||||||
label: this.$locale.baseText('mainSidebar.helpMenuItems.quickstart'),
|
label: this.$locale.baseText('mainSidebar.helpMenuItems.quickstart'),
|
||||||
type: 'link',
|
link: {
|
||||||
properties: {
|
|
||||||
href: 'https://www.youtube.com/watch?v=1MwSoB0gnM4',
|
href: 'https://www.youtube.com/watch?v=1MwSoB0gnM4',
|
||||||
newWindow: true,
|
target: '_blank',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'docs',
|
id: 'docs',
|
||||||
icon: 'book',
|
icon: 'book',
|
||||||
label: this.$locale.baseText('mainSidebar.helpMenuItems.documentation'),
|
label: this.$locale.baseText('mainSidebar.helpMenuItems.documentation'),
|
||||||
type: 'link',
|
link: {
|
||||||
properties: {
|
|
||||||
href: 'https://docs.n8n.io?utm_source=n8n_app&utm_medium=app_sidebar',
|
href: 'https://docs.n8n.io?utm_source=n8n_app&utm_medium=app_sidebar',
|
||||||
newWindow: true,
|
target: '_blank',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'forum',
|
id: 'forum',
|
||||||
icon: 'users',
|
icon: 'users',
|
||||||
label: this.$locale.baseText('mainSidebar.helpMenuItems.forum'),
|
label: this.$locale.baseText('mainSidebar.helpMenuItems.forum'),
|
||||||
type: 'link',
|
link: {
|
||||||
properties: {
|
|
||||||
href: 'https://community.n8n.io?utm_source=n8n_app&utm_medium=app_sidebar',
|
href: 'https://community.n8n.io?utm_source=n8n_app&utm_medium=app_sidebar',
|
||||||
newWindow: true,
|
target: '_blank',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'examples',
|
id: 'examples',
|
||||||
icon: 'graduation-cap',
|
icon: 'graduation-cap',
|
||||||
label: this.$locale.baseText('mainSidebar.helpMenuItems.course'),
|
label: this.$locale.baseText('mainSidebar.helpMenuItems.course'),
|
||||||
type: 'link',
|
link: {
|
||||||
properties: {
|
|
||||||
href: 'https://www.youtube.com/watch?v=1MwSoB0gnM4',
|
href: 'https://www.youtube.com/watch?v=1MwSoB0gnM4',
|
||||||
newWindow: true,
|
target: '_blank',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -421,46 +402,6 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
async handleSelect(key: string) {
|
async handleSelect(key: string) {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'workflows': {
|
|
||||||
if (this.$router.currentRoute.value.name !== VIEWS.WORKFLOWS) {
|
|
||||||
this.goToRoute({ name: VIEWS.WORKFLOWS });
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'templates': {
|
|
||||||
if (this.$router.currentRoute.value.name !== VIEWS.TEMPLATES) {
|
|
||||||
this.goToRoute({ name: VIEWS.TEMPLATES });
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'credentials': {
|
|
||||||
if (this.$router.currentRoute.value.name !== VIEWS.CREDENTIALS) {
|
|
||||||
this.goToRoute({ name: VIEWS.CREDENTIALS });
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'variables': {
|
|
||||||
if (this.$router.currentRoute.value.name !== VIEWS.VARIABLES) {
|
|
||||||
this.goToRoute({ name: VIEWS.VARIABLES });
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'executions': {
|
|
||||||
if (this.$router.currentRoute.value.name !== VIEWS.EXECUTIONS) {
|
|
||||||
this.goToRoute({ name: VIEWS.EXECUTIONS });
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'settings': {
|
|
||||||
const defaultRoute = this.findFirstAccessibleSettingsRoute();
|
|
||||||
if (defaultRoute) {
|
|
||||||
const route = this.$router.resolve({ name: defaultRoute });
|
|
||||||
if (this.$router.currentRoute.value.name !== defaultRoute) {
|
|
||||||
this.goToRoute(route.path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'about': {
|
case 'about': {
|
||||||
this.trackHelpItemClick('about');
|
this.trackHelpItemClick('about');
|
||||||
this.uiStore.openModal(ABOUT_MODAL_KEY);
|
this.uiStore.openModal(ABOUT_MODAL_KEY);
|
||||||
|
@ -481,25 +422,18 @@ export default defineComponent({
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
goToRoute(route: string | { name: string }) {
|
|
||||||
this.$router.push(route).catch((failure) => {
|
|
||||||
console.log(failure);
|
|
||||||
// Catch navigation failures caused by route guards
|
|
||||||
if (!isNavigationFailure(failure)) {
|
|
||||||
console.error(failure);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
findFirstAccessibleSettingsRoute() {
|
findFirstAccessibleSettingsRoute() {
|
||||||
const settingsRoutes = this.$router
|
const settingsRoutes = this.$router
|
||||||
.getRoutes()
|
.getRoutes()
|
||||||
.find((route) => route.path === '/settings')!
|
.find((route) => route.path === '/settings')!
|
||||||
.children.map((route) => route.name || '');
|
.children.map((route) => route.name ?? '');
|
||||||
|
|
||||||
let defaultSettingsRoute = null;
|
let defaultSettingsRoute = { name: VIEWS.USERS_SETTINGS };
|
||||||
for (const route of settingsRoutes) {
|
for (const route of settingsRoutes) {
|
||||||
if (this.canUserAccessRouteByName(route.toString())) {
|
if (this.canUserAccessRouteByName(route.toString())) {
|
||||||
defaultSettingsRoute = route;
|
defaultSettingsRoute = {
|
||||||
|
name: route.toString() as VIEWS,
|
||||||
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ export default defineComponent({
|
||||||
label: this.$locale.baseText('settings.usageAndPlan.title'),
|
label: this.$locale.baseText('settings.usageAndPlan.title'),
|
||||||
position: 'top',
|
position: 'top',
|
||||||
available: this.canAccessUsageAndPlan(),
|
available: this.canAccessUsageAndPlan(),
|
||||||
activateOnRouteNames: [VIEWS.USAGE],
|
route: { to: { name: VIEWS.USAGE } },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'settings-personal',
|
id: 'settings-personal',
|
||||||
|
@ -57,7 +57,7 @@ export default defineComponent({
|
||||||
label: this.$locale.baseText('settings.personal'),
|
label: this.$locale.baseText('settings.personal'),
|
||||||
position: 'top',
|
position: 'top',
|
||||||
available: this.canAccessPersonalSettings(),
|
available: this.canAccessPersonalSettings(),
|
||||||
activateOnRouteNames: [VIEWS.PERSONAL_SETTINGS],
|
route: { to: { name: VIEWS.PERSONAL_SETTINGS } },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'settings-users',
|
id: 'settings-users',
|
||||||
|
@ -65,7 +65,7 @@ export default defineComponent({
|
||||||
label: this.$locale.baseText('settings.users'),
|
label: this.$locale.baseText('settings.users'),
|
||||||
position: 'top',
|
position: 'top',
|
||||||
available: this.canAccessUsersSettings(),
|
available: this.canAccessUsersSettings(),
|
||||||
activateOnRouteNames: [VIEWS.USERS_SETTINGS],
|
route: { to: { name: VIEWS.USERS_SETTINGS } },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'settings-api',
|
id: 'settings-api',
|
||||||
|
@ -73,7 +73,7 @@ export default defineComponent({
|
||||||
label: this.$locale.baseText('settings.n8napi'),
|
label: this.$locale.baseText('settings.n8napi'),
|
||||||
position: 'top',
|
position: 'top',
|
||||||
available: this.canAccessApiSettings(),
|
available: this.canAccessApiSettings(),
|
||||||
activateOnRouteNames: [VIEWS.API_SETTINGS],
|
route: { to: { name: VIEWS.API_SETTINGS } },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'settings-external-secrets',
|
id: 'settings-external-secrets',
|
||||||
|
@ -81,10 +81,7 @@ export default defineComponent({
|
||||||
label: this.$locale.baseText('settings.externalSecrets.title'),
|
label: this.$locale.baseText('settings.externalSecrets.title'),
|
||||||
position: 'top',
|
position: 'top',
|
||||||
available: this.canAccessExternalSecrets(),
|
available: this.canAccessExternalSecrets(),
|
||||||
activateOnRouteNames: [
|
route: { to: { name: VIEWS.EXTERNAL_SECRETS_SETTINGS } },
|
||||||
VIEWS.EXTERNAL_SECRETS_SETTINGS,
|
|
||||||
VIEWS.EXTERNAL_SECRETS_PROVIDER_SETTINGS,
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'settings-audit-logs',
|
id: 'settings-audit-logs',
|
||||||
|
@ -92,7 +89,7 @@ export default defineComponent({
|
||||||
label: this.$locale.baseText('settings.auditLogs.title'),
|
label: this.$locale.baseText('settings.auditLogs.title'),
|
||||||
position: 'top',
|
position: 'top',
|
||||||
available: this.canAccessAuditLogs(),
|
available: this.canAccessAuditLogs(),
|
||||||
activateOnRouteNames: [VIEWS.AUDIT_LOGS],
|
route: { to: { name: VIEWS.AUDIT_LOGS } },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'settings-source-control',
|
id: 'settings-source-control',
|
||||||
|
@ -100,7 +97,7 @@ export default defineComponent({
|
||||||
label: this.$locale.baseText('settings.sourceControl.title'),
|
label: this.$locale.baseText('settings.sourceControl.title'),
|
||||||
position: 'top',
|
position: 'top',
|
||||||
available: this.canAccessSourceControl(),
|
available: this.canAccessSourceControl(),
|
||||||
activateOnRouteNames: [VIEWS.SOURCE_CONTROL],
|
route: { to: { name: VIEWS.SOURCE_CONTROL } },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'settings-sso',
|
id: 'settings-sso',
|
||||||
|
@ -108,7 +105,7 @@ export default defineComponent({
|
||||||
label: this.$locale.baseText('settings.sso'),
|
label: this.$locale.baseText('settings.sso'),
|
||||||
position: 'top',
|
position: 'top',
|
||||||
available: this.canAccessSso(),
|
available: this.canAccessSso(),
|
||||||
activateOnRouteNames: [VIEWS.SSO_SETTINGS],
|
route: { to: { name: VIEWS.SSO_SETTINGS } },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'settings-ldap',
|
id: 'settings-ldap',
|
||||||
|
@ -116,7 +113,7 @@ export default defineComponent({
|
||||||
label: this.$locale.baseText('settings.ldap'),
|
label: this.$locale.baseText('settings.ldap'),
|
||||||
position: 'top',
|
position: 'top',
|
||||||
available: this.canAccessLdapSettings(),
|
available: this.canAccessLdapSettings(),
|
||||||
activateOnRouteNames: [VIEWS.LDAP_SETTINGS],
|
route: { to: { name: VIEWS.LDAP_SETTINGS } },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'settings-workersview',
|
id: 'settings-workersview',
|
||||||
|
@ -126,7 +123,7 @@ export default defineComponent({
|
||||||
available:
|
available:
|
||||||
this.settingsStore.isQueueModeEnabled &&
|
this.settingsStore.isQueueModeEnabled &&
|
||||||
hasPermission(['rbac'], { rbac: { scope: 'workersView:manage' } }),
|
hasPermission(['rbac'], { rbac: { scope: 'workersView:manage' } }),
|
||||||
activateOnRouteNames: [VIEWS.WORKER_VIEW],
|
route: { to: { name: VIEWS.WORKER_VIEW } },
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -134,7 +131,7 @@ export default defineComponent({
|
||||||
if (item.uiLocations.includes('settings')) {
|
if (item.uiLocations.includes('settings')) {
|
||||||
menuItems.push({
|
menuItems.push({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
icon: item.icon || 'question',
|
icon: item.icon ?? 'question',
|
||||||
label: this.$locale.baseText(item.featureName as BaseTextKey),
|
label: this.$locale.baseText(item.featureName as BaseTextKey),
|
||||||
position: 'top',
|
position: 'top',
|
||||||
available: true,
|
available: true,
|
||||||
|
@ -149,7 +146,7 @@ export default defineComponent({
|
||||||
label: this.$locale.baseText('settings.log-streaming'),
|
label: this.$locale.baseText('settings.log-streaming'),
|
||||||
position: 'top',
|
position: 'top',
|
||||||
available: this.canAccessLogStreamingSettings(),
|
available: this.canAccessLogStreamingSettings(),
|
||||||
activateOnRouteNames: [VIEWS.LOG_STREAMING_SETTINGS],
|
route: { to: { name: VIEWS.LOG_STREAMING_SETTINGS } },
|
||||||
});
|
});
|
||||||
|
|
||||||
menuItems.push({
|
menuItems.push({
|
||||||
|
@ -158,7 +155,7 @@ export default defineComponent({
|
||||||
label: this.$locale.baseText('settings.communityNodes'),
|
label: this.$locale.baseText('settings.communityNodes'),
|
||||||
position: 'top',
|
position: 'top',
|
||||||
available: this.canAccessCommunityNodes(),
|
available: this.canAccessCommunityNodes(),
|
||||||
activateOnRouteNames: [VIEWS.COMMUNITY_NODES],
|
route: { to: { name: VIEWS.COMMUNITY_NODES } },
|
||||||
});
|
});
|
||||||
|
|
||||||
return menuItems;
|
return menuItems;
|
||||||
|
@ -211,51 +208,10 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
async handleSelect(key: string) {
|
async handleSelect(key: string) {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'settings-personal':
|
|
||||||
await this.navigateTo(VIEWS.PERSONAL_SETTINGS);
|
|
||||||
break;
|
|
||||||
case 'settings-users':
|
|
||||||
await this.navigateTo(VIEWS.USERS_SETTINGS);
|
|
||||||
break;
|
|
||||||
case 'settings-api':
|
|
||||||
await this.navigateTo(VIEWS.API_SETTINGS);
|
|
||||||
break;
|
|
||||||
case 'settings-ldap':
|
|
||||||
await this.navigateTo(VIEWS.LDAP_SETTINGS);
|
|
||||||
break;
|
|
||||||
case 'settings-log-streaming':
|
|
||||||
await this.navigateTo(VIEWS.LOG_STREAMING_SETTINGS);
|
|
||||||
break;
|
|
||||||
case 'users': // Fakedoor feature added via hooks when user management is disabled on cloud
|
case 'users': // Fakedoor feature added via hooks when user management is disabled on cloud
|
||||||
case 'logging':
|
case 'logging':
|
||||||
this.$router.push({ name: VIEWS.FAKE_DOOR, params: { featureId: key } }).catch(() => {});
|
this.$router.push({ name: VIEWS.FAKE_DOOR, params: { featureId: key } }).catch(() => {});
|
||||||
break;
|
break;
|
||||||
case 'settings-community-nodes':
|
|
||||||
await this.navigateTo(VIEWS.COMMUNITY_NODES);
|
|
||||||
break;
|
|
||||||
case 'settings-usage-and-plan':
|
|
||||||
await this.navigateTo(VIEWS.USAGE);
|
|
||||||
break;
|
|
||||||
case 'settings-sso':
|
|
||||||
await this.navigateTo(VIEWS.SSO_SETTINGS);
|
|
||||||
break;
|
|
||||||
case 'settings-external-secrets':
|
|
||||||
await this.navigateTo(VIEWS.EXTERNAL_SECRETS_SETTINGS);
|
|
||||||
break;
|
|
||||||
case 'settings-source-control':
|
|
||||||
if (this.$router.currentRoute.name !== VIEWS.SOURCE_CONTROL) {
|
|
||||||
void this.$router.push({ name: VIEWS.SOURCE_CONTROL });
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'settings-audit-logs':
|
|
||||||
if (this.$router.currentRoute.name !== VIEWS.AUDIT_LOGS) {
|
|
||||||
void this.$router.push({ name: VIEWS.AUDIT_LOGS });
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'settings-workersview': {
|
|
||||||
await this.navigateTo(VIEWS.WORKER_VIEW);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ vi.mock('vue-router', () => ({
|
||||||
useRoute: vi.fn().mockReturnValue({
|
useRoute: vi.fn().mockReturnValue({
|
||||||
name: VIEWS.WORKFLOW_EXECUTIONS,
|
name: VIEWS.WORKFLOW_EXECUTIONS,
|
||||||
}),
|
}),
|
||||||
|
RouterLink: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let pinia: ReturnType<typeof createTestingPinia>;
|
let pinia: ReturnType<typeof createTestingPinia>;
|
||||||
|
|
|
@ -9,6 +9,7 @@ vi.mock('vue-router', () => ({
|
||||||
path: '/workflows',
|
path: '/workflows',
|
||||||
params: {},
|
params: {},
|
||||||
})),
|
})),
|
||||||
|
RouterLink: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('@/stores/rbac.store', () => ({
|
vi.mock('@/stores/rbac.store', () => ({
|
||||||
|
|
|
@ -31,6 +31,7 @@ vi.mock('@/stores/ui.store', () => {
|
||||||
});
|
});
|
||||||
vi.mock('vue-router', () => ({
|
vi.mock('vue-router', () => ({
|
||||||
useRoute: () => ({}),
|
useRoute: () => ({}),
|
||||||
|
RouterLink: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const TestComponent = defineComponent({
|
const TestComponent = defineComponent({
|
||||||
|
|
|
@ -7,7 +7,7 @@ import type {
|
||||||
RouteLocationRaw,
|
RouteLocationRaw,
|
||||||
RouteLocationNormalized,
|
RouteLocationNormalized,
|
||||||
} from 'vue-router';
|
} from 'vue-router';
|
||||||
import { createRouter, createWebHistory } from 'vue-router';
|
import { createRouter, createWebHistory, isNavigationFailure } from 'vue-router';
|
||||||
import { useExternalHooks } from '@/composables/useExternalHooks';
|
import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import { useTemplatesStore } from '@/stores/templates.store';
|
import { useTemplatesStore } from '@/stores/templates.store';
|
||||||
|
@ -784,6 +784,7 @@ const router = createRouter({
|
||||||
});
|
});
|
||||||
|
|
||||||
router.beforeEach(async (to: RouteLocationNormalized & RouteConfig, from, next) => {
|
router.beforeEach(async (to: RouteLocationNormalized & RouteConfig, from, next) => {
|
||||||
|
try {
|
||||||
/**
|
/**
|
||||||
* Initialize application core
|
* Initialize application core
|
||||||
* This step executes before first route is loaded and is required for permission checks
|
* This step executes before first route is loaded and is required for permission checks
|
||||||
|
@ -827,9 +828,17 @@ router.beforeEach(async (to: RouteLocationNormalized & RouteConfig, from, next)
|
||||||
}
|
}
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
|
} catch (failure) {
|
||||||
|
if (isNavigationFailure(failure)) {
|
||||||
|
console.log(failure);
|
||||||
|
} else {
|
||||||
|
console.error(failure);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.afterEach((to, from) => {
|
router.afterEach((to, from) => {
|
||||||
|
try {
|
||||||
const telemetry = useTelemetry();
|
const telemetry = useTelemetry();
|
||||||
const uiStore = useUIStore();
|
const uiStore = useUIStore();
|
||||||
const templatesStore = useTemplatesStore();
|
const templatesStore = useTemplatesStore();
|
||||||
|
@ -851,6 +860,13 @@ router.afterEach((to, from) => {
|
||||||
templatesStore.resetSessionId(); // reset telemetry session id when user leaves template pages
|
templatesStore.resetSessionId(); // reset telemetry session id when user leaves template pages
|
||||||
}
|
}
|
||||||
telemetry.page(to);
|
telemetry.page(to);
|
||||||
|
} catch (failure) {
|
||||||
|
if (isNavigationFailure(failure)) {
|
||||||
|
console.log(failure);
|
||||||
|
} else {
|
||||||
|
console.error(failure);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
@ -44,7 +44,6 @@ import type {
|
||||||
CloudUpdateLinkSourceType,
|
CloudUpdateLinkSourceType,
|
||||||
CurlToJSONResponse,
|
CurlToJSONResponse,
|
||||||
IFakeDoorLocation,
|
IFakeDoorLocation,
|
||||||
IMenuItem,
|
|
||||||
INodeUi,
|
INodeUi,
|
||||||
IOnboardingCallPrompt,
|
IOnboardingCallPrompt,
|
||||||
IUser,
|
IUser,
|
||||||
|
@ -176,7 +175,6 @@ export const useUIStore = defineStore(STORES.UI, {
|
||||||
nodeViewOffsetPosition: [0, 0],
|
nodeViewOffsetPosition: [0, 0],
|
||||||
nodeViewMoveInProgress: false,
|
nodeViewMoveInProgress: false,
|
||||||
selectedNodes: [],
|
selectedNodes: [],
|
||||||
sidebarMenuItems: [],
|
|
||||||
nodeViewInitialized: false,
|
nodeViewInitialized: false,
|
||||||
addFirstStepOnLoad: false,
|
addFirstStepOnLoad: false,
|
||||||
executionSidebarAutoRefresh: true,
|
executionSidebarAutoRefresh: true,
|
||||||
|
@ -528,10 +526,6 @@ export const useUIStore = defineStore(STORES.UI, {
|
||||||
resetSelectedNodes(): void {
|
resetSelectedNodes(): void {
|
||||||
this.selectedNodes = [];
|
this.selectedNodes = [];
|
||||||
},
|
},
|
||||||
addSidebarMenuItems(menuItems: IMenuItem[]) {
|
|
||||||
const updated = this.sidebarMenuItems.concat(menuItems);
|
|
||||||
this.sidebarMenuItems = updated;
|
|
||||||
},
|
|
||||||
setCurlCommand(payload: { name: string; command: string }): void {
|
setCurlCommand(payload: { name: string; command: string }): void {
|
||||||
this.modals[payload.name] = {
|
this.modals[payload.name] = {
|
||||||
...this.modals[payload.name],
|
...this.modals[payload.name],
|
||||||
|
|
|
@ -14,6 +14,7 @@ vi.mock('vue-router', () => {
|
||||||
useRouter: () => ({
|
useRouter: () => ({
|
||||||
push,
|
push,
|
||||||
}),
|
}),
|
||||||
|
RouterLink: vi.fn(),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ vi.mock('vue-router', () => {
|
||||||
replace,
|
replace,
|
||||||
resolve,
|
resolve,
|
||||||
}),
|
}),
|
||||||
|
RouterLink: vi.fn(),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -867,6 +867,9 @@ importers:
|
||||||
vue-boring-avatars:
|
vue-boring-avatars:
|
||||||
specifier: ^1.3.0
|
specifier: ^1.3.0
|
||||||
version: 1.3.0(vue@3.3.4)
|
version: 1.3.0(vue@3.3.4)
|
||||||
|
vue-router:
|
||||||
|
specifier: ^4.2.2
|
||||||
|
version: 4.2.2(vue@3.3.4)
|
||||||
xss:
|
xss:
|
||||||
specifier: ^1.0.14
|
specifier: ^1.0.14
|
||||||
version: 1.0.14
|
version: 1.0.14
|
||||||
|
|
Loading…
Reference in a new issue