diff options
author | Sukanya Pandey <sukapan1@in.ibm.com> | 2020-04-28 17:48:28 +0300 |
---|---|---|
committer | Derick Montague <derick.montague@ibm.com> | 2020-06-17 23:48:38 +0300 |
commit | b1f559f03e3f464c1b8b19a9327158be0ecafe62 (patch) | |
tree | 6af8b850a9aebf58a2f4a08ac9832872e0f80cb3 | |
parent | 5918b48a0530a43a4dd9ee1a3f134846c948011e (diff) | |
download | webui-vue-b1f559f03e3f464c1b8b19a9327158be0ecafe62.tar.xz |
Profile settings page
-To set the profile by setting password.
-This commit adds a profile page which allows the user to change their
password.
In the future, the profile page will also contain user settings like
language and timezone.
The API called to change the user's
password is '/redfish/v1/AccountService/Accounts/<userName>'
Signed-off-by: Sukanya Pandey <sukapan1@in.ibm.com>
Change-Id: Ie54a54beff8c85bc9ac5af21c35edc481b34cf44
-rw-r--r-- | src/assets/styles/vendor-overrides/bootstrap/_dropdown.scss | 9 | ||||
-rw-r--r-- | src/components/AppHeader/AppHeader.vue | 116 | ||||
-rw-r--r-- | src/locales/en-US.json | 11 | ||||
-rw-r--r-- | src/router/index.js | 8 | ||||
-rw-r--r-- | src/store/modules/Authentication/AuthenticanStore.js | 1 | ||||
-rw-r--r-- | src/store/modules/GlobalStore.js | 9 | ||||
-rw-r--r-- | src/views/Login/Login.vue | 2 | ||||
-rw-r--r-- | src/views/ProfileSettings/ProfileSettings.vue | 162 | ||||
-rw-r--r-- | src/views/ProfileSettings/index.js | 2 |
9 files changed, 264 insertions, 56 deletions
diff --git a/src/assets/styles/vendor-overrides/bootstrap/_dropdown.scss b/src/assets/styles/vendor-overrides/bootstrap/_dropdown.scss index 0eb310f60..c7d39548c 100644 --- a/src/assets/styles/vendor-overrides/bootstrap/_dropdown.scss +++ b/src/assets/styles/vendor-overrides/bootstrap/_dropdown.scss @@ -9,11 +9,12 @@ } } +// Adding component style to global stylesheet because +// single-file component scoped styles aren't +// being applied to dynamically appended elements +// The overflow menu should be above the table + .table-filter { - // Adding component style to global stylesheet because - // single-file component scoped styles aren't - // being applied to dynamically appended elements - // The overflow menu should be above the table .dropdown-menu { z-index: $zindex-dropdown + 1; padding: 0; diff --git a/src/components/AppHeader/AppHeader.vue b/src/components/AppHeader/AppHeader.vue index a755a6289..39d52b83e 100644 --- a/src/components/AppHeader/AppHeader.vue +++ b/src/components/AppHeader/AppHeader.vue @@ -43,11 +43,19 @@ <icon-renew /> </b-button> </li> - <li> - <b-button id="app-header-logout" variant="link" @click="logout"> - {{ $t('appHeader.logOut') }} - <icon-avatar /> - </b-button> + <li class="nav-item"> + <b-dropdown id="app-header-user" variant="link" right> + <template v-slot:button-content> + <icon-avatar /> + {{ username }} + </template> + <b-dropdown-item to="/profile-settings" + >{{ $t('appHeader.profileSettings') }} + </b-dropdown-item> + <b-dropdown-item @click="logout">{{ + $t('appHeader.logOut') + }}</b-dropdown-item> + </b-dropdown> </li> </b-navbar-nav> </b-navbar> @@ -110,6 +118,9 @@ export default { default: return 'secondary'; } + }, + username() { + return this.$store.getters['global/username']; } }, created() { @@ -142,64 +153,71 @@ export default { }; </script> -<style lang="scss" scoped> +<style lang="scss"> @import 'src/assets/styles/helpers'; -.link-skip-nav { - position: absolute; - top: -60px; - left: 0.5rem; - z-index: $zindex-popover; - transition: $duration--moderate-01 $exit-easing--expressive; - &:focus { - top: 0.5rem; - transition-timing-function: $entrance-easing--expressive; +.app-header { + .link-skip-nav { + position: absolute; + top: -60px; + left: 0.5rem; + z-index: $zindex-popover; + transition: $duration--moderate-01 $exit-easing--expressive; + &:focus { + top: 0.5rem; + transition-timing-function: $entrance-easing--expressive; + } } -} -.navbar-dark { - .navbar-text, - .nav-link, - .btn-link { - color: $white !important; - fill: currentColor; + .navbar-dark { + .navbar-text, + .nav-link, + .btn-link { + color: $white !important; + fill: currentColor; + } } -} - -.nav-item { - fill: $light; -} -.navbar { - padding: 0; - height: $header-height; - overflow: hidden; - - .btn-link { - padding: $spacer / 2; + .nav-item { + fill: $light; } -} -.navbar-nav { - padding: 0 $spacer; -} + .navbar { + padding: 0; + height: $header-height; -.nav-trigger { - fill: $light; - width: $header-height; - height: $header-height; - transition: none; + .btn-link { + padding: $spacer / 2; + } + } - svg { - margin: 0; + .navbar-nav { + padding: 0 $spacer; } - &:hover { + .nav-trigger { fill: $light; - background-color: $dark; + width: $header-height; + height: $header-height; + transition: none; + + svg { + margin: 0; + } + + &:hover { + fill: $light; + background-color: $dark; + } + + @include media-breakpoint-up($responsive-layout-bp) { + display: none; + } } - @include media-breakpoint-up($responsive-layout-bp) { - display: none; + .dropdown { + .dropdown-menu { + margin-top: 7px; + } } } </style> diff --git a/src/locales/en-US.json b/src/locales/en-US.json index 1ccc330e4..84a2a604c 100644 --- a/src/locales/en-US.json +++ b/src/locales/en-US.json @@ -64,6 +64,7 @@ "health": "Health", "logOut": "Log out", "power": "Power", + "profileSettings": "@:appPageTitle.profileSettings", "refresh": "Refresh", "skipToContent": "Skip to content" }, @@ -98,6 +99,7 @@ "managePowerUsage": "Manage power usage", "networkSettings": "Network settings", "overview": "Overview", + "profileSettings":"Profile settings", "rebootBmc": "Reboot BMC", "sensors": "Sensors", "serverLed": "Server LED", @@ -299,6 +301,15 @@ "solConsole": "Serial over LAN console" } }, + "profileSettings": { + "changePassword": "Change password", + "confirmPassword": "Confirm new password", + "newPassword": "New password", + "newPassLabelTextInfo": "Password must be between %{min} - %{max} characters", + "passwordsDoNotMatch": "Passwords do not match", + "profileInfoTitle": "Profile information", + "username": "Username" + }, "pageManagePowerUsage": { "description": "Set a power cap to keep power consumption at or below the specified value in watts", "powerCapLabel": "Power cap value (in watts)", diff --git a/src/router/index.js b/src/router/index.js index f67d5ee44..22662d71a 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -24,6 +24,14 @@ const routes = [ } }, { + path: '/profile-settings', + name: 'profile-settings', + component: () => import('@/views/ProfileSettings'), + meta: { + title: 'appPageTitle.profileSettings' + } + }, + { path: '/health/event-logs', name: 'event-logs', component: () => import('@/views/Health/EventLogs'), diff --git a/src/store/modules/Authentication/AuthenticanStore.js b/src/store/modules/Authentication/AuthenticanStore.js index 7a0c5ba3a..407c2b575 100644 --- a/src/store/modules/Authentication/AuthenticanStore.js +++ b/src/store/modules/Authentication/AuthenticanStore.js @@ -23,6 +23,7 @@ const AuthenticationStore = { }, logout() { Cookies.remove('XSRF-TOKEN'); + localStorage.removeItem('storedUsername'); } }, actions: { diff --git a/src/store/modules/GlobalStore.js b/src/store/modules/GlobalStore.js index 42e9e2bf9..55b07965e 100644 --- a/src/store/modules/GlobalStore.js +++ b/src/store/modules/GlobalStore.js @@ -31,19 +31,22 @@ const GlobalStore = { state: { bmcTime: null, hostStatus: 'unreachable', - languagePreference: localStorage.getItem('storedLanguage') || 'en-US' + languagePreference: localStorage.getItem('storedLanguage') || 'en-US', + username: localStorage.getItem('storedUsername') }, getters: { hostStatus: state => state.hostStatus, bmcTime: state => state.bmcTime, - languagePreference: state => state.languagePreference + languagePreference: state => state.languagePreference, + username: state => state.username }, mutations: { setBmcTime: (state, bmcTime) => (state.bmcTime = bmcTime), setHostStatus: (state, hostState) => (state.hostStatus = hostStateMapper(hostState)), setLanguagePreference: (state, language) => - (state.languagePreference = language) + (state.languagePreference = language), + setUsername: (state, username) => (state.username = username) }, actions: { async getBmcTime({ commit }) { diff --git a/src/views/Login/Login.vue b/src/views/Login/Login.vue index 4d8f24844..3993800c8 100644 --- a/src/views/Login/Login.vue +++ b/src/views/Login/Login.vue @@ -138,6 +138,8 @@ export default { .then(() => this.$router.push('/')) .then(() => { localStorage.setItem('storedLanguage', i18n.locale); + localStorage.setItem('storedUsername', username); + this.$store.commit('global/setUsername', username); this.$store.commit('global/setLanguagePreference', i18n.locale); }) .catch(error => console.log(error)) diff --git a/src/views/ProfileSettings/ProfileSettings.vue b/src/views/ProfileSettings/ProfileSettings.vue new file mode 100644 index 000000000..df74b4b71 --- /dev/null +++ b/src/views/ProfileSettings/ProfileSettings.vue @@ -0,0 +1,162 @@ +<template> + <b-container fluid="xl"> + <page-title /> + + <b-row> + <b-col md="8" lg="8" xl="6"> + <page-section :section-title="$t('profileSettings.profileInfoTitle')"> + <dl> + <dt>{{ $t('profileSettings.username') }}</dt> + <dd> + {{ username }} + </dd> + </dl> + </page-section> + </b-col> + </b-row> + + <b-form @submit.prevent="submitForm"> + <b-row> + <b-col sm="8" md="6" xl="3"> + <page-section :section-title="$t('profileSettings.changePassword')"> + <b-form-group + id="input-group-1" + :label="$t('profileSettings.newPassword')" + label-for="input-1" + > + <b-form-text id="password-help-block"> + {{ + $t('pageLocalUserManagement.modal.passwordMustBeBetween', { + min: passwordRequirements.minLength, + max: passwordRequirements.maxLength + }) + }} + </b-form-text> + <input-password-toggle> + <b-form-input + id="password" + v-model="form.newPassword" + type="password" + aria-describedby="password-help-block" + :state="getValidationState($v.form.newPassword)" + @input="$v.form.newPassword.$touch()" + /> + <b-form-invalid-feedback role="alert"> + <template v-if="!$v.form.newPassword.required"> + {{ $t('global.form.fieldRequired') }} + </template> + <template + v-if=" + !$v.form.newPassword.minLength || + !$v.form.newPassword.maxLength + " + > + {{ + $t('profileSettings.newPassLabelTextInfo', { + min: passwordRequirements.minLength, + max: passwordRequirements.maxLength + }) + }} + </template> + </b-form-invalid-feedback> + </input-password-toggle> + </b-form-group> + <b-form-group + id="input-group-2" + :label="$t('profileSettings.confirmPassword')" + label-for="input-2" + > + <input-password-toggle> + <b-form-input + id="password-confirmation" + v-model="form.confirmPassword" + type="password" + :state="getValidationState($v.form.confirmPassword)" + @input="$v.form.confirmPassword.$touch()" + /> + <b-form-invalid-feedback role="alert"> + <template v-if="!$v.form.confirmPassword.required"> + {{ $t('global.form.fieldRequired') }} + </template> + <template v-else-if="!$v.form.confirmPassword.sameAsPassword"> + {{ $t('profileSettings.passwordsDoNotMatch') }} + </template> + </b-form-invalid-feedback> + </input-password-toggle> + </b-form-group> + </page-section> + </b-col> + </b-row> + <b-button variant="primary" type="submit"> + {{ $t('global.action.save') }} + </b-button> + </b-form> + </b-container> +</template> + +<script> +import PageTitle from '@/components/Global/PageTitle'; +import PageSection from '@/components/Global/PageSection'; +import BVToastMixin from '@/components/Mixins/BVToastMixin'; +import VuelidateMixin from '@/components/Mixins/VuelidateMixin.js'; +import InputPasswordToggle from '@/components/Global/InputPasswordToggle'; +import { + maxLength, + minLength, + required, + sameAs +} from 'vuelidate/lib/validators'; + +export default { + name: 'ProfileSettings', + components: { PageTitle, PageSection, InputPasswordToggle }, + mixins: [BVToastMixin, VuelidateMixin], + data() { + return { + passwordRequirements: { + minLength: 8, + maxLength: 20 + }, + form: { + newPassword: '', + confirmPassword: '' + } + }; + }, + validations() { + return { + form: { + newPassword: { + required, + minLength: minLength(this.passwordRequirements.minLength), + maxLength: maxLength(this.passwordRequirements.maxLength) + }, + confirmPassword: { + required, + sameAsPassword: sameAs('newPassword') + } + } + }; + }, + computed: { + username() { + return this.$store.getters['global/username']; + } + }, + methods: { + submitForm() { + this.$v.$touch(); + if (this.$v.$invalid) return; + let userData = { + originalUsername: this.username, + password: this.form.newPassword + }; + + this.$store + .dispatch('localUsers/updateUser', userData) + .then(message => this.successToast(message)) + .catch(({ message }) => this.errorToast(message)); + } + } +}; +</script> diff --git a/src/views/ProfileSettings/index.js b/src/views/ProfileSettings/index.js new file mode 100644 index 000000000..d6589c720 --- /dev/null +++ b/src/views/ProfileSettings/index.js @@ -0,0 +1,2 @@ +import ProfileSettings from './ProfileSettings.vue'; +export default ProfileSettings; |