From aa0e8a4a63ec53327fc6378096f2019e5d3b820a Mon Sep 17 00:00:00 2001 From: Lukas Gross Date: Thu, 22 Aug 2024 15:30:01 +0200 Subject: [PATCH 1/3] - Add navigation Guard if route updates - Reset namespace if navigation failed --- frontend/src/router/index.js | 6 ++++++ frontend/src/views/GNewShoot.vue | 7 +++++++ frontend/src/views/GNewShootEditor.vue | 14 ++++++++++++-- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index dfafdc0c7a..d8c75d1551 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -12,6 +12,7 @@ import { } from 'vue-router' import { useAppStore } from '@/store/app' +import { useAuthzStore } from '@/store/authz' import { useLogger } from '@/composables/useLogger' @@ -26,6 +27,7 @@ const zeroPoint = { left: 0, top: 0 } export function createRouter () { const logger = useLogger() const appStore = useAppStore() + const authzStore = useAuthzStore() const router = createVueRouter({ history: createWebHistory(import.meta.env.BASE_URL), @@ -52,6 +54,10 @@ export function createRouter () { } else { logger.info('Navigation failure: %s', failure) } + + // Reset namespace if navigation failed + const namespace = from.params.namespace ?? from.query.namespace + authzStore.fetchRules(namespace) } }) diff --git a/frontend/src/views/GNewShoot.vue b/frontend/src/views/GNewShoot.vue index 68ce225786..bba50fc9bb 100644 --- a/frontend/src/views/GNewShoot.vue +++ b/frontend/src/views/GNewShoot.vue @@ -192,6 +192,13 @@ export default { return next() }, + async beforeRouteUpdate (to, from, next) { + if (!this.isShootCreated && this.isShootDirty && !await this.confirmNavigation()) { + return next(false) + } + + return next() + }, setup () { const { shootNamespace, diff --git a/frontend/src/views/GNewShootEditor.vue b/frontend/src/views/GNewShootEditor.vue index 3a91c6d456..bdcfc66c87 100644 --- a/frontend/src/views/GNewShootEditor.vue +++ b/frontend/src/views/GNewShootEditor.vue @@ -42,6 +42,7 @@ import { import { useRouter, onBeforeRouteLeave, + onBeforeRouteUpdate, } from 'vue-router' import { useAppStore } from '@/store/app' @@ -72,7 +73,6 @@ const appStore = useAppStore() const { shootNamespace, - isShootDirty, shootManifest, setShootManifest, } = useShootContext() @@ -84,6 +84,7 @@ const useProvide = (key, value) => { const { getEditorValue, focusEditor, + clean, } = useProvide(injectionKey, useShootEditor(shootManifest)) function confirmEditorNavigation () { @@ -134,7 +135,16 @@ onBeforeRouteLeave(async (to, from, next) => { if (isShootCreated.value) { return next() } - if (isShootDirty.value) { + if (!clean.value) { + if (!await confirmEditorNavigation()) { + focusEditor() + return next(false) + } + } + return next() +}) +onBeforeRouteUpdate(async (to, from, next) => { + if (!clean.value) { if (!await confirmEditorNavigation()) { focusEditor() return next(false) From 4f0aa23d18a0b15dd691dfd1882e03785c5baa4d Mon Sep 17 00:00:00 2001 From: Holger Koser Date: Thu, 17 Oct 2024 11:03:07 +0200 Subject: [PATCH 2/3] move data loading to resolve guard --- frontend/src/plugins/router.js | 2 ++ frontend/src/router/guards.js | 59 +++++++++++++++++++++++----------- frontend/src/router/index.js | 14 ++++---- 3 files changed, 50 insertions(+), 25 deletions(-) diff --git a/frontend/src/plugins/router.js b/frontend/src/plugins/router.js index ccb479d174..7814616eb7 100644 --- a/frontend/src/plugins/router.js +++ b/frontend/src/plugins/router.js @@ -7,6 +7,7 @@ import { createRouter, registerGlobalBeforeGuards, + registerGlobalResolveGuards, registerGlobalAfterHooks, } from '@/router' @@ -19,6 +20,7 @@ export default { // must be done after the router plugin has been installed // because some stores use the router or route registerGlobalBeforeGuards(router) + registerGlobalResolveGuards(router) registerGlobalAfterHooks(router) }, } diff --git a/frontend/src/router/guards.js b/frontend/src/router/guards.js index 0e4e6ea70b..9d108255a6 100644 --- a/frontend/src/router/guards.js +++ b/frontend/src/router/guards.js @@ -24,17 +24,12 @@ export function createGlobalBeforeGuards () { const logger = useLogger() const appStore = useAppStore() const authnStore = useAuthnStore() - const authzStore = useAuthzStore() const configStore = useConfigStore() const projectStore = useProjectStore() const cloudProfileStore = useCloudProfileStore() const seedStore = useSeedStore() const gardenerExtensionStore = useGardenerExtensionStore() const kubeconfigStore = useKubeconfigStore() - const memberStore = useMemberStore() - const secretStore = useSecretStore() - const shootStore = useShootStore() - const terminalStore = useTerminalStore() function ensureUserAuthenticatedForNonPublicRoutes () { return to => { @@ -68,16 +63,13 @@ export function createGlobalBeforeGuards () { } } - function ensureDataLoaded () { - return async (to, from, next) => { - const { meta = {} } = to - if (meta.public) { - shootStore.unsubscribeShoots() - return next() + function ensureCommonDataLoaded () { + return async to => { + if (to.meta?.public) { + return } try { - const namespace = to.params.namespace ?? to.query.namespace await Promise.all([ ensureConfigLoaded(configStore), ensureProjectsLoaded(projectStore), @@ -85,8 +77,43 @@ export function createGlobalBeforeGuards () { ensureSeedsLoaded(seedStore), ensureGardenerExtensionsLoaded(gardenerExtensionStore), ensureKubeconfigLoaded(kubeconfigStore), - refreshRules(authzStore, namespace), ]) + } catch (err) { + appStore.setRouterError(err) + } + } + } + + return [ + (to, from) => { + appStore.loading = true + }, + ensureUserAuthenticatedForNonPublicRoutes(), + ensureCommonDataLoaded(), + ] +} + +export function createGlobalResolveGuards () { + const logger = useLogger() + const appStore = useAppStore() + const authnStore = useAuthnStore() + const authzStore = useAuthzStore() + const projectStore = useProjectStore() + const memberStore = useMemberStore() + const secretStore = useSecretStore() + const shootStore = useShootStore() + const terminalStore = useTerminalStore() + + function ensureDataLoaded () { + return async to => { + if (to.meta?.public) { + shootStore.unsubscribeShoots() + return + } + + try { + const namespace = to.params.namespace ?? to.query.namespace + await refreshRules(authzStore, namespace) if (namespace && namespace !== '_all' && !projectStore.namespaces.includes(namespace)) { authzStore.$reset() @@ -147,17 +174,11 @@ export function createGlobalBeforeGuards () { } } catch (err) { appStore.setRouterError(err) - } finally { - next() } } } return [ - (to, from) => { - appStore.loading = true - }, - ensureUserAuthenticatedForNonPublicRoutes(), ensureDataLoaded(), ] } diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index d8c75d1551..52c5257c53 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -12,13 +12,13 @@ import { } from 'vue-router' import { useAppStore } from '@/store/app' -import { useAuthzStore } from '@/store/authz' import { useLogger } from '@/composables/useLogger' import { createRoutes } from './routes' import { createGlobalBeforeGuards, + createGlobalResolveGuards, createGlobalAfterHooks, } from './guards' @@ -27,7 +27,6 @@ const zeroPoint = { left: 0, top: 0 } export function createRouter () { const logger = useLogger() const appStore = useAppStore() - const authzStore = useAuthzStore() const router = createVueRouter({ history: createWebHistory(import.meta.env.BASE_URL), @@ -54,10 +53,6 @@ export function createRouter () { } else { logger.info('Navigation failure: %s', failure) } - - // Reset namespace if navigation failed - const namespace = from.params.namespace ?? from.query.namespace - authzStore.fetchRules(namespace) } }) @@ -71,6 +66,13 @@ export function registerGlobalBeforeGuards (router) { } } +export function registerGlobalResolveGuards (router) { + const guards = createGlobalResolveGuards() + for (const guard of guards) { + router.beforeEach(guard) + } +} + export function registerGlobalAfterHooks (router) { const hooks = createGlobalAfterHooks() for (const hook of hooks) { From e48f93e0dbc85b99c5907fc64ff517f2a1cb0ae7 Mon Sep 17 00:00:00 2001 From: "Gross, Lukas" Date: Thu, 17 Oct 2024 16:37:12 +0200 Subject: [PATCH 3/3] - Show navigation confirmation popup also when shoot has been modified on overview page before navigating to shoot editor - Fallback to shoot list when switching the project on create cluster page --- frontend/src/components/GMainNavigation.vue | 3 +++ frontend/src/views/GNewShootEditor.vue | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/GMainNavigation.vue b/frontend/src/components/GMainNavigation.vue index cffc8ac2f4..dc7079a160 100644 --- a/frontend/src/components/GMainNavigation.vue +++ b/frontend/src/components/GMainNavigation.vue @@ -440,6 +440,9 @@ function getProjectMenuTargetRoute (namespace) { if (has(route, 'params.name')) { return true } + if (get(route, 'name') === 'NewShoot' || get(route, 'name') === 'NewShootEditor') { + return true + } if (get(route, 'name') === 'GardenTerminal') { return true } diff --git a/frontend/src/views/GNewShootEditor.vue b/frontend/src/views/GNewShootEditor.vue index bdcfc66c87..fd672a2053 100644 --- a/frontend/src/views/GNewShootEditor.vue +++ b/frontend/src/views/GNewShootEditor.vue @@ -75,6 +75,7 @@ const { shootNamespace, shootManifest, setShootManifest, + isShootDirty, } = useShootContext() const useProvide = (key, value) => { @@ -135,7 +136,7 @@ onBeforeRouteLeave(async (to, from, next) => { if (isShootCreated.value) { return next() } - if (!clean.value) { + if (!clean.value || isShootDirty.value) { if (!await confirmEditorNavigation()) { focusEditor() return next(false) @@ -144,7 +145,7 @@ onBeforeRouteLeave(async (to, from, next) => { return next() }) onBeforeRouteUpdate(async (to, from, next) => { - if (!clean.value) { + if (!clean.value || isShootDirty.value) { if (!await confirmEditorNavigation()) { focusEditor() return next(false)