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