diff --git a/src/App.vue b/src/App.vue index 420561b7dd8162a2950310338fd572d9acf45fd4..e293b275eeb1734b9b1b052650c3b7e19088ea7f 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,16 +1,33 @@ <script setup lang="ts"> import NavbarTop from "./components/NavbarTop.vue"; -import SidebarLeft from "./components/SidebarLeft.vue"; +import { onBeforeMount } from "vue"; +import { useCookies } from "vue3-cookies"; +import { useAuthStore } from "@/stores/auth"; +import { useRouter } from "vue-router"; + +const { cookies } = useCookies(); +const store = useAuthStore(); +const router = useRouter(); + +onBeforeMount(() => { + store.setToken(cookies.get("bearer")); + router.beforeEach(async (to) => { + // make sure the user is authenticated + if ( + !store.authenticated && + // â—ï¸ Avoid an infinite redirect + to.name !== "login" + ) { + // redirect the user to the login page + return { name: "login" }; + } + }); +}); </script> -<template style="height: 100%"> +<template> <NavbarTop /> - <SidebarLeft /> - <router-view class="cona"></router-view> + <router-view></router-view> </template> -<style scoped> -.cona { - min-height: 80vh; -} -</style> +<style scoped></style> diff --git a/src/assets/images/ls-login.png b/src/assets/images/ls-login.png new file mode 100644 index 0000000000000000000000000000000000000000..ab72fa293def5199720b4a00bac0d824aad6d26a Binary files /dev/null and b/src/assets/images/ls-login.png differ diff --git a/src/assets/logo.svg b/src/assets/logo.svg deleted file mode 100644 index bc826fed80ad0c846e5ca25978776f555f4a2370..0000000000000000000000000000000000000000 --- a/src/assets/logo.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69" xmlns:v="https://vecta.io/nano"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg> \ No newline at end of file diff --git a/src/components/NavbarTop.vue b/src/components/NavbarTop.vue index edf18f4400735c1f171ae6eab84b1bdf42cec32b..07ad562daac5ad49e6bfd6e55335412fae788652 100644 --- a/src/components/NavbarTop.vue +++ b/src/components/NavbarTop.vue @@ -2,8 +2,15 @@ import BootstrapIcon from "./BootstrapIcon.vue"; import { reactive, onBeforeUnmount, onMounted } from "vue"; import { MiscellaneousService } from "@/client"; +import { useAuthStore } from "@/stores/auth"; +import { useRouter } from "vue-router"; +import { useCookies } from "vue3-cookies"; -const api_connection = reactive({ connected: false, timer: null }); +const router = useRouter(); +const store = useAuthStore(); +const { cookies } = useCookies(); + +const api_connection = reactive({ connected: true, timer: null }); let timer: ReturnType<typeof setInterval> | undefined = undefined; function checkApiHealth() { MiscellaneousService.miscellaneousHealthCheck() @@ -15,6 +22,12 @@ function checkApiHealth() { }); } +function logout() { + store.logout(); + cookies.remove("bearer"); + router.push({ name: "login" }); +} + onMounted(() => { checkApiHealth(); timer = setInterval(checkApiHealth, 10000); @@ -36,7 +49,10 @@ onBeforeUnmount(() => { > Backend not reachable </span> - <div class="dropdown d-flex me-3"> + <div + class="dropdown d-flex me-3" + v-if="store.authenticated && store.user != null" + > <a href="#" class="d-flex align-items-center text-white text-decoration-none dropdown-toggle-split" @@ -44,7 +60,7 @@ onBeforeUnmount(() => { data-bs-toggle="dropdown" aria-expanded="false" > - <strong class="me-2">Bilbo Baggins</strong> + <strong class="me-2">{{ store.user.display_name }}</strong> <bootstrap-icon icon="person-circle" fill="white" @@ -56,15 +72,18 @@ onBeforeUnmount(() => { class="dropdown-menu dropdown-menu-dark text-small shadow" aria-labelledby="dropdownUser1" > - <li><a class="dropdown-item" href="#">New project...</a></li> - <li><a class="dropdown-item" href="#">Settings</a></li> - <li><a class="dropdown-item" href="#">Profile</a></li> <li><hr class="dropdown-divider" /></li> - <li><a class="dropdown-item" href="#">Sign out</a></li> + <li> + <a class="dropdown-item pseudo-link" @click="logout">Sign out</a> + </li> </ul> </div> </div> </nav> </template> -<style scoped></style> +<style scoped> +.pseudo-link { + cursor: pointer; +} +</style> diff --git a/src/components/SidebarLeft.vue b/src/components/SidebarLeft.vue index a5d18665f20910f3f14d1fa6bb6ec3d756b064e8..677c417e8f4b40c0bbcb034854ab259e15c2560a 100644 --- a/src/components/SidebarLeft.vue +++ b/src/components/SidebarLeft.vue @@ -2,32 +2,28 @@ <template> <div - class="d-flex flex-column flex-shrink-0 p-3 text-white bg-dark" - style="width: 200px" + class="d-flex flex-column flex-shrink-0 p-3 text-white bg-dark position-fixed top-50 start-0 translate-middle-y" + style="width: 200px; min-height: 80vh" > <ul class="nav nav-pills flex-column mb-auto"> - <li class="nav-item"> + <li class="nav-item mb-1"> <button - class="btn btn-toggle align-items-center rounded collapsed text-white" + class="btn btn-lg btn-toggle align-items-center rounded collapsed text-white" data-bs-toggle="collapse" - data-bs-target="#home-collapse" + data-bs-target="#os-collapse" aria-expanded="true" > Object Storage </button> - <div class="collapse show" id="home-collapse"> + <div class="collapse show fs-5" id="os-collapse"> <ul class="btn-toggle-nav list-unstyled fw-normal pb-1 small"> <li> - <router-link - to="/object-storage/buckets" - class="link-light rounded" + <router-link :to="{ name: 'buckets' }" class="link-light rounded" >Buckets</router-link > </li> <li> - <router-link - to="/object-storage/s3-keys" - class="link-light rounded" + <router-link :to="{ name: 's3_keys' }" class="link-light rounded" >S3 Keys</router-link > </li> @@ -38,4 +34,5 @@ </div> </template> -<style scoped></style> +<style scoped> +</style> diff --git a/src/router/index.ts b/src/router/index.ts index 3cc545efae8506530b96dd66ec15e72b0238fc60..6034817887ef91bc8e17408158b5e7832621f30e 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,21 +1,35 @@ import { createRouter, createWebHistory } from "vue-router"; import BucketsView from "../views/BucketsView.vue"; +import DashboardView from "../views/DashboardView.vue"; +import LoginView from "../views/LoginView.vue"; const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { - path: "/object-storage/buckets", - name: "buckets", - component: BucketsView, + path: "/dashboard", + name: "dashboard", + component: DashboardView, + children: [ + { + path: "object-storage/buckets", + name: "buckets", + component: BucketsView, + }, + { + path: "object-storage/s3-keys", + name: "s3_keys", + // route level code-splitting + // this generates a separate chunk (About.[hash].js) for this route + // which is lazy-loaded when the route is visited. + component: () => import("../views/S3KeysView.vue"), + }, + ], }, { - path: "/object-storage/s3-keys", - name: "about", - // route level code-splitting - // this generates a separate chunk (About.[hash].js) for this route - // which is lazy-loaded when the route is visited. - component: () => import("../views/S3KeysView.vue"), + path: "/login", + name: "login", + component: LoginView, }, { path: "/", diff --git a/src/stores/auth.ts b/src/stores/auth.ts new file mode 100644 index 0000000000000000000000000000000000000000..93dd60eaa64a3db0b5f9e0852e33d0f16ece69d8 --- /dev/null +++ b/src/stores/auth.ts @@ -0,0 +1,45 @@ +import { defineStore } from "pinia"; +import type { User } from "@/client"; +import { UserService } from "@/client"; +import { OpenAPI } from "@/client"; + +export type RootState = { + token: string | null; + user: User | null; +}; + +export const useAuthStore = defineStore({ + id: "auth", + state: () => + ({ + token: null, + user: null, + } as RootState), + getters: { + authenticated: (state) => state.token != null, + }, + actions: { + setToken(token: string | null) { + if (token != null) { + OpenAPI.TOKEN = token; + this.token = token; + UserService.userGetLoggedInUser() + .then((user) => { + this.user = user; + }) + .catch(() => { + this.token = null; + }); + } else { + this.token = null; + this.user = null; + } + }, + updateUser() { + this.setToken(this.token); + }, + logout() { + this.setToken(null); + }, + }, +}); diff --git a/src/views/BucketsView.vue b/src/views/BucketsView.vue index 94874bbaa4e3c1028874d2e9dc2796b47f975f6e..3635de2549d203e645aad1cd7c96ccf7140fc3bf 100644 --- a/src/views/BucketsView.vue +++ b/src/views/BucketsView.vue @@ -1,25 +1,7 @@ -<script setup lang="ts"> -import { onMounted, reactive } from "vue"; -import { useCookies } from "vue3-cookies"; - -const { cookies } = useCookies(); -const my_cookie = reactive({ - val: "defaults", -}); - -onMounted(() => { - if (cookies.isKey("bearer")) { - my_cookie.val = cookies.get("bearer"); - } -}); -</script> +<script setup lang="ts"></script> <template> <main> - <p v-if="my_cookie.val !== 'defaults'"> - Cookie Present: {{ my_cookie.val }} - </p> - <p v-else>No Cookie set</p> <div>This the is the Buckets Page</div> </main> </template> diff --git a/src/views/DashboardView.vue b/src/views/DashboardView.vue new file mode 100644 index 0000000000000000000000000000000000000000..ed97b6c31c9465a513929051ac1bdee5aca896a4 --- /dev/null +++ b/src/views/DashboardView.vue @@ -0,0 +1,10 @@ +<script setup lang="ts"> +import SidebarLeft from "../components/SidebarLeft.vue"; +</script> + +<template> + <SidebarLeft /> + <router-view></router-view> +</template> + +<style scoped></style> diff --git a/src/views/LoginView.vue b/src/views/LoginView.vue new file mode 100644 index 0000000000000000000000000000000000000000..5de83ebb2da5e07d155dcba45ab06219d5cc5c0d --- /dev/null +++ b/src/views/LoginView.vue @@ -0,0 +1,34 @@ +<script setup lang="ts"> +import { onBeforeMount } from "vue"; +import { useAuthStore } from "@/stores/auth"; +import { useRouter } from "vue-router"; +import { OpenAPI } from "@/client"; + +const router = useRouter(); + +const store = useAuthStore(); + +onBeforeMount(() => { + if (store.authenticated) { + // If user is authenticated redirect him to the dashboard + router.push({ name: "buckets" }); + } +}); +</script> + +<template> + <div + class="card text-center bg-dark ms-md-auto position-absolute top-50 left-50 translate-middle" + > + <div class="card-header text-dark bg-light">LoginView</div> + <div class="card-body p-5"> + <h5 class="card-title">Login</h5> + <p class="card-text">Login to this service with LifeScience</p> + <a :href="OpenAPI.BASE + '/auth/login'" class="m-2"> + <img src="/src/assets/images/ls-login.png" alt="[LS Login]" /> + </a> + </div> + </div> +</template> + +<style scoped></style>