summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSukanya Pandey <sukapan1@in.ibm.com>2020-06-23 20:24:27 +0300
committerDerick Montague <derick.montague@ibm.com>2020-07-28 00:35:55 +0300
commitfc16f3c268a9d35f889f5770331d11e87ab16af3 (patch)
tree447e8f3b8b1eba2767e05ab016a46fd540e3a230
parenta2c99829a713e8411bda20091765ee972cd4d8b9 (diff)
downloadwebui-vue-fc16f3c268a9d35f889f5770331d11e87ab16af3.tar.xz
Add timezone to profile settings page
- Users will have two options to select a timezone. - UTC and browser offset timezone are the two options for the application. - date-fns and date-fns-tz is used for date and time manipulations because:- - The package size of library is smaller. - It allows for importing functions to work with the native date object rather than having to create a moment instance that carries a larger payload. Signed-off-by: Sukanya Pandey <sukapan1@in.ibm.com> Change-Id: I581803f230f501c0d34d0b53e7c2d89e8466ee60
-rw-r--r--package-lock.json20
-rw-r--r--package.json2
-rw-r--r--src/locales/en-US.json12
-rw-r--r--src/main.js39
-rw-r--r--src/store/modules/GlobalStore.js7
-rw-r--r--src/views/ProfileSettings/ProfileSettings.vue89
6 files changed, 134 insertions, 35 deletions
diff --git a/package-lock.json b/package-lock.json
index 03fb44fd..98709b26 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6004,10 +6004,14 @@
}
},
"date-fns": {
- "version": "1.30.1",
- "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz",
- "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==",
- "dev": true
+ "version": "2.14.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.14.0.tgz",
+ "integrity": "sha512-1zD+68jhFgDIM0rF05rcwYO8cExdNqxjq4xP1QKM60Q45mnO6zaMWB4tOzrIr4M4GSLntsKeE4c9Bdl2jhL/yw=="
+ },
+ "date-fns-tz": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-1.0.10.tgz",
+ "integrity": "sha512-cHQAz0/9uDABaUNDM80Mj1FL4ODlxs1xEY4b0DQuAooO2UdNKvDkNbV8ogLnxLbv02Ru1HXFcot0pVvDRBgptg=="
},
"de-indent": {
"version": "1.0.2",
@@ -11802,6 +11806,14 @@
"cli-cursor": "^2.1.0",
"date-fns": "^1.27.2",
"figures": "^2.0.0"
+ },
+ "dependencies": {
+ "date-fns": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz",
+ "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==",
+ "dev": true
+ }
}
},
"load-json-file": {
diff --git a/package.json b/package.json
index 115937bf..ae920659 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,8 @@
"bootstrap": "4.4.1",
"bootstrap-vue": "2.12.0",
"core-js": "3.3.2",
+ "date-fns": "2.14.0",
+ "date-fns-tz": "1.0.10",
"js-cookie": "2.2.1",
"lodash": "4.17.19",
"vue": "2.6.11",
diff --git a/src/locales/en-US.json b/src/locales/en-US.json
index 023efa55..63c75367 100644
--- a/src/locales/en-US.json
+++ b/src/locales/en-US.json
@@ -367,14 +367,22 @@
"solConsole": "Serial over LAN console"
}
},
- "profileSettings": {
+ "pageProfileSettings": {
+ "browserOffset": "Browser offset (%{timezone})",
"changePassword": "Change password",
"confirmPassword": "Confirm new password",
+ "defaultUTC": "Default (UTC)",
"newPassword": "New password",
"newPassLabelTextInfo": "Password must be between %{min} - %{max} characters",
"passwordsDoNotMatch": "Passwords do not match",
"profileInfoTitle": "Profile information",
- "username": "Username"
+ "timezone": "Timezone",
+ "timezoneDisplay": "Timezone display preference",
+ "timezoneDisplayDesc": "Select how time is displayed throughout the application",
+ "username": "Username",
+ "toast": {
+ "successSaveSettings": "Successfully saved account settings."
+ }
},
"pageManagePowerUsage": {
"description": "Set a power cap to keep power consumption at or below the specified value in watts",
diff --git a/src/main.js b/src/main.js
index 0c6d5b07..8336cb3d 100644
--- a/src/main.js
+++ b/src/main.js
@@ -33,23 +33,46 @@ import {
} from 'bootstrap-vue';
import Vuelidate from 'vuelidate';
import i18n from './i18n';
+import { format } from 'date-fns-tz';
// Filters
+Vue.filter('shortTimeZone', function(value) {
+ const longTZ = value
+ .toString()
+ .match(/\((.*)\)/)
+ .pop();
+ const regexNotUpper = /[*a-z ]/g;
+ return longTZ.replace(regexNotUpper, '');
+});
+
Vue.filter('formatDate', function(value) {
+ const isUtcDisplay = store.getters['global/isUtcDisplay'];
+
if (value instanceof Date) {
- return value.toISOString().substring(0, 10);
+ if (isUtcDisplay) {
+ return value.toISOString().substring(0, 10);
+ }
+ const pattern = `yyyy-MM-dd`;
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
+ return format(value, pattern, { timezone });
}
});
Vue.filter('formatTime', function(value) {
- const timeOptions = {
- hour: 'numeric',
- minute: 'numeric',
- second: 'numeric',
- timeZoneName: 'short'
- };
+ const isUtcDisplay = store.getters['global/isUtcDisplay'];
+
if (value instanceof Date) {
- return value.toLocaleTimeString('default', timeOptions);
+ if (isUtcDisplay) {
+ let timeOptions = {
+ timeZone: 'UTC',
+ hour12: false
+ };
+ return `${value.toLocaleTimeString('default', timeOptions)} UTC`;
+ }
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
+ const shortTz = Vue.filter('shortTimeZone')(value);
+ const pattern = `HH:mm:ss ('${shortTz}' O)`;
+ return format(value, pattern, { timezone }).replace('GMT', 'UTC');
}
});
diff --git a/src/store/modules/GlobalStore.js b/src/store/modules/GlobalStore.js
index 55b07965..39f3d1d4 100644
--- a/src/store/modules/GlobalStore.js
+++ b/src/store/modules/GlobalStore.js
@@ -32,12 +32,16 @@ const GlobalStore = {
bmcTime: null,
hostStatus: 'unreachable',
languagePreference: localStorage.getItem('storedLanguage') || 'en-US',
+ isUtcDisplay: localStorage.getItem('storedUtcDisplay')
+ ? JSON.parse(localStorage.getItem('storedUtcDisplay'))
+ : true,
username: localStorage.getItem('storedUsername')
},
getters: {
hostStatus: state => state.hostStatus,
bmcTime: state => state.bmcTime,
languagePreference: state => state.languagePreference,
+ isUtcDisplay: state => state.isUtcDisplay,
username: state => state.username
},
mutations: {
@@ -46,7 +50,8 @@ const GlobalStore = {
(state.hostStatus = hostStateMapper(hostState)),
setLanguagePreference: (state, language) =>
(state.languagePreference = language),
- setUsername: (state, username) => (state.username = username)
+ setUsername: (state, username) => (state.username = username),
+ setUtcTime: (state, isUtcDisplay) => (state.isUtcDisplay = isUtcDisplay)
},
actions: {
async getBmcTime({ commit }) {
diff --git a/src/views/ProfileSettings/ProfileSettings.vue b/src/views/ProfileSettings/ProfileSettings.vue
index 07aac198..2abee633 100644
--- a/src/views/ProfileSettings/ProfileSettings.vue
+++ b/src/views/ProfileSettings/ProfileSettings.vue
@@ -4,9 +4,11 @@
<b-row>
<b-col md="8" lg="8" xl="6">
- <page-section :section-title="$t('profileSettings.profileInfoTitle')">
+ <page-section
+ :section-title="$t('pageProfileSettings.profileInfoTitle')"
+ >
<dl>
- <dt>{{ $t('profileSettings.username') }}</dt>
+ <dt>{{ $t('pageProfileSettings.username') }}</dt>
<dd>
{{ username }}
</dd>
@@ -18,10 +20,12 @@
<b-form @submit.prevent="submitForm">
<b-row>
<b-col sm="8" md="6" xl="3">
- <page-section :section-title="$t('profileSettings.changePassword')">
+ <page-section
+ :section-title="$t('pageProfileSettings.changePassword')"
+ >
<b-form-group
id="input-group-1"
- :label="$t('profileSettings.newPassword')"
+ :label="$t('pageProfileSettings.newPassword')"
label-for="input-1"
>
<b-form-text id="password-help-block">
@@ -42,9 +46,6 @@
@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 ||
@@ -52,7 +53,7 @@
"
>
{{
- $t('profileSettings.newPassLabelTextInfo', {
+ $t('pageProfileSettings.newPassLabelTextInfo', {
min: passwordRequirements.minLength,
max: passwordRequirements.maxLength
})
@@ -63,7 +64,7 @@
</b-form-group>
<b-form-group
id="input-group-2"
- :label="$t('profileSettings.confirmPassword')"
+ :label="$t('pageProfileSettings.confirmPassword')"
label-for="input-2"
>
<input-password-toggle>
@@ -75,11 +76,8 @@
@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 v-if="!$v.form.confirmPassword.sameAsPassword">
+ {{ $t('pageProfileSettings.passwordsDoNotMatch') }}
</template>
</b-form-invalid-feedback>
</input-password-toggle>
@@ -87,16 +85,45 @@
</page-section>
</b-col>
</b-row>
+ <page-section :section-title="$t('pageProfileSettings.timezoneDisplay')">
+ <p>{{ $t('pageProfileSettings.timezoneDisplayDesc') }}</p>
+ <b-row>
+ <b-col md="9" lg="8" xl="9">
+ <b-form-group :label="$t('pageProfileSettings.timezone')">
+ <b-form-radio
+ v-model="form.isUtcDisplay"
+ :value="true"
+ @change="$v.form.isUtcDisplay.$touch()"
+ >
+ {{ $t('pageProfileSettings.defaultUTC') }}
+ </b-form-radio>
+ <b-form-radio
+ v-model="form.isUtcDisplay"
+ :value="false"
+ @change="$v.form.isUtcDisplay.$touch()"
+ >
+ {{
+ $t('pageProfileSettings.browserOffset', {
+ timezone
+ })
+ }}
+ </b-form-radio>
+ </b-form-group>
+ </b-col>
+ </b-row>
+ </page-section>
<b-button variant="primary" type="submit">
- {{ $t('global.action.save') }}
+ {{ $t('global.action.saveSettings') }}
</b-button>
</b-form>
</b-container>
</template>
<script>
+import i18n from '@/i18n';
import BVToastMixin from '@/components/Mixins/BVToastMixin';
import InputPasswordToggle from '@/components/Global/InputPasswordToggle';
+import { format } from 'date-fns-tz';
import {
maxLength,
minLength,
@@ -116,7 +143,8 @@ export default {
return {
form: {
newPassword: '',
- confirmPassword: ''
+ confirmPassword: '',
+ isUtcDisplay: this.$store.getters['global/isUtcDisplay']
}
};
},
@@ -126,6 +154,12 @@ export default {
},
passwordRequirements() {
return this.$store.getters['localUsers/accountPasswordRequirements'];
+ },
+ timezone() {
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
+ const shortTz = this.$options.filters.shortTimeZone(new Date());
+ const pattern = `'${shortTz}' O`;
+ return format(new Date(), pattern, { timezone }).replace('GMT', 'UTC');
}
},
created() {
@@ -137,21 +171,21 @@ export default {
validations() {
return {
form: {
+ isUtcDisplay: { required },
newPassword: {
- required,
minLength: minLength(this.passwordRequirements.minLength),
maxLength: maxLength(this.passwordRequirements.maxLength)
},
confirmPassword: {
- required,
sameAsPassword: sameAs('newPassword')
}
}
};
},
methods: {
- submitForm() {
- this.$v.$touch();
+ saveNewPasswordInputData() {
+ this.$v.form.confirmPassword.$touch();
+ this.$v.form.newPassword.$touch();
if (this.$v.$invalid) return;
let userData = {
originalUsername: this.username,
@@ -166,6 +200,21 @@ export default {
this.successToast(message);
})
.catch(({ message }) => this.errorToast(message));
+ },
+ saveTimeZonePrefrenceData() {
+ localStorage.setItem('storedUtcDisplay', this.form.isUtcDisplay);
+ this.$store.commit('global/setUtcTime', this.form.isUtcDisplay);
+ this.successToast(
+ i18n.t('pageProfileSettings.toast.successSaveSettings')
+ );
+ },
+ submitForm() {
+ if (this.form.confirmPassword || this.form.newPassword) {
+ this.saveNewPasswordInputData();
+ }
+ if (this.$v.form.isUtcDisplay.$anyDirty) {
+ this.saveTimeZonePrefrenceData();
+ }
}
}
};