From 921389a59cd2ac03cb13177de06d28a2c938b8ff Mon Sep 17 00:00:00 2001 From: Edward Sammut Alessi Date: Thu, 23 Apr 2026 17:19:54 +0200 Subject: [PATCH] fix(frontend): fix eula handling to prevent being stuck on /eula If initial EULA request fails, we will show AppUnavailable instead of sending to /eula. If you navigate directly /eula and its already accepted, navigate away to the Home page. Signed-off-by: Edward Sammut Alessi --- frontend/src/main.ts | 10 +++++++++- frontend/src/methods/useResourceGet.ts | 13 ++++++++++++- frontend/src/methods/useResourceList.ts | 13 ++++++++++++- frontend/src/pages/eula.stories.ts | 26 ++++++++++++++++++++++++- frontend/src/pages/eula.vue | 19 ++++++++++++++++-- 5 files changed, 75 insertions(+), 6 deletions(-) diff --git a/frontend/src/main.ts b/frontend/src/main.ts index c9134df6..da9bd52b 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -11,6 +11,8 @@ import { createApp } from 'vue' import { handleHotUpdate } from 'vue-router/auto-routes' import { Runtime } from '@/api/common/omni.pb' +import type { RequestError } from '@/api/fetch.pb' +import { Code } from '@/api/google/rpc/code.pb' import type { Resource } from '@/api/grpc' import { initState, ResourceService } from '@/api/grpc' import type { AuthConfigSpec, EulaAcceptanceSpec } from '@/api/omni/specs/auth.pb' @@ -65,7 +67,13 @@ const setupApp = async () => { withRuntime(Runtime.Omni), ) eulaAccepted.value = true - } catch { + } catch (e) { + if ((e as RequestError)?.code !== Code.NOT_FOUND) { + console.error('failed to get eula state', e) + createApp(AppUnavailable).mount('#app') + return + } + eulaAccepted.value = false } diff --git a/frontend/src/methods/useResourceGet.ts b/frontend/src/methods/useResourceGet.ts index 1ffc487f..40c1c081 100644 --- a/frontend/src/methods/useResourceGet.ts +++ b/frontend/src/methods/useResourceGet.ts @@ -8,12 +8,19 @@ import { Runtime } from '@/api/common/omni.pb' import type { fetchOption } from '@/api/fetch.pb' import { type Resource, ResourceService } from '@/api/grpc' import type { GetRequest } from '@/api/omni/resources/resources.pb' -import { withAbortController, withContext, withRuntime, withSelectors } from '@/api/options' +import { + withAbortController, + withContext, + withRuntime, + withSelectors, + withSkipRequestSignature, +} from '@/api/options' import type { WatchContext } from '@/api/watch' interface GetOptionsCommon { resource: GetRequest selectors?: string[] + skipSignature?: boolean skip?: boolean } @@ -54,6 +61,10 @@ export function useResourceGet( const fetchOptions: fetchOption[] = [] + if (options.skipSignature) { + fetchOptions.push(withSkipRequestSignature()) + } + if (options.runtime) { fetchOptions.push(withRuntime(options.runtime)) } diff --git a/frontend/src/methods/useResourceList.ts b/frontend/src/methods/useResourceList.ts index a47abce9..2988e4aa 100644 --- a/frontend/src/methods/useResourceList.ts +++ b/frontend/src/methods/useResourceList.ts @@ -8,12 +8,19 @@ import { Runtime } from '@/api/common/omni.pb' import type { fetchOption } from '@/api/fetch.pb' import { type Resource, ResourceService } from '@/api/grpc' import type { ListRequest } from '@/api/omni/resources/resources.pb' -import { withAbortController, withContext, withRuntime, withSelectors } from '@/api/options' +import { + withAbortController, + withContext, + withRuntime, + withSelectors, + withSkipRequestSignature, +} from '@/api/options' import type { WatchContext } from '@/api/watch' interface ListOptionsCommon { resource: ListRequest selectors?: string[] + skipSignature?: boolean skip?: boolean } @@ -54,6 +61,10 @@ export function useResourceList( const fetchOptions: fetchOption[] = [] + if (options.skipSignature) { + fetchOptions.push(withSkipRequestSignature()) + } + if (options.runtime) { fetchOptions.push(withRuntime(options.runtime)) } diff --git a/frontend/src/pages/eula.stories.ts b/frontend/src/pages/eula.stories.ts index c722015c..886e93da 100644 --- a/frontend/src/pages/eula.stories.ts +++ b/frontend/src/pages/eula.stories.ts @@ -5,7 +5,8 @@ import type { Meta, StoryObj } from '@storybook/vue3-vite' import { delay, http, HttpResponse } from 'msw' -import type { CreateRequest, CreateResponse } from '@/api/omni/resources/resources.pb' +import { Code } from '@/api/google/rpc/code.pb' +import type { CreateRequest, CreateResponse, GetRequest } from '@/api/omni/resources/resources.pb' import { DefaultNamespace, EulaAcceptanceID, EulaAcceptanceType } from '@/api/resources' import Eula from './eula.vue' @@ -21,6 +22,29 @@ export const Default: Story = { parameters: { msw: { handlers: [ + http.post('/omni.resources.ResourceService/Get', async ({ request }) => { + const { id, type, namespace } = await request.clone().json() + + if ( + id !== EulaAcceptanceID || + type !== EulaAcceptanceType || + namespace !== DefaultNamespace + ) { + return + } + + return HttpResponse.json( + { + body: { + code: Code.NOT_FOUND, + message: + "failed to get: resource EulaAcceptances.omni.sidero.dev(default/eula@undefined) doesn't exist", + }, + }, + { status: 404 }, + ) + }), + http.post( '/omni.resources.ResourceService/Create', async ({ request }) => { diff --git a/frontend/src/pages/eula.vue b/frontend/src/pages/eula.vue index 3f9272f2..b5b2a26e 100644 --- a/frontend/src/pages/eula.vue +++ b/frontend/src/pages/eula.vue @@ -5,7 +5,7 @@ Use of this software is governed by the Business Source License included in the LICENSE file. -->