summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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;