1<template> 2 <b-container fluid="xl"> 3 <page-title /> 4 <b-row> 5 <b-col md="8" xl="6"> 6 <alert variant="info" class="mb-4"> 7 <span> 8 {{ $t('pageDateTime.alert.message') }} 9 <b-link to="/profile-settings"> 10 {{ $t('pageDateTime.alert.link') }}</b-link 11 > 12 </span> 13 </alert> 14 </b-col> 15 </b-row> 16 <page-section> 17 <b-row> 18 <b-col lg="3"> 19 <dl> 20 <dt>{{ $t('pageDateTime.form.date') }}</dt> 21 <dd v-if="bmcTime">{{ $filters.formatDate(bmcTime) }}</dd> 22 <dd v-else>--</dd> 23 </dl> 24 </b-col> 25 <b-col lg="3"> 26 <dl> 27 <dt>{{ $t('pageDateTime.form.time.label') }}</dt> 28 <dd v-if="bmcTime">{{ $filters.formatTime(bmcTime) }}</dd> 29 <dd v-else>--</dd> 30 </dl> 31 </b-col> 32 </b-row> 33 </page-section> 34 <page-section :section-title="$t('pageDateTime.configureSettings')"> 35 <b-form novalidate @submit.prevent="submitForm"> 36 <b-form-group 37 label="Configure date and time" 38 :disabled="loading" 39 label-sr-only 40 > 41 <b-form-radio 42 v-model="form.configurationSelected" 43 value="manual" 44 data-test-id="dateTime-radio-configureManual" 45 > 46 {{ $t('pageDateTime.form.manual') }} 47 </b-form-radio> 48 <b-row class="mt-3 ml-3"> 49 <b-col sm="6" lg="4" xl="3"> 50 <b-form-group 51 :label="$t('pageDateTime.form.date')" 52 label-for="input-manual-date" 53 > 54 <b-form-text id="date-format-help">YYYY-MM-DD</b-form-text> 55 <b-input-group> 56 <b-form-input 57 id="input-manual-date" 58 v-model="form.manual.date" 59 :state="getValidationState(v$.form.manual.date)" 60 :disabled="ntpOptionSelected" 61 data-test-id="dateTime-input-manualDate" 62 class="form-control-with-button" 63 @blur="v$.form.manual.date.$touch()" 64 /> 65 <b-form-invalid-feedback role="alert"> 66 <div v-if="!v$.form.manual.date.pattern"> 67 {{ $t('global.form.invalidFormat') }} 68 </div> 69 <div v-if="!v$.form.manual.date.required"> 70 {{ $t('global.form.fieldRequired') }} 71 </div> 72 </b-form-invalid-feedback> 73 <b-form-datepicker 74 v-model="form.manual.date" 75 class="btn-datepicker btn-icon-only" 76 button-only 77 right 78 :hide-header="true" 79 :locale="locale" 80 :label-help=" 81 $t('global.calendar.useCursorKeysToNavigateCalendarDates') 82 " 83 :title="$t('global.calendar.selectDate')" 84 :disabled="ntpOptionSelected" 85 button-variant="link" 86 aria-controls="input-manual-date" 87 > 88 <template #button-content> 89 <icon-calendar /> 90 <span class="sr-only"> 91 {{ $t('global.calendar.selectDate') }} 92 </span> 93 </template> 94 </b-form-datepicker> 95 </b-input-group> 96 </b-form-group> 97 </b-col> 98 <b-col sm="6" lg="4" xl="3"> 99 <b-form-group 100 :label="$t('pageDateTime.form.time.timezone', { timezone })" 101 label-for="input-manual-time" 102 > 103 <b-form-text id="time-format-help">HH:MM</b-form-text> 104 <b-input-group> 105 <b-form-input 106 id="input-manual-time" 107 v-model="form.manual.time" 108 :state="getValidationState(v$.form.manual.time)" 109 :disabled="ntpOptionSelected" 110 data-test-id="dateTime-input-manualTime" 111 @blur="v$.form.manual.time.$touch()" 112 /> 113 <b-form-invalid-feedback role="alert"> 114 <div v-if="!v$.form.manual.time.pattern"> 115 {{ $t('global.form.invalidFormat') }} 116 </div> 117 <div v-if="!v$.form.manual.time.required"> 118 {{ $t('global.form.fieldRequired') }} 119 </div> 120 </b-form-invalid-feedback> 121 </b-input-group> 122 </b-form-group> 123 </b-col> 124 </b-row> 125 <b-form-radio 126 v-model="form.configurationSelected" 127 value="ntp" 128 data-test-id="dateTime-radio-configureNTP" 129 > 130 NTP 131 </b-form-radio> 132 <b-row class="mt-3 ml-3"> 133 <b-col sm="6" lg="4" xl="3"> 134 <b-form-group 135 :label="$t('pageDateTime.form.ntpServers.server1')" 136 label-for="input-ntp-1" 137 > 138 <b-input-group> 139 <b-form-input 140 id="input-ntp-1" 141 v-model="form.ntp.firstAddress" 142 :state="getValidationState(v$.form.ntp.firstAddress)" 143 :disabled="manualOptionSelected" 144 data-test-id="dateTime-input-ntpServer1" 145 @blur="v$.form.ntp.firstAddress.$touch()" 146 /> 147 <b-form-invalid-feedback role="alert"> 148 <div v-if="!v$.form.ntp.firstAddress.required"> 149 {{ $t('global.form.fieldRequired') }} 150 </div> 151 </b-form-invalid-feedback> 152 </b-input-group> 153 </b-form-group> 154 </b-col> 155 <b-col sm="6" lg="4" xl="3"> 156 <b-form-group 157 :label="$t('pageDateTime.form.ntpServers.server2')" 158 label-for="input-ntp-2" 159 > 160 <b-input-group> 161 <b-form-input 162 id="input-ntp-2" 163 v-model="form.ntp.secondAddress" 164 :disabled="manualOptionSelected" 165 data-test-id="dateTime-input-ntpServer2" 166 /> 167 </b-input-group> 168 </b-form-group> 169 </b-col> 170 <b-col sm="6" lg="4" xl="3"> 171 <b-form-group 172 :label="$t('pageDateTime.form.ntpServers.server3')" 173 label-for="input-ntp-3" 174 > 175 <b-input-group> 176 <b-form-input 177 id="input-ntp-3" 178 v-model="form.ntp.thirdAddress" 179 :disabled="manualOptionSelected" 180 data-test-id="dateTime-input-ntpServer3" 181 /> 182 </b-input-group> 183 </b-form-group> 184 </b-col> 185 </b-row> 186 <b-button 187 variant="primary" 188 type="submit" 189 data-test-id="dateTime-button-saveSettings" 190 > 191 {{ $t('global.action.saveSettings') }} 192 </b-button> 193 </b-form-group> 194 </b-form> 195 </page-section> 196 </b-container> 197</template> 198 199<script> 200import Alert from '@/components/Global/Alert'; 201import IconCalendar from '@carbon/icons-vue/es/calendar/20'; 202import PageTitle from '@/components/Global/PageTitle'; 203import PageSection from '@/components/Global/PageSection'; 204 205import BVToastMixin from '@/components/Mixins/BVToastMixin'; 206import LoadingBarMixin, { loading } from '@/components/Mixins/LoadingBarMixin'; 207import LocalTimezoneLabelMixin from '@/components/Mixins/LocalTimezoneLabelMixin'; 208import VuelidateMixin from '@/components/Mixins/VuelidateMixin.js'; 209import { useVuelidate } from '@vuelidate/core'; 210 211import { mapState } from 'vuex'; 212import { requiredIf, helpers } from '@vuelidate/validators'; 213import { useI18n } from 'vue-i18n'; 214 215const isoDateRegex = /([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))/; 216const isoTimeRegex = /^(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$/; 217 218export default { 219 name: 'DateTime', 220 components: { Alert, IconCalendar, PageTitle, PageSection }, 221 mixins: [ 222 BVToastMixin, 223 LoadingBarMixin, 224 LocalTimezoneLabelMixin, 225 VuelidateMixin, 226 ], 227 beforeRouteLeave(to, from, next) { 228 this.hideLoader(); 229 next(); 230 }, 231 setup() { 232 return { 233 v$: useVuelidate(), 234 }; 235 }, 236 data() { 237 return { 238 $t: useI18n().t, 239 locale: this.$store.getters['global/languagePreference'], 240 form: { 241 configurationSelected: 'manual', 242 manual: { 243 date: '', 244 time: '', 245 }, 246 ntp: { firstAddress: '', secondAddress: '', thirdAddress: '' }, 247 }, 248 loading, 249 }; 250 }, 251 validations() { 252 return { 253 form: { 254 manual: { 255 date: { 256 required: requiredIf(function () { 257 return this.form.configurationSelected === 'manual'; 258 }), 259 pattern: helpers.regex('pattern', isoDateRegex), 260 }, 261 time: { 262 required: requiredIf(function () { 263 return this.form.configurationSelected === 'manual'; 264 }), 265 pattern: helpers.regex('pattern', isoTimeRegex), 266 }, 267 }, 268 ntp: { 269 firstAddress: { 270 required: requiredIf(function () { 271 return this.form.configurationSelected === 'ntp'; 272 }), 273 }, 274 }, 275 }, 276 }; 277 }, 278 computed: { 279 ...mapState('dateTime', ['ntpServers', 'isNtpProtocolEnabled']), 280 bmcTime() { 281 return this.$store.getters['global/bmcTime']; 282 }, 283 ntpOptionSelected() { 284 return this.form.configurationSelected === 'ntp'; 285 }, 286 manualOptionSelected() { 287 return this.form.configurationSelected === 'manual'; 288 }, 289 isUtcDisplay() { 290 return this.$store.getters['global/isUtcDisplay']; 291 }, 292 timezone() { 293 if (this.isUtcDisplay) { 294 return 'UTC'; 295 } 296 return this.localOffset(); 297 }, 298 }, 299 watch: { 300 ntpServers() { 301 this.setNtpValues(); 302 }, 303 manualDate() { 304 this.emitChange(); 305 }, 306 bmcTime() { 307 this.form.manual.date = this.$filters.formatDate( 308 this.$store.getters['global/bmcTime'], 309 ); 310 this.form.manual.time = this.$filters 311 .formatTime(this.$store.getters['global/bmcTime']) 312 .slice(0, 5); 313 }, 314 }, 315 created() { 316 this.startLoader(); 317 this.setNtpValues(); 318 Promise.all([ 319 this.$store.dispatch('global/getBmcTime'), 320 this.$store.dispatch('dateTime/getNtpData'), 321 ]).finally(() => this.endLoader()); 322 }, 323 methods: { 324 emitChange() { 325 if (this.v$.$invalid) return; 326 this.v$.$reset(); //reset to re-validate on blur 327 this.$emit('change', { 328 manualDate: this.manualDate ? new Date(this.manualDate) : null, 329 }); 330 }, 331 setNtpValues() { 332 this.form.configurationSelected = this.isNtpProtocolEnabled 333 ? 'ntp' 334 : 'manual'; 335 [ 336 this.form.ntp.firstAddress = '', 337 this.form.ntp.secondAddress = '', 338 this.form.ntp.thirdAddress = '', 339 ] = [this.ntpServers[0], this.ntpServers[1], this.ntpServers[2]]; 340 }, 341 submitForm() { 342 this.v$.$touch(); 343 if (this.v$.$invalid) return; 344 this.startLoader(); 345 346 let dateTimeForm = {}; 347 let isNTPEnabled = this.form.configurationSelected === 'ntp'; 348 349 if (!isNTPEnabled) { 350 const isUtcDisplay = this.$store.getters['global/isUtcDisplay']; 351 let date; 352 353 dateTimeForm.ntpProtocolEnabled = false; 354 355 if (isUtcDisplay) { 356 // Create UTC Date 357 date = this.getUtcDate(this.form.manual.date, this.form.manual.time); 358 } else { 359 // Create local Date 360 date = new Date(`${this.form.manual.date} ${this.form.manual.time}`); 361 } 362 363 dateTimeForm.updatedDateTime = date.toISOString(); 364 } else { 365 dateTimeForm.ntpProtocolEnabled = true; 366 367 const ntpArray = [ 368 this.form.ntp.firstAddress, 369 this.form.ntp.secondAddress, 370 this.form.ntp.thirdAddress, 371 ]; 372 373 // Filter the ntpArray to remove empty strings, 374 // per Redfish spec there should be no empty strings or null on the ntp array. 375 const ntpArrayFiltered = ntpArray.filter((x) => x); 376 377 dateTimeForm.ntpServersArray = [...ntpArrayFiltered]; 378 379 [this.ntpServers[0], this.ntpServers[1], this.ntpServers[2]] = [ 380 ...dateTimeForm.ntpServersArray, 381 ]; 382 383 this.setNtpValues(); 384 } 385 386 this.$store 387 .dispatch('dateTime/updateDateTime', dateTimeForm) 388 .then((success) => { 389 this.successToast(success); 390 if (!isNTPEnabled) return; 391 // Shift address up if second address is empty 392 // to avoid refreshing after delay when updating NTP 393 if (!this.form.ntp.secondAddress && this.form.ntp.thirdAddres) { 394 this.form.ntp.secondAddress = this.form.ntp.thirdAddres; 395 this.form.ntp.thirdAddress = ''; 396 } 397 }) 398 .then(() => { 399 this.$store.dispatch('global/getBmcTime'); 400 }) 401 .catch(({ message }) => this.errorToast(message)) 402 .finally(() => { 403 this.v$.form.$reset(); 404 this.endLoader(); 405 }); 406 }, 407 getUtcDate(date, time) { 408 // Split user input string values to create 409 // a UTC Date object 410 const datesArray = date.split('-'); 411 const timeArray = time.split(':'); 412 let utcDate = Date.UTC( 413 datesArray[0], // User input year 414 //UTC expects zero-index month value 0-11 (January-December) 415 //for reference https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/UTC#Parameters 416 parseInt(datesArray[1]) - 1, // User input month 417 datesArray[2], // User input day 418 timeArray[0], // User input hour 419 timeArray[1], // User input minute 420 ); 421 return new Date(utcDate); 422 }, 423 }, 424}; 425</script> 426