diff options
-rw-r--r-- | package-lock.json | 20 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | src/locales/en-US.json | 12 | ||||
-rw-r--r-- | src/main.js | 39 | ||||
-rw-r--r-- | src/store/modules/GlobalStore.js | 7 | ||||
-rw-r--r-- | src/views/ProfileSettings/ProfileSettings.vue | 89 |
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(); + } } } }; |