@swiss-ai-hub/web 0.291.7 → 0.291.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app.vue +10 -0
- package/composables/useHomeResolving.ts +2 -0
- package/middleware/auth.global.ts +1 -0
- package/middleware/home-redirect.ts +30 -0
- package/package.json +1 -1
- package/pages/index.vue +3 -43
- package/plugins/home-resolving.client.ts +13 -0
- package/plugins/oidc-client.ts +6 -24
package/app.vue
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
<NuxtPage />
|
|
4
4
|
<Toast />
|
|
5
5
|
<ConfirmDialog />
|
|
6
|
+
<div
|
|
7
|
+
v-if="homeResolving"
|
|
8
|
+
class="fixed inset-0 z-50 flex items-center justify-center bg-white dark:bg-surface-900"
|
|
9
|
+
>
|
|
10
|
+
<ProgressSpinner />
|
|
11
|
+
</div>
|
|
6
12
|
</NuxtLayout>
|
|
7
13
|
</template>
|
|
8
14
|
|
|
@@ -17,6 +23,10 @@ const localePath = useLocalePath()
|
|
|
17
23
|
const route = useRoute()
|
|
18
24
|
const toast = useToast()
|
|
19
25
|
useNotificationPoller()
|
|
26
|
+
|
|
27
|
+
// Overlay lives at the root, not the page: the home-redirect middleware
|
|
28
|
+
// redirects before any page mounts, so the page can't show it.
|
|
29
|
+
const homeResolving = useHomeResolving()
|
|
20
30
|
client.setConfig({
|
|
21
31
|
baseURL: '/api/v1',
|
|
22
32
|
auth: async () => {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { getMyTenants } from '@core/sdk/client'
|
|
2
|
+
|
|
3
|
+
import { useLocalePath } from '#i18n'
|
|
4
|
+
|
|
5
|
+
const REDIRECT_KEY = 'aihub_redirect_after_login'
|
|
6
|
+
|
|
7
|
+
// Redirect runs in the guard pipeline, not the page: a page-component redirect
|
|
8
|
+
// here can be dropped by an in-flight navigation.
|
|
9
|
+
export default defineNuxtRouteMiddleware(async () => {
|
|
10
|
+
useHomeResolving().value = true
|
|
11
|
+
|
|
12
|
+
const localePath = useLocalePath()
|
|
13
|
+
|
|
14
|
+
const response = await getMyTenants({ composable: '$fetch' }).catch(() => null)
|
|
15
|
+
const tenants = response?.tenants ?? []
|
|
16
|
+
if (!tenants.length) {
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const storedRedirect = sessionStorage.getItem(REDIRECT_KEY)
|
|
21
|
+
sessionStorage.removeItem(REDIRECT_KEY)
|
|
22
|
+
|
|
23
|
+
if (storedRedirect && storedRedirect !== '/') {
|
|
24
|
+
return navigateTo(storedRedirect, { replace: true })
|
|
25
|
+
}
|
|
26
|
+
if (tenants.length === 1) {
|
|
27
|
+
return navigateTo(localePath(`/${tenants[0].id}/service/openai`), { replace: true })
|
|
28
|
+
}
|
|
29
|
+
return navigateTo(localePath('/select-tenant'), { replace: true })
|
|
30
|
+
})
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"license": "AGPL-3.0-or-later",
|
|
4
4
|
"author": "bbv Software Services AG (https://www.bbv.ch)",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"version": "0.291.
|
|
6
|
+
"version": "0.291.8",
|
|
7
7
|
"description": "Swiss AI Hub - Admin & Management UI (Nuxt 3 layer)",
|
|
8
8
|
"main": "./nuxt.config.ts",
|
|
9
9
|
"repository": {
|
package/pages/index.vue
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="flex h-screen items-center justify-center">
|
|
3
|
-
<
|
|
4
|
-
<div
|
|
5
|
-
v-else-if="!tenants?.length"
|
|
6
|
-
class="text-center"
|
|
7
|
-
>
|
|
3
|
+
<div class="text-center">
|
|
8
4
|
<h1 class="mb-4 text-2xl font-bold">
|
|
9
5
|
{{ t('tenant.no_tenant_title') }}
|
|
10
6
|
</h1>
|
|
@@ -16,44 +12,8 @@
|
|
|
16
12
|
</template>
|
|
17
13
|
|
|
18
14
|
<script setup lang="ts">
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const REDIRECT_KEY = 'aihub_redirect_after_login'
|
|
15
|
+
// Shown only when the user has no tenants; home-redirect middleware redirects otherwise.
|
|
16
|
+
definePageMeta({ middleware: 'home-redirect' })
|
|
22
17
|
|
|
23
18
|
const { t } = useI18n()
|
|
24
|
-
const localePath = useLocalePath()
|
|
25
|
-
|
|
26
|
-
const tenantsAreLoading = ref(true)
|
|
27
|
-
const tenants = ref<{ id: string }[] | null>(null)
|
|
28
|
-
|
|
29
|
-
onMounted(async () => {
|
|
30
|
-
try {
|
|
31
|
-
const response = await getMyTenants({ composable: '$fetch' })
|
|
32
|
-
tenants.value = response.tenants
|
|
33
|
-
|
|
34
|
-
if (!response.tenants?.length) {
|
|
35
|
-
tenantsAreLoading.value = false
|
|
36
|
-
return
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const storedRedirect = sessionStorage.getItem(REDIRECT_KEY)
|
|
40
|
-
sessionStorage.removeItem(REDIRECT_KEY)
|
|
41
|
-
|
|
42
|
-
if (storedRedirect && storedRedirect !== '/') {
|
|
43
|
-
await navigateTo(storedRedirect, { replace: true })
|
|
44
|
-
return
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (response.tenants.length === 1) {
|
|
48
|
-
const tenant = response.tenants[0]
|
|
49
|
-
await navigateTo(localePath(`/${tenant.id}/service/openai`), { replace: true })
|
|
50
|
-
return
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
await navigateTo(localePath('/select-tenant'), { replace: true })
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
tenantsAreLoading.value = false
|
|
57
|
-
}
|
|
58
|
-
})
|
|
59
19
|
</script>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// In a plugin, not app.vue, so afterEach registers before the initial navigation
|
|
2
|
+
// — otherwise a full load landing on the home route never clears the flag.
|
|
3
|
+
export default defineNuxtPlugin(() => {
|
|
4
|
+
const router = useRouter()
|
|
5
|
+
const homeResolving = useHomeResolving()
|
|
6
|
+
|
|
7
|
+
router.afterEach(() => {
|
|
8
|
+
homeResolving.value = false
|
|
9
|
+
})
|
|
10
|
+
router.onError(() => {
|
|
11
|
+
homeResolving.value = false
|
|
12
|
+
})
|
|
13
|
+
})
|
package/plugins/oidc-client.ts
CHANGED
|
@@ -40,35 +40,17 @@ export default defineNuxtPlugin(async ({ $i18n, $router }) => {
|
|
|
40
40
|
$router.push(`/${locale}/auth/login`)
|
|
41
41
|
})
|
|
42
42
|
|
|
43
|
-
auth.events.addSilentRenewError((error) => {
|
|
43
|
+
auth.events.addSilentRenewError(async (error) => {
|
|
44
44
|
console.error('Silent renew error:', error)
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
// the user to the login page now so they re-auth in place.
|
|
45
|
+
// Refresh token rejected (e.g. Keycloak invalidated it): drop the dead
|
|
46
|
+
// session before redirecting so it is not reused.
|
|
47
|
+
await auth.removeUser()
|
|
49
48
|
const locale = $i18n.locale.value
|
|
50
49
|
$router.push(`/${locale}/auth/login`)
|
|
51
50
|
})
|
|
52
51
|
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
const user = await auth.getUser()
|
|
56
|
-
if (user && !user.expired) {
|
|
57
|
-
console.log('User already logged in')
|
|
58
|
-
}
|
|
59
|
-
else if (user?.expired) {
|
|
60
|
-
console.log('User session expired, attempting renewal')
|
|
61
|
-
try {
|
|
62
|
-
await auth.signinSilent()
|
|
63
|
-
}
|
|
64
|
-
catch (e) {
|
|
65
|
-
console.error('Failed to renew session:', e)
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
catch (e) {
|
|
70
|
-
console.error('Error checking initial user state:', e)
|
|
71
|
-
}
|
|
52
|
+
// Session renewal is handled per-navigation by middleware/auth.global.ts;
|
|
53
|
+
// intentionally not done here (it would block app bootstrap).
|
|
72
54
|
|
|
73
55
|
return {
|
|
74
56
|
provide: {
|