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