diff options
-rw-r--r-- | src/App.vue | 31 | ||||
-rw-r--r-- | src/assets/images/openbmc-logo.svg | 1 | ||||
-rw-r--r-- | src/assets/styles/_obmc-custom.scss | 13 | ||||
-rw-r--r-- | src/components/AppHeader/AppHeader.vue | 9 | ||||
-rw-r--r-- | src/layouts/AppLayout.vue | 38 | ||||
-rw-r--r-- | src/main.js | 7 | ||||
-rw-r--r-- | src/router/index.js | 47 | ||||
-rw-r--r-- | src/store/api.js | 15 | ||||
-rw-r--r-- | src/store/index.js | 4 | ||||
-rw-r--r-- | src/store/modules/Authentication/AuthenticanStore.js | 56 | ||||
-rw-r--r-- | src/views/Login/Login.vue | 111 | ||||
-rw-r--r-- | src/views/Login/index.js | 2 |
12 files changed, 265 insertions, 69 deletions
diff --git a/src/App.vue b/src/App.vue index d0e1a0ae..56674955 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,18 +1,6 @@ <template> <div id="app"> - <AppHeader /> - <b-container fluid class="page-container"> - <b-row no-gutters> - <b-col tag="nav" cols="12" md="3" lg="2"> - <AppNavigation /> - </b-col> - <b-col cols="12" md="9" lg="10"> - <main id="#main-content"> - <router-view /> - </main> - </b-col> - </b-row> - </b-container> + <router-view /> </div> </template> @@ -21,22 +9,7 @@ </style> <script> -import AppHeader from "@/components/AppHeader"; -import AppNavigation from "@/components/AppNavigation"; export default { - name: "App", - components: { - AppHeader, - AppNavigation - } + name: "App" }; </script> - -<style lang="scss" scoped> -.page-container { - margin-right: 0; - margin-left: 0; - padding-right: 0; - padding-left: 0; -} -</style> diff --git a/src/assets/images/openbmc-logo.svg b/src/assets/images/openbmc-logo.svg new file mode 100644 index 00000000..d0fa158c --- /dev/null +++ b/src/assets/images/openbmc-logo.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 241.23 240.05"><defs><style>.cls-1{fill:#a6a8ab;}.cls-2{fill:url(#linear-gradient);}.cls-3{fill:url(#linear-gradient-2);}.cls-4{fill:url(#linear-gradient-3);}.cls-5{fill:url(#linear-gradient-4);}.cls-6{fill:#626366;}</style><linearGradient id="linear-gradient" x1="82.9" y1="11.55" x2="82.9" y2="154.54" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#00b0da"/><stop offset="1" stop-color="#008abf"/></linearGradient><linearGradient id="linear-gradient-2" x1="81.55" y1="27.55" x2="81.55" y2="158.66" xlink:href="#linear-gradient"/><linearGradient id="linear-gradient-3" x1="156.66" y1="51.54" x2="156.66" y2="154.8" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#a5d440"/><stop offset="1" stop-color="#8cce3f"/></linearGradient><linearGradient id="linear-gradient-4" x1="158.41" y1="51.54" x2="158.41" y2="154.8" xlink:href="#linear-gradient-3"/></defs><title>Asset 1</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M241.23,205.77a18.66,18.66,0,1,0-.24,16L237.26,220a14.51,14.51,0,1,1,.21-12.46Z"/><path class="cls-2" d="M65.85,81.86a53.68,53.68,0,0,0,11.61,33.41c-.1.29-.15.6-.22.9a10.81,10.81,0,0,0-.34,2.57,11,11,0,1,0,11-11,10.75,10.75,0,0,0-1.2.07c-.31,0-.61.08-.91.13A42.82,42.82,0,0,1,99.95,43.86h0V2.07l-.77.21q-3.63.94-7.12,2.2c-1.29.47-2.58,1-3.84,1.48h0V38.19l-.13.1A53.79,53.79,0,0,0,65.85,81.86Z"/><path class="cls-3" d="M120.28,96.58a14.54,14.54,0,0,1-14.55-14.37H93.59v0a26.29,26.29,0,0,0,21,25.65v45.35A71.13,71.13,0,0,1,63.9,38.1c.31.06.63.1,1,.13s.64,0,1,0a10.83,10.83,0,1,0-10.25-7.41,82.23,82.23,0,0,0,64.18,133.6c1.41,0,2.81-.06,4.2-.14l1.63-.09h0V95.57A14.47,14.47,0,0,1,120.28,96.58Z"/><path class="cls-4" d="M171.95,68.54a53.78,53.78,0,0,0-9.85-19.71,11.31,11.31,0,0,0,.32-1.3,10.78,10.78,0,0,0,.24-2.17,11,11,0,1,0-8.89,10.8,42.83,42.83,0,0,1-14.17,64.08V162c1.08-.27,2.14-.56,3.2-.87a82.35,82.35,0,0,0,8.53-3V125.91a53.91,53.91,0,0,0,20.6-57.37Z"/><path class="cls-5" d="M184.63,132.75A82.21,82.21,0,0,0,119.79,0c-1.64,0-3.26.06-4.87.16h-.11V68.55h0A14.53,14.53,0,0,1,120,67.48h.27A14.56,14.56,0,0,1,134.87,82s0,.07,0,.11,0,.08,0,.13h11.08A26.21,26.21,0,0,0,125.81,56.8V11.3A71.14,71.14,0,0,1,176,125.83h-.07a11,11,0,0,0-12.58,10.88,11,11,0,0,0,11,11h0a11,11,0,0,0,10.54-14.13C184.82,133.3,184.73,133,184.63,132.75Z"/><polygon class="cls-1" points="201.22 231.42 201.22 195.65 196.53 195.65 182 225.14 167.47 195.65 162.72 195.65 162.72 231.42 166.89 231.42 166.89 204.21 180.33 231.42 183.67 231.42 197.1 204.21 197.1 231.42 201.22 231.42"/><path class="cls-6" d="M119.82,208.4a10.6,10.6,0,0,0-7.9-3.34,10.15,10.15,0,0,0-4.16.83,15.94,15.94,0,0,0-3.62,2.24v-2.7H99.91v26h4.23V216.64a7.74,7.74,0,0,1,2.08-5.5,7.48,7.48,0,0,1,10.66,0,7.76,7.76,0,0,1,2.08,5.48v14.78h4.11v-15a11.12,11.12,0,0,0-3.24-8"/><path class="cls-6" d="M63.68,224.4a12.41,12.41,0,0,1-4.86,5.17,13.54,13.54,0,0,1-7,1.85H45.54V240h-4V205.43H51.64a13.41,13.41,0,0,1,9.57,3.76,12.73,12.73,0,0,1,2.47,15.21m-18.14,3.24h5.77A9.48,9.48,0,0,0,58.05,225a8.59,8.59,0,0,0,2.76-6.54,8.38,8.38,0,0,0-2.7-6.41,9.43,9.43,0,0,0-6.68-2.51H45.54Z"/><path class="cls-6" d="M96.44,219.75a4.56,4.56,0,0,0,.14-1.36c0-7.38-6.27-13.36-14-13.36s-14,6-14,13.36,6.27,13.36,14,13.36a14,14,0,0,0,11.93-6.52l-3.25-2.45a9.89,9.89,0,0,1-8.68,5,9.43,9.43,0,1,1,0-18.83,9.8,9.8,0,0,1,9.35,6.54c0,.07.15.63.17.7H76.59v3.52Z"/><path class="cls-6" d="M33.19,213.53A14.53,14.53,0,1,1,18.66,199a14.53,14.53,0,0,1,14.53,14.53m4.12,0a18.66,18.66,0,1,0-18.66,18.66,18.66,18.66,0,0,0,18.66-18.66"/><path class="cls-1" d="M154.37,220.69a6.77,6.77,0,0,1-6.75,6.79H132.14V213.9h15.49a6.78,6.78,0,0,1,6.75,6.79m-2.29-15.93a5.08,5.08,0,0,1-5.05,5.08l-14.89,0V199.67H147a5.07,5.07,0,0,1,5.05,5.08m.94,6.7a9,9,0,0,0-5.69-15.75H128v35.75h20.14l.28,0v0A10.73,10.73,0,0,0,153,211.46"/></g></g></svg>
\ No newline at end of file diff --git a/src/assets/styles/_obmc-custom.scss b/src/assets/styles/_obmc-custom.scss index 45dec1f8..7deb6cba 100644 --- a/src/assets/styles/_obmc-custom.scss +++ b/src/assets/styles/_obmc-custom.scss @@ -1,16 +1,3 @@ -// Major breakpoints using 16px base em * 16 -// xs: 0, sm: 768px, md: 1024px, lg: 1376px, xl: 1600px -// Using em's will allow for changes if base font size is updated -// to accommodate for responsive text. Using 0 as xs due to -// Bootstrap requirements. -$grid-breakpoints: ( - xs: 0, - sm: 48em, - md: 64em, - lg: 86em, - xl: 100em -); - // Required @import "~bootstrap/scss/functions"; @import "./functions"; diff --git a/src/components/AppHeader/AppHeader.vue b/src/components/AppHeader/AppHeader.vue index 9177403c..8dacd03d 100644 --- a/src/components/AppHeader/AppHeader.vue +++ b/src/components/AppHeader/AppHeader.vue @@ -7,9 +7,9 @@ <b-nav-text>BMC System Management</b-nav-text> </b-navbar-nav> <b-navbar-nav small class="ml-auto"> - <b-nav-item> + <b-nav-item @click="logout"> <user-avatar-20 /> - User Avatar + Logout </b-nav-item> </b-navbar-nav> </b-navbar> @@ -77,6 +77,11 @@ export default { methods: { getHostInfo() { this.$store.dispatch("global/getHostName"); + }, + logout() { + this.$store.dispatch("authentication/logout").then(() => { + this.$router.push("/login"); + }); } } }; diff --git a/src/layouts/AppLayout.vue b/src/layouts/AppLayout.vue new file mode 100644 index 00000000..dcfb52e7 --- /dev/null +++ b/src/layouts/AppLayout.vue @@ -0,0 +1,38 @@ +<template> + <div> + <AppHeader /> + <b-container fluid class="page-container"> + <b-row no-gutters> + <b-col tag="nav" cols="12" md="3" lg="2"> + <AppNavigation /> + </b-col> + <b-col cols="12" md="9" lg="10"> + <main id="#main-content"> + <router-view /> + </main> + </b-col> + </b-row> + </b-container> + </div> +</template> + +<script> +import AppHeader from "@/components/AppHeader"; +import AppNavigation from "@/components/AppNavigation"; +export default { + name: "App", + components: { + AppHeader, + AppNavigation + } +}; +</script> + +<style lang="scss" scoped> +.page-container { + margin-right: 0; + margin-left: 0; + padding-right: 0; + padding-left: 0; +} +</style> diff --git a/src/main.js b/src/main.js index e18ce89e..6f4acc6d 100644 --- a/src/main.js +++ b/src/main.js @@ -7,7 +7,10 @@ import { BadgePlugin, ButtonPlugin, CollapsePlugin, + FormPlugin, FormCheckboxPlugin, + FormGroupPlugin, + FormInputPlugin, LayoutPlugin, LinkPlugin, ListGroupPlugin, @@ -22,7 +25,11 @@ Vue.filter("date", dateFilter); Vue.use(BadgePlugin); Vue.use(ButtonPlugin); Vue.use(CollapsePlugin); +Vue.use(FormPlugin); Vue.use(FormCheckboxPlugin); +Vue.use(FormGroupPlugin); +Vue.use(FormInputPlugin); +Vue.use(LayoutPlugin); Vue.use(LayoutPlugin); Vue.use(LinkPlugin); Vue.use(ListGroupPlugin); diff --git a/src/router/index.js b/src/router/index.js index 11d1a475..698aa700 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,28 +1,35 @@ import Vue from "vue"; import VueRouter from "vue-router"; +import store from "../store/index"; +import AppLayout from "../layouts/AppLayout.vue"; Vue.use(VueRouter); const routes = [ { path: "/", - name: "overview", - component: () => import("@/views/Overview") + name: "", + meta: { + requiresAuth: true + }, + component: AppLayout, + children: [ + { + path: "", + component: () => import("@/views/Overview") + }, + { + path: "/access-control/local-user-management", + name: "local-users", + component: () => import("@/views/AccessControl/LocalUserManagement") + } + ] }, { - path: "/access-control/local-user-management", - name: "local-users", - component: () => import("@/views/AccessControl/LocalUserManagement") + path: "/login", + name: "login", + component: () => import("@/views/Login") } - // { - // path: "/about", - // 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(/* webpackChunkName: "about" */ "../views/About.vue") - // } ]; const router = new VueRouter({ @@ -32,4 +39,16 @@ const router = new VueRouter({ linkExactActiveClass: "nav__link--current" }); +router.beforeEach((to, from, next) => { + if (to.matched.some(record => record.meta.requiresAuth)) { + if (store.getters["authentication/isLoggedIn"]) { + next(); + return; + } + next("/login"); + } else { + next(); + } +}); + export default router; diff --git a/src/store/api.js b/src/store/api.js index d40ad0ad..39a6355c 100644 --- a/src/store/api.js +++ b/src/store/api.js @@ -2,15 +2,9 @@ import Axios from "axios"; const api = Axios.create(); -// TODO: this is a temporary workaround until -// authentication with login is working -const username = process.env.VUE_APP_USERNAME; -const password = process.env.VUE_APP_PASSWORD; -if (username && password) { - api.defaults.auth = {}; - api.defaults.auth.username = username; - api.defaults.auth.password = password; -} +// TODO: Permanent authentication solution +// Using defaults to set auth for sending +// auth object in header export default { get(path) { @@ -30,5 +24,6 @@ export default { }, all(promises) { return Axios.all(promises); - } + }, + defaults: api.defaults }; diff --git a/src/store/index.js b/src/store/index.js index af06a471..b4be6ccd 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1,8 +1,9 @@ import Vue from "vue"; import Vuex from "vuex"; -import LocalUserManagementStore from "./modules/AccessControl/LocalUserMangementStore"; import GlobalStore from "./modules/GlobalStore"; +import AuthenticationStore from "./modules/Authentication/AuthenticanStore"; +import LocalUserManagementStore from "./modules/AccessControl/LocalUserMangementStore"; Vue.use(Vuex); @@ -12,6 +13,7 @@ export default new Vuex.Store({ actions: {}, modules: { global: GlobalStore, + authentication: AuthenticationStore, localUsers: LocalUserManagementStore } }); diff --git a/src/store/modules/Authentication/AuthenticanStore.js b/src/store/modules/Authentication/AuthenticanStore.js new file mode 100644 index 00000000..828c3cc8 --- /dev/null +++ b/src/store/modules/Authentication/AuthenticanStore.js @@ -0,0 +1,56 @@ +import api from "../../api"; + +const AuthenticationStore = { + namespaced: true, + state: { + auth: {}, + status: "", + token: sessionStorage.getItem("token") || "" + }, + getters: { + authStatus: state => state.status, + isLoggedIn: state => !!state.token + }, + mutations: { + authRequest(state) { + state.status = "loading"; + }, + authSuccess(state, token, auth) { + state.status = "authenicated"; + state.auth = auth; + state.token = token; + }, + authError(state) { + state.status = "error"; + }, + logout(state) { + state.status = ""; + state.token = ""; + } + }, + actions: { + login({ commit }, auth) { + commit("authRequest"); + return api + .post("/login", auth) + .then(response => { + const token = response.data.token; + sessionStorage.setItem("token", token); + api.defaults.auth = auth; // TODO Permanent Solution + commit("authSuccess", token, auth); + }) + .catch(error => { + commit("authError"); + sessionStorage.removeItem("token"); + throw new Error(error); + }); + }, + logout({ commit }) { + commit("logout"); + sessionStorage.removeItem("token"); + api.defaults.auth = {}; // Permanent solution + } + } +}; + +export default AuthenticationStore; diff --git a/src/views/Login/Login.vue b/src/views/Login/Login.vue new file mode 100644 index 00000000..f122d289 --- /dev/null +++ b/src/views/Login/Login.vue @@ -0,0 +1,111 @@ +<template> + <b-container class="login-container" fluid> + <b-row class="login-row" align-v="center"> + <b-col class="login-branding mt-5 mb-5" md="6"> + <div class="login-branding__container"> + <img + class="logo" + width="200px" + src="@/assets/images/openbmc-logo.svg" + alt="" + /> + <h1>OpenBMC</h1> + </div> + </b-col> + + <b-col class="login-form" md="6"> + <b-form @submit.prevent="login"> + <b-form-group + id="username-group" + label="Username" + label-for="username" + > + <b-form-input id="username" v-model="username" type="text" required> + </b-form-input> + </b-form-group> + + <b-form-group + id="password-group" + label="Password" + label-for="password" + > + <b-form-input + id="password" + v-model="password" + type="password" + required + > + </b-form-input> + </b-form-group> + + <b-button type="submit" variant="primary">Login</b-button> + </b-form> + </b-col> + </b-row> + </b-container> +</template> + +<script> +export default { + name: "Login", + data() { + return { + username: "", + password: "" + }; + }, + methods: { + login: function() { + const username = this.username; + const password = this.password; + this.$store + .dispatch("authentication/login", { username, password }) + .then(() => this.$router.push("/")) + .catch(error => console.log(error)); + } + } +}; +</script> + +<style lang="scss" scoped> +@import "~bootstrap/scss/functions"; +@import "~bootstrap/scss/variables"; +@import "~bootstrap/scss/mixins"; + +.login-container { + @include media-breakpoint-up(md) { + background: linear-gradient( + to right, + var(--light) 50%, + var(--secondary-light) 50% + ); + } +} + +.login-row { + @include media-breakpoint-up(md) { + min-height: 100vh; + } +} + +.login-branding__container { + @include media-breakpoint-up(md) { + float: right; + margin-right: 4rem; + } +} + +.login-form form { + max-width: 360px; + margin-right: auto; + margin-left: auto; + + @include media-breakpoint-up(md) { + margin-left: 4rem; + } +} + +.login-branding { + text-align: center; +} +</style> diff --git a/src/views/Login/index.js b/src/views/Login/index.js new file mode 100644 index 00000000..7fc2b5ca --- /dev/null +++ b/src/views/Login/index.js @@ -0,0 +1,2 @@ +import Login from "./Login.vue"; +export default Login; |