xref: /openbmc/webui-vue/src/views/Settings/DateTime/DateTime.vue (revision d36ac8a8be8636ddd0e64ce005d507b21bcdeb00)
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