From 8754a10bd26d6c23e1395bbc38e07b73e9ed6bce Mon Sep 17 00:00:00 2001 From: Peter Sutter Date: Tue, 10 Dec 2024 14:27:31 +0100 Subject: [PATCH] [openMFP] Move luigiContext and isInIframe to own composables (#2223) * move luigiContext and isInIframe to own composables * rename to useOpenMFP * PR feedback * PR feedback II --- frontend/src/App.vue | 4 - .../src/components/dialogs/GProjectDialog.vue | 6 +- frontend/src/composables/useIsInIframe.js | 12 +++ frontend/src/composables/useOpenMFP.js | 77 +++++++++++++++++++ frontend/src/composables/useProjectContext.js | 15 ++-- frontend/src/layouts/GDefault.vue | 4 +- frontend/src/router/guards.js | 4 +- frontend/src/store/app.js | 71 +---------------- frontend/src/views/GAdministration.vue | 11 ++- frontend/src/views/GProjectList.vue | 9 ++- frontend/src/views/GShootList.vue | 5 +- 11 files changed, 120 insertions(+), 98 deletions(-) create mode 100644 frontend/src/composables/useIsInIframe.js create mode 100644 frontend/src/composables/useOpenMFP.js diff --git a/frontend/src/App.vue b/frontend/src/App.vue index e50ae10ec0..5265270427 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -31,7 +31,6 @@ import { useLoginStore } from '@/store/login' import { useLocalStorageStore } from '@/store/localStorage' import { useShootStore } from '@/store/shoot' import { useProjectStore } from '@/store/project' -import { useAppStore } from '@/store/app' import { useCustomColors } from '@/composables/useCustomColors' @@ -45,11 +44,8 @@ const configStore = useConfigStore() const loginStore = useLoginStore() const shootStore = useShootStore() const projectStore = useProjectStore() -const appStore = useAppStore() const logger = inject('logger') -appStore.setRoute(route) - async function setCustomColors () { try { await useCustomColors(() => configStore.themes ?? loginStore.themes ?? null, theme) diff --git a/frontend/src/components/dialogs/GProjectDialog.vue b/frontend/src/components/dialogs/GProjectDialog.vue index f96c94ce71..f7981e04fd 100644 --- a/frontend/src/components/dialogs/GProjectDialog.vue +++ b/frontend/src/components/dialogs/GProjectDialog.vue @@ -105,7 +105,6 @@ import { } from '@vuelidate/validators' import { useRouter } from 'vue-router' -import { useAppStore } from '@/store/app' import { useConfigStore } from '@/store/config' import { useProjectStore } from '@/store/project' @@ -115,6 +114,7 @@ import GProjectCostObject from '@/components/GProjectCostObject.vue' import { useLogger } from '@/composables/useLogger' import { useProvideProjectContext } from '@/composables/useProjectContext' +import { useOpenMFP } from '@/composables/useOpenMFP' import { messageFromErrors, @@ -146,7 +146,7 @@ const emit = defineEmits([ ]) const logger = useLogger() -const appStore = useAppStore() +const openMFP = useOpenMFP() const configStore = useConfigStore() const projectStore = useProjectStore() const router = useRouter() @@ -157,7 +157,7 @@ const { description, purpose, } = useProvideProjectContext({ - appStore, + openMFP, configStore, }) diff --git a/frontend/src/composables/useIsInIframe.js b/frontend/src/composables/useIsInIframe.js new file mode 100644 index 0000000000..f5da3b9558 --- /dev/null +++ b/frontend/src/composables/useIsInIframe.js @@ -0,0 +1,12 @@ +// +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors +// +// SPDX-License-Identifier: Apache-2.0 +// + +import { computed } from 'vue' +import { createGlobalState } from '@vueuse/core' + +export const useIsInIframe = createGlobalState(() => { + return computed(() => window.self !== window.top) +}) diff --git a/frontend/src/composables/useOpenMFP.js b/frontend/src/composables/useOpenMFP.js new file mode 100644 index 0000000000..56277aff24 --- /dev/null +++ b/frontend/src/composables/useOpenMFP.js @@ -0,0 +1,77 @@ +// +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors +// +// SPDX-License-Identifier: Apache-2.0 +// + +import { useRoute } from 'vue-router' +import { + until, + createGlobalState, +} from '@vueuse/core' +import LuigiClient from '@luigi-project/client' +import { + computed, + ref, + toRef, + watch, +} from 'vue' + +import { useLogger } from '@/composables/useLogger' +import { useIsInIframe } from '@/composables/useIsInIframe' + +export const useOpenMFP = createGlobalState((options = {}) => { + const { + logger = useLogger(), + isInIframe = useIsInIframe(), + route = useRoute(), + } = options + + const luigiContext = ref(null) + + if (isInIframe.value) { + logger.debug('Registering listener for Luigi context initialization and context updates') + LuigiClient.addInitListener(context => setLuigiContext(context)) + LuigiClient.addContextUpdateListener(context => setLuigiContext(context)) + const pathname = toRef(route, 'path') + watch(pathname, value => { + if (value) { + LuigiClient.linkManager().fromVirtualTreeRoot().withoutSync().navigate(value) + } + }, { + immediate: true, + }) + } + + function setLuigiContext (value) { + luigiContext.value = value + } + + const accountId = computed(() => luigiContext.value?.accountId) + + async function getLuigiContext () { + if (!isInIframe.value) { + return null + } + if (luigiContext.value !== null) { + return luigiContext.value + } + const timeout = 3000 + try { + await until(luigiContext).toBeTruthy({ + timeout, + throwOnTimeout: true, + }) + return luigiContext.value + } catch (err) { + logger.error('The initialization of the Luigi Client has timed out after %d milliseconds', timeout) + return null + } + } + + return { + accountId, + luigiContext, + getLuigiContext, + } +}) diff --git a/frontend/src/composables/useProjectContext.js b/frontend/src/composables/useProjectContext.js index e63651fedc..72089fa428 100644 --- a/frontend/src/composables/useProjectContext.js +++ b/frontend/src/composables/useProjectContext.js @@ -12,14 +12,13 @@ import { provide, } from 'vue' -import { useAppStore } from '@/store/app' import { useConfigStore } from '@/store/config' import { cleanup } from '@/composables/helper' - -import { useProjectShootCustomFields } from './useProjectShootCustomFields' -import { useProjectMetadata } from './useProjectMetadata' -import { useProjectCostObject } from './useProjectCostObject' +import { useOpenMFP } from '@/composables/useOpenMFP' +import { useProjectShootCustomFields } from '@/composables/useProjectShootCustomFields' +import { useProjectMetadata } from '@/composables/useProjectMetadata' +import { useProjectCostObject } from '@/composables/useProjectCostObject' import cloneDeep from 'lodash/cloneDeep' import get from 'lodash/get' @@ -28,7 +27,7 @@ import set from 'lodash/set' export function createProjectContextComposable (options = {}) { const { - appStore = useAppStore(), + openMFP = useOpenMFP(), configStore = useConfigStore(), } = options @@ -63,9 +62,9 @@ export function createProjectContextComposable (options = {}) { function createProjectManifest () { manifest.value = {} - if (appStore.accountId) { + if (openMFP.accountId) { set(manifest.value, ['metadata', 'label', 'openmfp.org/managed-by'], 'true') - set(manifest.value, ['metadata', 'annotations', 'openmfp.org/account-id'], appStore.accountId) + set(manifest.value, ['metadata', 'annotations', 'openmfp.org/account-id'], openMFP.accountId) } initialManifest.value = cloneDeep(normalizedManifest.value) } diff --git a/frontend/src/layouts/GDefault.vue b/frontend/src/layouts/GDefault.vue index 7a0cefa779..c19ce4b366 100644 --- a/frontend/src/layouts/GDefault.vue +++ b/frontend/src/layouts/GDefault.vue @@ -32,7 +32,6 @@ SPDX-License-Identifier: Apache-2.0 import { ref, computed, - toRef, onMounted, } from 'vue' import { onBeforeRouteUpdate } from 'vue-router' @@ -49,15 +48,16 @@ import GNotify from '@/components/GNotify.vue' import GBreadcrumbs from '@/components/GBreadcrumbs.vue' import { useLogger } from '@/composables/useLogger' +import { useIsInIframe } from '@/composables/useIsInIframe' import get from 'lodash/get' const logger = useLogger() +const isInIframe = useIsInIframe() const appStore = useAppStore() const authnStore = useAuthnStore() // refs -const isInIframe = toRef(appStore, 'isInIframe') const app = ref(null) const mainContent = ref(null) diff --git a/frontend/src/router/guards.js b/frontend/src/router/guards.js index 7f04232563..e555041409 100644 --- a/frontend/src/router/guards.js +++ b/frontend/src/router/guards.js @@ -18,6 +18,7 @@ import { useSeedStore } from '@/store/seed' import { useShootStore } from '@/store/shoot' import { useTerminalStore } from '@/store/terminal' +import { useOpenMFP } from '@/composables/useOpenMFP' import { useLogger } from '@/composables/useLogger' import { useApi } from '@/composables/useApi' @@ -50,7 +51,8 @@ export function createGlobalBeforeGuards () { return true } - const context = await appStore.getLuigiContext() + const openMFP = useOpenMFP() + const context = await openMFP.getLuigiContext() if (context) { logger.debug('Luigi context:', context) const token = context.token diff --git a/frontend/src/store/app.js b/frontend/src/store/app.js index 8a2be1d6f3..363e270222 100644 --- a/frontend/src/store/app.js +++ b/frontend/src/store/app.js @@ -8,17 +8,9 @@ import { defineStore, acceptHMRUpdate, } from 'pinia' -import { - ref, - watch, - toRef, - computed, -} from 'vue' -import LuigiClient from '@luigi-project/client' +import { ref } from 'vue' import { useNotification } from '@kyvg/vue3-notification' -import { useLogger } from '@/composables/useLogger' - import { parseWarningHeader } from '@/utils/headerWarnings' import { errorDetailsFromError } from '@/utils/error' import moment from '@/utils/moment' @@ -26,8 +18,6 @@ import moment from '@/utils/moment' import assign from 'lodash/assign' export const useAppStore = defineStore('app', () => { - const logger = useLogger() - const ready = ref(false) const sidebar = ref(true) const redirectPath = ref(null) @@ -38,63 +28,9 @@ export const useAppStore = defineStore('app', () => { const splitpaneResize = ref(0) const fromRoute = ref(null) const routerError = ref(null) - const luigiContext = ref(null) const { notify } = useNotification() - const isInIframe = computed(() => window.self !== window.top) - - if (isInIframe.value) { - logger.debug('Registering listener for Luigi context initialization and context updates') - LuigiClient.addInitListener(context => setLuigiContext(context)) - LuigiClient.addContextUpdateListener(context => setLuigiContext(context)) - } - - function setLuigiContext (value) { - luigiContext.value = value - } - - const accountId = computed(() => luigiContext.value?.accountId) - - function getLuigiContext () { - if (!isInIframe.value) { - return Promise.resolve(null) - } - if (luigiContext.value !== null) { - return Promise.resolve(luigiContext.value) - } - return new Promise(resolve => { - const timeout = 3000 - const timeoutId = setTimeout(() => { - unwatch() - logger.error('The initialization of the Luigi Client has timed out after %d milliseconds', timeout) - resolve(null) - }, timeout) - const unwatch = watch(luigiContext, context => { - if (context !== null) { - clearTimeout(timeoutId) - unwatch() - resolve(context) - } - }, { - immediate: true, - }) - }) - } - - function setRoute (route) { - if (isInIframe.value) { - const pathname = toRef(route, 'path') - watch(pathname, value => { - if (value) { - LuigiClient.linkManager().fromVirtualTreeRoot().withoutSync().navigate(value) - } - }, { - immediate: true, - }) - } - } - function updateSplitpaneResize () { splitpaneResize.value = Date.now() } @@ -149,12 +85,7 @@ export const useAppStore = defineStore('app', () => { focusedElementId, splitpaneResize, fromRoute, - isInIframe, - setRoute, routerError, - accountId, - luigiContext, - getLuigiContext, updateSplitpaneResize, setError, setHeaderWarning, diff --git a/frontend/src/views/GAdministration.vue b/frontend/src/views/GAdministration.vue index ff2ea74d33..e7c9aff86c 100644 --- a/frontend/src/views/GAdministration.vue +++ b/frontend/src/views/GAdministration.vue @@ -511,6 +511,7 @@ import GShootCustomFieldsConfiguration from '@/components/GShootCustomFieldsConf import GResourceQuotaHelp from '@/components/GResourceQuotaHelp.vue' import GTextRouterLink from '@/components/GTextRouterLink.vue' +import { useOpenMFP } from '@/composables/useOpenMFP' import { useProvideProjectItem } from '@/composables/useProjectItem' import { useProvideProjectContext } from '@/composables/useProjectContext' import { useLogger } from '@/composables/useLogger' @@ -528,6 +529,7 @@ import set from 'lodash/set' import includes from 'lodash/includes' const logger = useLogger() +const openMFP = useOpenMFP() const appStore = useAppStore() const configStore = useConfigStore() const quotaStore = useQuotaStore() @@ -540,7 +542,10 @@ const kubeconfigStore = useKubeconfigStore() const route = useRoute() const router = useRouter() -useProvideProjectContext() +useProvideProjectContext({ + openMFP, + configStore, +}) const color = ref('primary') const errorMessage = ref(undefined) @@ -646,9 +651,9 @@ async function updateProperty (key, value, options = {}) { metadata: { name }, spec: { namespace }, } - if (appStore.accountId && !get(projectStore.project, ['metadata', 'annotations', 'openmfp.org/account-id'])) { + if (openMFP.accountId && !get(projectStore.project, ['metadata', 'annotations', 'openmfp.org/account-id'])) { set(mergePatchDocument, ['metadata', 'labels', 'openmfp.org/managed-by'], 'true') - set(mergePatchDocument, ['metadata', 'annotations', 'openmfp.org/account-id'], appStore.accountId) + set(mergePatchDocument, ['metadata', 'annotations', 'openmfp.org/account-id'], openMFP.accountId) } set(mergePatchDocument, ['spec', key], value) await projectStore.patchProject(mergePatchDocument) diff --git a/frontend/src/views/GProjectList.vue b/frontend/src/views/GProjectList.vue index c2c60db728..c45d2bbb6b 100644 --- a/frontend/src/views/GProjectList.vue +++ b/frontend/src/views/GProjectList.vue @@ -132,7 +132,6 @@ import { toRef, } from 'vue' -import { useAppStore } from '@/store/app' import { useAuthzStore } from '@/store/authz' import { useProjectStore } from '@/store/project' @@ -140,20 +139,22 @@ import GProjectDialog from '@/components/dialogs/GProjectDialog.vue' import GTextRouterLink from '@/components/GTextRouterLink.vue' import GAccountAvatar from '@/components/GAccountAvatar.vue' +import { useOpenMFP } from '@/composables/useOpenMFP' + import get from 'lodash/get' import filter from 'lodash/filter' const projectStore = useProjectStore() const authzStore = useAuthzStore() -const appStore = useAppStore() +const openMFP = useOpenMFP() const canCreateProject = toRef(authzStore, 'canCreateProject') const projectDialog = ref(false) const projectList = computed(() => { - const belongsToAccount = project => appStore.accountId === get(project, ['metadata', 'annotations', 'openmfp.org/account-id']) - return !appStore.accountId + const belongsToAccount = project => openMFP.accountId === get(project, ['metadata', 'annotations', 'openmfp.org/account-id']) + return !openMFP.accountId ? projectStore.projectList : filter(projectStore.projectList, belongsToAccount) }) diff --git a/frontend/src/views/GShootList.vue b/frontend/src/views/GShootList.vue index 631f825ac8..e2ec4cf0c3 100644 --- a/frontend/src/views/GShootList.vue +++ b/frontend/src/views/GShootList.vue @@ -218,7 +218,6 @@ import { } from 'pinia' import { useUrlSearchParams } from '@vueuse/core' -import { useAppStore } from '@/store/app' import { useAuthnStore } from '@/store/authn' import { useAuthzStore } from '@/store/authz' import { useShootStore } from '@/store/shoot' @@ -236,6 +235,7 @@ import GCertifiedKubernetes from '@/components/icons/GCertifiedKubernetes.vue' import GDataTableFooter from '@/components/GDataTableFooter.vue' import GShootListActions from '@/components/GShootListActions.vue' +import { useIsInIframe } from '@/composables/useIsInIframe' import { useProjectShootCustomFields } from '@/composables/useProjectShootCustomFields' import { isCustomField } from '@/composables/useProjectShootCustomFields/helper' import { useProvideShootAction } from '@/composables/useShootAction' @@ -292,14 +292,13 @@ export default { setup () { const projectStore = useProjectStore() const shootStore = useShootStore() + const isInIframe = useIsInIframe() useProvideShootAction({ shootStore }) const activePopoverKey = ref('') const expandedWorkerGroups = reactive({ default: false }) const expandedAccessRestrictions = reactive({ default: false }) - const appStore = useAppStore() - const isInIframe = toRef(appStore, 'isInIframe') provide('activePopoverKey', activePopoverKey) provide('expandedWorkerGroups', expandedWorkerGroups) provide('expandedAccessRestrictions', expandedAccessRestrictions)