summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSukanya Pandey <sukapan1@in.ibm.com>2020-04-28 17:48:28 +0300
committerDerick Montague <derick.montague@ibm.com>2020-06-17 23:48:38 +0300
commitb1f559f03e3f464c1b8b19a9327158be0ecafe62 (patch)
tree6af8b850a9aebf58a2f4a08ac9832872e0f80cb3
parent5918b48a0530a43a4dd9ee1a3f134846c948011e (diff)
downloadwebui-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.scss9
-rw-r--r--src/components/AppHeader/AppHeader.vue116
-rw-r--r--src/locales/en-US.json11
-rw-r--r--src/router/index.js8
-rw-r--r--src/store/modules/Authentication/AuthenticanStore.js1
-rw-r--r--src/store/modules/GlobalStore.js9
-rw-r--r--src/views/Login/Login.vue2
-rw-r--r--src/views/ProfileSettings/ProfileSettings.vue162
-rw-r--r--src/views/ProfileSettings/index.js2
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 0eb310f6..c7d39548 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 a755a628..39d52b83 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 1ccc330e..84a2a604 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 f67d5ee4..22662d71 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 7a0c5ba3..407c2b57 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 42e9e2bf..55b07965 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 4d8f2484..3993800c 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 00000000..df74b4b7
--- /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 00000000..d6589c72
--- /dev/null
+++ b/src/views/ProfileSettings/index.js
@@ -0,0 +1,2 @@
+import ProfileSettings from './ProfileSettings.vue';
+export default ProfileSettings;