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