fix(editor): Use web native <a> element in nav menus (#8385)
This commit is contained in:
@@ -118,7 +118,6 @@ import { useUIStore } from '@/stores/ui.store';
|
||||
import { useUsersStore } from '@/stores/users.store';
|
||||
import { useVersionsStore } from '@/stores/versions.store';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { isNavigationFailure } from 'vue-router';
|
||||
import ExecutionsUsage from '@/components/ExecutionsUsage.vue';
|
||||
import BecomeTemplateCreatorCta from '@/components/BecomeTemplateCreatorCta/BecomeTemplateCreatorCta.vue';
|
||||
import MainSidebarSourceControl from '@/components/MainSidebarSourceControl.vue';
|
||||
@@ -205,38 +204,24 @@ export default defineComponent({
|
||||
},
|
||||
mainMenuItems(): IMenuItem[] {
|
||||
const items: IMenuItem[] = [];
|
||||
const injectedItems = this.uiStore.sidebarMenuItems;
|
||||
|
||||
const workflows: IMenuItem = {
|
||||
id: 'workflows',
|
||||
icon: 'network-wired',
|
||||
label: this.$locale.baseText('mainSidebar.workflows'),
|
||||
position: 'top',
|
||||
activateOnRouteNames: [VIEWS.WORKFLOWS],
|
||||
route: { to: { name: VIEWS.WORKFLOWS } },
|
||||
secondaryIcon: this.sourceControlStore.preferences.branchReadOnly
|
||||
? {
|
||||
name: 'lock',
|
||||
tooltip: {
|
||||
content: this.$locale.baseText('mainSidebar.workflows.readOnlyEnv.tooltip'),
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
|
||||
if (this.sourceControlStore.preferences.branchReadOnly) {
|
||||
workflows.secondaryIcon = {
|
||||
name: 'lock',
|
||||
tooltip: {
|
||||
content: this.$locale.baseText('mainSidebar.workflows.readOnlyEnv.tooltip'),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
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[] = [
|
||||
workflows,
|
||||
{
|
||||
@@ -245,7 +230,7 @@ export default defineComponent({
|
||||
label: this.$locale.baseText('mainSidebar.templates'),
|
||||
position: 'top',
|
||||
available: this.settingsStore.isTemplatesEnabled,
|
||||
activateOnRouteNames: [VIEWS.TEMPLATES],
|
||||
route: { to: { name: VIEWS.TEMPLATES } },
|
||||
},
|
||||
{
|
||||
id: 'credentials',
|
||||
@@ -253,7 +238,7 @@ export default defineComponent({
|
||||
label: this.$locale.baseText('mainSidebar.credentials'),
|
||||
customIconSize: 'medium',
|
||||
position: 'top',
|
||||
activateOnRouteNames: [VIEWS.CREDENTIALS],
|
||||
route: { to: { name: VIEWS.CREDENTIALS } },
|
||||
},
|
||||
{
|
||||
id: 'variables',
|
||||
@@ -261,18 +246,17 @@ export default defineComponent({
|
||||
label: this.$locale.baseText('mainSidebar.variables'),
|
||||
customIconSize: 'medium',
|
||||
position: 'top',
|
||||
activateOnRouteNames: [VIEWS.VARIABLES],
|
||||
route: { to: { name: VIEWS.VARIABLES } },
|
||||
},
|
||||
{
|
||||
id: 'executions',
|
||||
icon: 'tasks',
|
||||
label: this.$locale.baseText('mainSidebar.executions'),
|
||||
position: 'top',
|
||||
activateOnRouteNames: [VIEWS.EXECUTIONS],
|
||||
route: { to: { name: VIEWS.EXECUTIONS } },
|
||||
},
|
||||
{
|
||||
id: 'cloud-admin',
|
||||
type: 'link',
|
||||
position: 'bottom',
|
||||
label: 'Admin Panel',
|
||||
icon: 'home',
|
||||
@@ -285,6 +269,7 @@ export default defineComponent({
|
||||
position: 'bottom',
|
||||
available: this.canUserAccessSettings && this.usersStore.currentUser !== null,
|
||||
activateOnRouteNames: [VIEWS.USERS_SETTINGS, VIEWS.API_SETTINGS, VIEWS.PERSONAL_SETTINGS],
|
||||
route: { to: defaultSettingsRoute },
|
||||
},
|
||||
{
|
||||
id: 'help',
|
||||
@@ -296,40 +281,36 @@ export default defineComponent({
|
||||
id: 'quickstart',
|
||||
icon: 'video',
|
||||
label: this.$locale.baseText('mainSidebar.helpMenuItems.quickstart'),
|
||||
type: 'link',
|
||||
properties: {
|
||||
link: {
|
||||
href: 'https://www.youtube.com/watch?v=1MwSoB0gnM4',
|
||||
newWindow: true,
|
||||
target: '_blank',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'docs',
|
||||
icon: 'book',
|
||||
label: this.$locale.baseText('mainSidebar.helpMenuItems.documentation'),
|
||||
type: 'link',
|
||||
properties: {
|
||||
link: {
|
||||
href: 'https://docs.n8n.io?utm_source=n8n_app&utm_medium=app_sidebar',
|
||||
newWindow: true,
|
||||
target: '_blank',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'forum',
|
||||
icon: 'users',
|
||||
label: this.$locale.baseText('mainSidebar.helpMenuItems.forum'),
|
||||
type: 'link',
|
||||
properties: {
|
||||
link: {
|
||||
href: 'https://community.n8n.io?utm_source=n8n_app&utm_medium=app_sidebar',
|
||||
newWindow: true,
|
||||
target: '_blank',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'examples',
|
||||
icon: 'graduation-cap',
|
||||
label: this.$locale.baseText('mainSidebar.helpMenuItems.course'),
|
||||
type: 'link',
|
||||
properties: {
|
||||
link: {
|
||||
href: 'https://www.youtube.com/watch?v=1MwSoB0gnM4',
|
||||
newWindow: true,
|
||||
target: '_blank',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -421,46 +402,6 @@ export default defineComponent({
|
||||
},
|
||||
async handleSelect(key: string) {
|
||||
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': {
|
||||
this.trackHelpItemClick('about');
|
||||
this.uiStore.openModal(ABOUT_MODAL_KEY);
|
||||
@@ -481,25 +422,18 @@ export default defineComponent({
|
||||
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() {
|
||||
const settingsRoutes = this.$router
|
||||
.getRoutes()
|
||||
.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) {
|
||||
if (this.canUserAccessRouteByName(route.toString())) {
|
||||
defaultSettingsRoute = route;
|
||||
defaultSettingsRoute = {
|
||||
name: route.toString() as VIEWS,
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ export default defineComponent({
|
||||
label: this.$locale.baseText('settings.usageAndPlan.title'),
|
||||
position: 'top',
|
||||
available: this.canAccessUsageAndPlan(),
|
||||
activateOnRouteNames: [VIEWS.USAGE],
|
||||
route: { to: { name: VIEWS.USAGE } },
|
||||
},
|
||||
{
|
||||
id: 'settings-personal',
|
||||
@@ -57,7 +57,7 @@ export default defineComponent({
|
||||
label: this.$locale.baseText('settings.personal'),
|
||||
position: 'top',
|
||||
available: this.canAccessPersonalSettings(),
|
||||
activateOnRouteNames: [VIEWS.PERSONAL_SETTINGS],
|
||||
route: { to: { name: VIEWS.PERSONAL_SETTINGS } },
|
||||
},
|
||||
{
|
||||
id: 'settings-users',
|
||||
@@ -65,7 +65,7 @@ export default defineComponent({
|
||||
label: this.$locale.baseText('settings.users'),
|
||||
position: 'top',
|
||||
available: this.canAccessUsersSettings(),
|
||||
activateOnRouteNames: [VIEWS.USERS_SETTINGS],
|
||||
route: { to: { name: VIEWS.USERS_SETTINGS } },
|
||||
},
|
||||
{
|
||||
id: 'settings-api',
|
||||
@@ -73,7 +73,7 @@ export default defineComponent({
|
||||
label: this.$locale.baseText('settings.n8napi'),
|
||||
position: 'top',
|
||||
available: this.canAccessApiSettings(),
|
||||
activateOnRouteNames: [VIEWS.API_SETTINGS],
|
||||
route: { to: { name: VIEWS.API_SETTINGS } },
|
||||
},
|
||||
{
|
||||
id: 'settings-external-secrets',
|
||||
@@ -81,10 +81,7 @@ export default defineComponent({
|
||||
label: this.$locale.baseText('settings.externalSecrets.title'),
|
||||
position: 'top',
|
||||
available: this.canAccessExternalSecrets(),
|
||||
activateOnRouteNames: [
|
||||
VIEWS.EXTERNAL_SECRETS_SETTINGS,
|
||||
VIEWS.EXTERNAL_SECRETS_PROVIDER_SETTINGS,
|
||||
],
|
||||
route: { to: { name: VIEWS.EXTERNAL_SECRETS_SETTINGS } },
|
||||
},
|
||||
{
|
||||
id: 'settings-audit-logs',
|
||||
@@ -92,7 +89,7 @@ export default defineComponent({
|
||||
label: this.$locale.baseText('settings.auditLogs.title'),
|
||||
position: 'top',
|
||||
available: this.canAccessAuditLogs(),
|
||||
activateOnRouteNames: [VIEWS.AUDIT_LOGS],
|
||||
route: { to: { name: VIEWS.AUDIT_LOGS } },
|
||||
},
|
||||
{
|
||||
id: 'settings-source-control',
|
||||
@@ -100,7 +97,7 @@ export default defineComponent({
|
||||
label: this.$locale.baseText('settings.sourceControl.title'),
|
||||
position: 'top',
|
||||
available: this.canAccessSourceControl(),
|
||||
activateOnRouteNames: [VIEWS.SOURCE_CONTROL],
|
||||
route: { to: { name: VIEWS.SOURCE_CONTROL } },
|
||||
},
|
||||
{
|
||||
id: 'settings-sso',
|
||||
@@ -108,7 +105,7 @@ export default defineComponent({
|
||||
label: this.$locale.baseText('settings.sso'),
|
||||
position: 'top',
|
||||
available: this.canAccessSso(),
|
||||
activateOnRouteNames: [VIEWS.SSO_SETTINGS],
|
||||
route: { to: { name: VIEWS.SSO_SETTINGS } },
|
||||
},
|
||||
{
|
||||
id: 'settings-ldap',
|
||||
@@ -116,7 +113,7 @@ export default defineComponent({
|
||||
label: this.$locale.baseText('settings.ldap'),
|
||||
position: 'top',
|
||||
available: this.canAccessLdapSettings(),
|
||||
activateOnRouteNames: [VIEWS.LDAP_SETTINGS],
|
||||
route: { to: { name: VIEWS.LDAP_SETTINGS } },
|
||||
},
|
||||
{
|
||||
id: 'settings-workersview',
|
||||
@@ -126,7 +123,7 @@ export default defineComponent({
|
||||
available:
|
||||
this.settingsStore.isQueueModeEnabled &&
|
||||
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')) {
|
||||
menuItems.push({
|
||||
id: item.id,
|
||||
icon: item.icon || 'question',
|
||||
icon: item.icon ?? 'question',
|
||||
label: this.$locale.baseText(item.featureName as BaseTextKey),
|
||||
position: 'top',
|
||||
available: true,
|
||||
@@ -149,7 +146,7 @@ export default defineComponent({
|
||||
label: this.$locale.baseText('settings.log-streaming'),
|
||||
position: 'top',
|
||||
available: this.canAccessLogStreamingSettings(),
|
||||
activateOnRouteNames: [VIEWS.LOG_STREAMING_SETTINGS],
|
||||
route: { to: { name: VIEWS.LOG_STREAMING_SETTINGS } },
|
||||
});
|
||||
|
||||
menuItems.push({
|
||||
@@ -158,7 +155,7 @@ export default defineComponent({
|
||||
label: this.$locale.baseText('settings.communityNodes'),
|
||||
position: 'top',
|
||||
available: this.canAccessCommunityNodes(),
|
||||
activateOnRouteNames: [VIEWS.COMMUNITY_NODES],
|
||||
route: { to: { name: VIEWS.COMMUNITY_NODES } },
|
||||
});
|
||||
|
||||
return menuItems;
|
||||
@@ -211,51 +208,10 @@ export default defineComponent({
|
||||
},
|
||||
async handleSelect(key: string) {
|
||||
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 'logging':
|
||||
this.$router.push({ name: VIEWS.FAKE_DOOR, params: { featureId: key } }).catch(() => {});
|
||||
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:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ vi.mock('vue-router', () => ({
|
||||
useRoute: vi.fn().mockReturnValue({
|
||||
name: VIEWS.WORKFLOW_EXECUTIONS,
|
||||
}),
|
||||
RouterLink: vi.fn(),
|
||||
}));
|
||||
|
||||
let pinia: ReturnType<typeof createTestingPinia>;
|
||||
|
||||
@@ -9,6 +9,7 @@ vi.mock('vue-router', () => ({
|
||||
path: '/workflows',
|
||||
params: {},
|
||||
})),
|
||||
RouterLink: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@/stores/rbac.store', () => ({
|
||||
|
||||
Reference in New Issue
Block a user