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.$invalid"> 67 {{ $t('global.form.invalidFormat') }} 68 </div> 69 <div v-if="v$.form.manual.date.required.$invalid"> 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.$invalid"> 115 {{ $t('global.form.invalidFormat') }} 116 </div> 117 <div v-if="v$.form.manual.time.required.$invalid"> 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.$invalid"> 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 } from '@vuelidate/validators'; 213import { helpers } from 'vuelidate/lib/validators'; 214import { useI18n } from 'vue-i18n'; 215 216const isoDateRegex = /([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))/; 217const isoTimeRegex = /^(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$/; 218 219export default { 220 name: 'DateTime', 221 components: { Alert, IconCalendar, PageTitle, PageSection }, 222 mixins: [ 223 BVToastMixin, 224 LoadingBarMixin, 225 LocalTimezoneLabelMixin, 226 VuelidateMixin, 227 ], 228 beforeRouteLeave(to, from, next) { 229 this.hideLoader(); 230 next(); 231 }, 232 setup() { 233 return { 234 v$: useVuelidate(), 235 }; 236 }, 237 data() { 238 return { 239 $t: useI18n().t, 240 locale: this.$store.getters['global/languagePreference'], 241 form: { 242 configurationSelected: 'manual', 243 manual: { 244 date: '', 245 time: '', 246 }, 247 ntp: { firstAddress: '', secondAddress: '', thirdAddress: '' }, 248 }, 249 loading, 250 }; 251 }, 252 validations() { 253 return { 254 form: { 255 manual: { 256 date: { 257 required: requiredIf(function () { 258 return this.form.configurationSelected === 'manual'; 259 }), 260 pattern: helpers.regex('pattern', isoDateRegex), 261 }, 262 time: { 263 required: requiredIf(function () { 264 return this.form.configurationSelected === 'manual'; 265 }), 266 pattern: helpers.regex('pattern', isoTimeRegex), 267 }, 268 }, 269 ntp: { 270 firstAddress: { 271 required: requiredIf(function () { 272 return this.form.configurationSelected === 'ntp'; 273 }), 274 }, 275 }, 276 }, 277 }; 278 }, 279 computed: { 280 ...mapState('dateTime', ['ntpServers', 'isNtpProtocolEnabled']), 281 bmcTime() { 282 return this.$store.getters['global/bmcTime']; 283 }, 284 ntpOptionSelected() { 285 return this.form.configurationSelected === 'ntp'; 286 }, 287 manualOptionSelected() { 288 return this.form.configurationSelected === 'manual'; 289 }, 290 isUtcDisplay() { 291 return this.$store.getters['global/isUtcDisplay']; 292 }, 293 timezone() { 294 if (this.isUtcDisplay) { 295 return 'UTC'; 296 } 297 return this.localOffset(); 298 }, 299 }, 300 watch: { 301 ntpServers() { 302 this.setNtpValues(); 303 }, 304 manualDate() { 305 this.emitChange(); 306 }, 307 bmcTime() { 308 this.form.manual.date = this.$filters.formatDate( 309 this.$store.getters['global/bmcTime'], 310 ); 311 this.form.manual.time = this.$filters 312 .formatTime(this.$store.getters['global/bmcTime']) 313 .slice(0, 5); 314 }, 315 }, 316 created() { 317 this.startLoader(); 318 this.setNtpValues(); 319 Promise.all([ 320 this.$store.dispatch('global/getBmcTime'), 321 this.$store.dispatch('dateTime/getNtpData'), 322 ]).finally(() => this.endLoader()); 323 }, 324 methods: { 325 emitChange() { 326 if (this.v$.$invalid) return; 327 this.v$.$reset(); //reset to re-validate on blur 328 this.$emit('change', { 329 manualDate: this.manualDate ? new Date(this.manualDate) : null, 330 }); 331 }, 332 setNtpValues() { 333 this.form.configurationSelected = this.isNtpProtocolEnabled 334 ? 'ntp' 335 : 'manual'; 336 [ 337 this.form.ntp.firstAddress = '', 338 this.form.ntp.secondAddress = '', 339 this.form.ntp.thirdAddress = '', 340 ] = [this.ntpServers[0], this.ntpServers[1], this.ntpServers[2]]; 341 }, 342 submitForm() { 343 this.v$.$touch(); 344 if (this.v$.$invalid) return; 345 this.startLoader(); 346 347 let dateTimeForm = {}; 348 let isNTPEnabled = this.form.configurationSelected === 'ntp'; 349 350 if (!isNTPEnabled) { 351 const isUtcDisplay = this.$store.getters['global/isUtcDisplay']; 352 let date; 353 354 dateTimeForm.ntpProtocolEnabled = false; 355 356 if (isUtcDisplay) { 357 // Create UTC Date 358 date = this.getUtcDate(this.form.manual.date, this.form.manual.time); 359 } else { 360 // Create local Date 361 date = new Date(`${this.form.manual.date} ${this.form.manual.time}`); 362 } 363 364 dateTimeForm.updatedDateTime = date.toISOString(); 365 } else { 366 dateTimeForm.ntpProtocolEnabled = true; 367 368 const ntpArray = [ 369 this.form.ntp.firstAddress, 370 this.form.ntp.secondAddress, 371 this.form.ntp.thirdAddress, 372 ]; 373 374 // Filter the ntpArray to remove empty strings, 375 // per Redfish spec there should be no empty strings or null on the ntp array. 376 const ntpArrayFiltered = ntpArray.filter((x) => x); 377 378 dateTimeForm.ntpServersArray = [...ntpArrayFiltered]; 379 380 [this.ntpServers[0], this.ntpServers[1], this.ntpServers[2]] = [ 381 ...dateTimeForm.ntpServersArray, 382 ]; 383 384 this.setNtpValues(); 385 } 386 387 this.$store 388 .dispatch('dateTime/updateDateTime', dateTimeForm) 389 .then((success) => { 390 this.successToast(success); 391 if (!isNTPEnabled) return; 392 // Shift address up if second address is empty 393 // to avoid refreshing after delay when updating NTP 394 if (!this.form.ntp.secondAddress && this.form.ntp.thirdAddres) { 395 this.form.ntp.secondAddress = this.form.ntp.thirdAddres; 396 this.form.ntp.thirdAddress = ''; 397 } 398 }) 399 .then(() => { 400 this.$store.dispatch('global/getBmcTime'); 401 }) 402 .catch(({ message }) => this.errorToast(message)) 403 .finally(() => { 404 this.v$.form.$reset(); 405 this.endLoader(); 406 }); 407 }, 408 getUtcDate(date, time) { 409 // Split user input string values to create 410 // a UTC Date object 411 const datesArray = date.split('-'); 412 const timeArray = time.split(':'); 413 let utcDate = Date.UTC( 414 datesArray[0], // User input year 415 //UTC expects zero-index month value 0-11 (January-December) 416 //for reference https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/UTC#Parameters 417 parseInt(datesArray[1]) - 1, // User input month 418 datesArray[2], // User input day 419 timeArray[0], // User input hour 420 timeArray[1], // User input minute 421 ); 422 return new Date(utcDate); 423 }, 424 }, 425}; 426</script> 427