diff options
Diffstat (limited to 'src/views/Settings')
-rw-r--r-- | src/views/Settings/DateTime/DateTime.vue | 405 | ||||
-rw-r--r-- | src/views/Settings/DateTime/index.js | 2 | ||||
-rw-r--r-- | src/views/Settings/Network/Network.vue | 664 | ||||
-rw-r--r-- | src/views/Settings/Network/index.js | 2 | ||||
-rw-r--r-- | src/views/Settings/PowerRestorePolicy/PowerRestorePolicy.vue | 80 | ||||
-rw-r--r-- | src/views/Settings/PowerRestorePolicy/index.js | 2 | ||||
-rw-r--r-- | src/views/Settings/SecuritySettings/SecuritySettings.vue | 125 | ||||
-rw-r--r-- | src/views/Settings/SecuritySettings/index.js | 2 |
8 files changed, 1282 insertions, 0 deletions
diff --git a/src/views/Settings/DateTime/DateTime.vue b/src/views/Settings/DateTime/DateTime.vue new file mode 100644 index 00000000..e62e3444 --- /dev/null +++ b/src/views/Settings/DateTime/DateTime.vue @@ -0,0 +1,405 @@ +<template> + <b-container fluid="xl"> + <page-title /> + <b-row> + <b-col md="8" xl="6"> + <alert variant="info" class="mb-4"> + <span> + {{ $t('pageDateTime.alert.message') }} + <b-link to="/profile-settings"> + {{ $t('pageDateTime.alert.link') }}</b-link + > + </span> + </alert> + </b-col> + </b-row> + <page-section> + <b-row> + <b-col lg="3"> + <dl> + <dt>{{ $t('pageDateTime.form.date') }}</dt> + <dd v-if="bmcTime">{{ bmcTime | formatDate }}</dd> + <dd v-else>--</dd> + </dl> + </b-col> + <b-col lg="3"> + <dl> + <dt>{{ $t('pageDateTime.form.time.label') }}</dt> + <dd v-if="bmcTime">{{ bmcTime | formatTime }}</dd> + <dd v-else>--</dd> + </dl> + </b-col> + </b-row> + </page-section> + <page-section :section-title="$t('pageDateTime.configureSettings')"> + <b-form novalidate @submit.prevent="submitForm"> + <b-form-group + label="Configure date and time" + :disabled="loading" + label-sr-only + > + <b-form-radio + v-model="form.configurationSelected" + value="manual" + data-test-id="dateTime-radio-configureManual" + > + {{ $t('pageDateTime.form.manual') }} + </b-form-radio> + <b-row class="mt-3 ml-3"> + <b-col sm="6" lg="4" xl="3"> + <b-form-group + :label="$t('pageDateTime.form.date')" + label-for="input-manual-date" + > + <b-form-text id="date-format-help">YYYY-MM-DD</b-form-text> + <b-input-group> + <b-form-input + id="input-manual-date" + v-model="form.manual.date" + :state="getValidationState($v.form.manual.date)" + :disabled="ntpOptionSelected" + data-test-id="dateTime-input-manualDate" + class="form-control-with-button" + @blur="$v.form.manual.date.$touch()" + /> + <b-form-invalid-feedback role="alert"> + <div v-if="!$v.form.manual.date.pattern"> + {{ $t('global.form.invalidFormat') }} + </div> + <div v-if="!$v.form.manual.date.required"> + {{ $t('global.form.fieldRequired') }} + </div> + </b-form-invalid-feedback> + <b-form-datepicker + v-model="form.manual.date" + class="btn-datepicker btn-icon-only" + button-only + right + :hide-header="true" + :locale="locale" + :label-help=" + $t('global.calendar.useCursorKeysToNavigateCalendarDates') + " + :title="$t('global.calendar.selectDate')" + :disabled="ntpOptionSelected" + button-variant="link" + aria-controls="input-manual-date" + > + <template #button-content> + <icon-calendar /> + <span class="sr-only"> + {{ $t('global.calendar.selectDate') }} + </span> + </template> + </b-form-datepicker> + </b-input-group> + </b-form-group> + </b-col> + <b-col sm="6" lg="4" xl="3"> + <b-form-group + :label="$t('pageDateTime.form.time.timezone', { timezone })" + label-for="input-manual-time" + > + <b-form-text id="time-format-help">HH:MM</b-form-text> + <b-input-group> + <b-form-input + id="input-manual-time" + v-model="form.manual.time" + :state="getValidationState($v.form.manual.time)" + :disabled="ntpOptionSelected" + data-test-id="dateTime-input-manualTime" + @blur="$v.form.manual.time.$touch()" + /> + <b-form-invalid-feedback role="alert"> + <div v-if="!$v.form.manual.time.pattern"> + {{ $t('global.form.invalidFormat') }} + </div> + <div v-if="!$v.form.manual.time.required"> + {{ $t('global.form.fieldRequired') }} + </div> + </b-form-invalid-feedback> + </b-input-group> + </b-form-group> + </b-col> + </b-row> + <b-form-radio + v-model="form.configurationSelected" + value="ntp" + data-test-id="dateTime-radio-configureNTP" + > + NTP + </b-form-radio> + <b-row class="mt-3 ml-3"> + <b-col sm="6" lg="4" xl="3"> + <b-form-group + :label="$t('pageDateTime.form.ntpServers.server1')" + label-for="input-ntp-1" + > + <b-input-group> + <b-form-input + id="input-ntp-1" + v-model="form.ntp.firstAddress" + :state="getValidationState($v.form.ntp.firstAddress)" + :disabled="manualOptionSelected" + data-test-id="dateTime-input-ntpServer1" + @blur="$v.form.ntp.firstAddress.$touch()" + /> + <b-form-invalid-feedback role="alert"> + <div v-if="!$v.form.ntp.firstAddress.required"> + {{ $t('global.form.fieldRequired') }} + </div> + </b-form-invalid-feedback> + </b-input-group> + </b-form-group> + </b-col> + <b-col sm="6" lg="4" xl="3"> + <b-form-group + :label="$t('pageDateTime.form.ntpServers.server2')" + label-for="input-ntp-2" + > + <b-input-group> + <b-form-input + id="input-ntp-2" + v-model="form.ntp.secondAddress" + :disabled="manualOptionSelected" + data-test-id="dateTime-input-ntpServer2" + /> + </b-input-group> + </b-form-group> + </b-col> + <b-col sm="6" lg="4" xl="3"> + <b-form-group + :label="$t('pageDateTime.form.ntpServers.server3')" + label-for="input-ntp-3" + > + <b-input-group> + <b-form-input + id="input-ntp-3" + v-model="form.ntp.thirdAddress" + :disabled="manualOptionSelected" + data-test-id="dateTime-input-ntpServer3" + /> + </b-input-group> + </b-form-group> + </b-col> + </b-row> + <b-button + variant="primary" + type="submit" + data-test-id="dateTime-button-saveSettings" + > + {{ $t('global.action.saveSettings') }} + </b-button> + </b-form-group> + </b-form> + </page-section> + </b-container> +</template> + +<script> +import Alert from '@/components/Global/Alert'; +import IconCalendar from '@carbon/icons-vue/es/calendar/20'; +import PageTitle from '@/components/Global/PageTitle'; +import PageSection from '@/components/Global/PageSection'; + +import BVToastMixin from '@/components/Mixins/BVToastMixin'; +import LoadingBarMixin, { loading } from '@/components/Mixins/LoadingBarMixin'; +import LocalTimezoneLabelMixin from '@/components/Mixins/LocalTimezoneLabelMixin'; +import VuelidateMixin from '@/components/Mixins/VuelidateMixin.js'; + +import { mapState } from 'vuex'; +import { requiredIf, helpers } from 'vuelidate/lib/validators'; + +const isoDateRegex = /([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))/; +const isoTimeRegex = /^(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$/; + +export default { + name: 'DateTime', + components: { Alert, IconCalendar, PageTitle, PageSection }, + mixins: [ + BVToastMixin, + LoadingBarMixin, + LocalTimezoneLabelMixin, + VuelidateMixin, + ], + beforeRouteLeave(to, from, next) { + this.hideLoader(); + next(); + }, + data() { + return { + locale: this.$store.getters['global/languagePreference'], + form: { + configurationSelected: 'manual', + manual: { + date: '', + time: '', + }, + ntp: { firstAddress: '', secondAddress: '', thirdAddress: '' }, + }, + loading, + }; + }, + validations() { + return { + form: { + manual: { + date: { + required: requiredIf(function () { + return this.form.configurationSelected === 'manual'; + }), + pattern: helpers.regex('pattern', isoDateRegex), + }, + time: { + required: requiredIf(function () { + return this.form.configurationSelected === 'manual'; + }), + pattern: helpers.regex('pattern', isoTimeRegex), + }, + }, + ntp: { + firstAddress: { + required: requiredIf(function () { + return this.form.configurationSelected === 'ntp'; + }), + }, + }, + }, + }; + }, + computed: { + ...mapState('dateTime', ['ntpServers', 'isNtpProtocolEnabled']), + bmcTime() { + return this.$store.getters['global/bmcTime']; + }, + ntpOptionSelected() { + return this.form.configurationSelected === 'ntp'; + }, + manualOptionSelected() { + return this.form.configurationSelected === 'manual'; + }, + isUtcDisplay() { + return this.$store.getters['global/isUtcDisplay']; + }, + timezone() { + if (this.isUtcDisplay) { + return 'UTC'; + } + return this.localOffset(); + }, + }, + watch: { + ntpServers() { + this.setNtpValues(); + }, + manualDate() { + this.emitChange(); + }, + bmcTime() { + this.form.manual.date = this.$options.filters.formatDate( + this.$store.getters['global/bmcTime'] + ); + this.form.manual.time = this.$options.filters + .formatTime(this.$store.getters['global/bmcTime']) + .slice(0, 5); + }, + }, + created() { + this.startLoader(); + this.setNtpValues(); + Promise.all([ + this.$store.dispatch('global/getBmcTime'), + this.$store.dispatch('dateTime/getNtpData'), + ]).finally(() => this.endLoader()); + }, + methods: { + emitChange() { + if (this.$v.$invalid) return; + this.$v.$reset(); //reset to re-validate on blur + this.$emit('change', { + manualDate: this.manualDate ? new Date(this.manualDate) : null, + }); + }, + setNtpValues() { + this.form.configurationSelected = this.isNtpProtocolEnabled + ? 'ntp' + : 'manual'; + [ + this.form.ntp.firstAddress = '', + this.form.ntp.secondAddress = '', + this.form.ntp.thirdAddress = '', + ] = [this.ntpServers[0], this.ntpServers[1], this.ntpServers[2]]; + }, + submitForm() { + this.$v.$touch(); + if (this.$v.$invalid) return; + this.startLoader(); + + let dateTimeForm = {}; + let isNTPEnabled = this.form.configurationSelected === 'ntp'; + + if (!isNTPEnabled) { + const isUtcDisplay = this.$store.getters['global/isUtcDisplay']; + let date; + + dateTimeForm.ntpProtocolEnabled = false; + + if (isUtcDisplay) { + // Create UTC Date + date = this.getUtcDate(this.form.manual.date, this.form.manual.time); + } else { + // Create local Date + date = new Date(`${this.form.manual.date} ${this.form.manual.time}`); + } + + dateTimeForm.updatedDateTime = date.toISOString(); + } else { + dateTimeForm.ntpProtocolEnabled = true; + + dateTimeForm.ntpServersArray = [ + this.form.ntp.firstAddress, + this.form.ntp.secondAddress, + this.form.ntp.thirdAddress, + ]; + } + + this.$store + .dispatch('dateTime/updateDateTime', dateTimeForm) + .then((success) => { + this.successToast(success); + if (!isNTPEnabled) return; + // Shift address up if second address is empty + // to avoid refreshing after delay when updating NTP + if (!this.form.ntp.secondAddress && this.form.ntp.thirdAddres) { + this.form.ntp.secondAddress = this.form.ntp.thirdAddres; + this.form.ntp.thirdAddress = ''; + } + }) + .then(() => { + this.$store.dispatch('global/getBmcTime'); + }) + .catch(({ message }) => this.errorToast(message)) + .finally(() => { + this.$v.form.$reset(); + this.endLoader(); + }); + }, + getUtcDate(date, time) { + // Split user input string values to create + // a UTC Date object + const datesArray = date.split('-'); + const timeArray = time.split(':'); + let utcDate = Date.UTC( + datesArray[0], // User input year + //UTC expects zero-index month value 0-11 (January-December) + //for reference https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/UTC#Parameters + parseInt(datesArray[1]) - 1, // User input month + datesArray[2], // User input day + timeArray[0], // User input hour + timeArray[1] // User input minute + ); + return new Date(utcDate); + }, + }, +}; +</script> diff --git a/src/views/Settings/DateTime/index.js b/src/views/Settings/DateTime/index.js new file mode 100644 index 00000000..2df21eae --- /dev/null +++ b/src/views/Settings/DateTime/index.js @@ -0,0 +1,2 @@ +import DateTime from './DateTime.vue'; +export default DateTime; diff --git a/src/views/Settings/Network/Network.vue b/src/views/Settings/Network/Network.vue new file mode 100644 index 00000000..ab5003f8 --- /dev/null +++ b/src/views/Settings/Network/Network.vue @@ -0,0 +1,664 @@ +<template> + <b-container fluid="xl"> + <page-title :description="$t('pageNetwork.pageDescription')" /> + <page-section :section-title="$t('pageNetwork.interface')"> + <b-row> + <b-col lg="3"> + <b-form-group + label-for="interface-select" + :label="$t('pageNetwork.form.networkInterface')" + > + <b-form-select + id="interface-select" + v-model="selectedInterfaceIndex" + :disabled="loading" + data-test-id="network-select-interface" + :options="interfaceSelectOptions" + @change="selectInterface" + > + </b-form-select> + </b-form-group> + </b-col> + </b-row> + </page-section> + <b-form novalidate @submit.prevent="submitForm"> + <b-form-group :disabled="loading"> + <page-section :section-title="$t('pageNetwork.system')"> + <b-row> + <b-col lg="3"> + <b-form-group + :label="$t('pageNetwork.form.defaultGateway')" + label-for="default-gateway" + > + <b-form-input + id="default-gateway" + v-model.trim="form.gateway" + data-test-id="network-input-gateway" + type="text" + :state="getValidationState($v.form.gateway)" + @change="$v.form.gateway.$touch()" + /> + <b-form-invalid-feedback role="alert"> + <div v-if="!$v.form.gateway.required"> + {{ $t('global.form.fieldRequired') }} + </div> + <div v-if="!$v.form.gateway.ipAddress"> + {{ $t('global.form.invalidFormat') }} + </div> + </b-form-invalid-feedback> + </b-form-group> + </b-col> + <b-col lg="3"> + <b-form-group + :label="$t('pageNetwork.form.hostname')" + label-for="hostname-field" + > + <b-form-input + id="hostname-field" + v-model.trim="form.hostname" + data-test-id="network-input-hostname" + type="text" + :state="getValidationState($v.form.hostname)" + @change="$v.form.hostname.$touch()" + /> + <b-form-invalid-feedback role="alert"> + <div v-if="!$v.form.hostname.required"> + {{ $t('global.form.fieldRequired') }} + </div> + <div v-if="!$v.form.hostname.validateHostname"> + {{ + $t('global.form.lengthMustBeBetween', { min: 1, max: 64 }) + }} + </div> + </b-form-invalid-feedback> + </b-form-group> + </b-col> + <b-col lg="3"> + <b-form-group + :label="$t('pageNetwork.form.macAddress')" + label-for="mac-address" + > + <b-form-input + id="mac-address" + v-model.trim="form.macAddress" + data-test-id="network-input-macAddress" + type="text" + :state="getValidationState($v.form.macAddress)" + @change="$v.form.macAddress.$touch()" + /> + <b-form-invalid-feedback role="alert"> + <div v-if="!$v.form.macAddress.required"> + {{ $t('global.form.fieldRequired') }} + </div> + <div v-if="!$v.form.macAddress.macAddress"> + {{ $t('global.form.invalidFormat') }} + </div> + </b-form-invalid-feedback> + </b-form-group> + </b-col> + </b-row> + </page-section> + <page-section :section-title="$t('pageNetwork.ipv4')"> + <b-form-group :label="$t('pageNetwork.ipv4Configuration')"> + <b-form-text id="enable-secure-help-block"> + {{ $t('pageNetwork.ipv4Helper') }} + </b-form-text> + <b-form-radio + v-model="form.dhcpEnabled" + name="dhcp-radio" + :value="true" + @change="onChangeIpv4Config" + > + {{ $t('pageNetwork.dhcp') }} + </b-form-radio> + <b-form-radio + v-model="form.dhcpEnabled" + name="static-radio" + :value="false" + @change="onChangeIpv4Config" + > + {{ $t('pageNetwork.static') }} + </b-form-radio> + </b-form-group> + <b-row> + <b-col lg="9" class="mb-3"> + <h3 class="h4"> + {{ $t('pageNetwork.dhcp') }} + </h3> + <b-table + responsive="md" + hover + :fields="ipv4DhcpTableFields" + :items="form.ipv4DhcpTableItems" + :empty-text="$t('global.table.emptyMessage')" + class="mb-0" + show-empty + > + <template #cell(Address)="{ item, index }"> + <b-form-input + v-model.trim="item.Address" + :data-test-id="`network-input-dhcpIpv4-${index}`" + :aria-label=" + $t('pageNetwork.table.dhcpIpv4AddressRow') + + ' ' + + (index + 1) + " + readonly + /> + </template> + <template #cell(SubnetMask)="{ item, index }"> + <b-form-input + v-model.trim="item.SubnetMask" + :data-test-id="`network-input-subnetMask-${index}`" + :aria-label=" + $t('pageNetwork.table.dhcpIpv4SubnetRow') + + ' ' + + (index + 1) + " + readonly + /> + </template> + <template #cell(actions)="{ item, index }"> + <table-row-action + v-for="(action, actionIndex) in item.actions" + :key="actionIndex" + :value="action.value" + :title="action.title" + :enabled="false" + @click-table-action=" + onDeleteIpv4StaticTableRow($event, index) + " + > + <template #icon> + <icon-trashcan v-if="action.value === 'delete'" /> + </template> + </table-row-action> + </template> + </b-table> + </b-col> + <b-col lg="9" class="mb-3"> + <h3 class="h4"> + {{ $t('pageNetwork.static') }} + </h3> + <b-table + responsive="md" + hover + :fields="ipv4StaticTableFields" + :items="form.ipv4StaticTableItems" + :empty-text="$t('global.table.emptyMessage')" + class="mb-0" + show-empty + > + <template #cell(Address)="{ item, index }"> + <b-form-input + v-model.trim="item.Address" + :data-test-id="`network-input-staticIpv4-${index}`" + :aria-label=" + $t('pageNetwork.table.staticIpv4AddressRow') + + ' ' + + (index + 1) + " + :state=" + getValidationState( + $v.form.ipv4StaticTableItems.$each.$iter[index].Address + ) + " + @change=" + $v.form.ipv4StaticTableItems.$each.$iter[ + index + ].Address.$touch() + " + /> + <b-form-invalid-feedback role="alert"> + <div + v-if=" + !$v.form.ipv4StaticTableItems.$each.$iter[index].Address + .required + " + > + {{ $t('global.form.fieldRequired') }} + </div> + <div + v-if=" + !$v.form.ipv4StaticTableItems.$each.$iter[index].Address + .ipAddress + " + > + {{ $t('global.form.invalidFormat') }} + </div> + </b-form-invalid-feedback> + </template> + <template #cell(SubnetMask)="{ item, index }"> + <b-form-input + v-model.trim="item.SubnetMask" + :data-test-id="`network-input-subnetMask-${index}`" + :aria-label=" + $t('pageNetwork.table.staticIpv4SubnetRow') + + ' ' + + (index + 1) + " + :state=" + getValidationState( + $v.form.ipv4StaticTableItems.$each.$iter[index] + .SubnetMask + ) + " + @change=" + $v.form.ipv4StaticTableItems.$each.$iter[ + index + ].SubnetMask.$touch() + " + /> + <b-form-invalid-feedback role="alert"> + <div + v-if=" + !$v.form.ipv4StaticTableItems.$each.$iter[index] + .SubnetMask.required + " + > + {{ $t('global.form.fieldRequired') }} + </div> + <div + v-if=" + !$v.form.ipv4StaticTableItems.$each.$iter[index] + .SubnetMask.ipAddress + " + > + {{ $t('global.form.invalidFormat') }} + </div> + </b-form-invalid-feedback> + </template> + <template #cell(actions)="{ item, index }"> + <table-row-action + v-for="(action, actionIndex) in item.actions" + :key="actionIndex" + :value="action.value" + :title="action.title" + @click-table-action=" + onDeleteIpv4StaticTableRow($event, index) + " + > + <template #icon> + <icon-trashcan v-if="action.value === 'delete'" /> + </template> + </table-row-action> + </template> + </b-table> + <b-button variant="link" @click="addIpv4StaticTableRow"> + <icon-add /> + {{ $t('pageNetwork.table.addStaticIpv4Address') }} + </b-button> + </b-col> + </b-row> + </page-section> + <page-section :section-title="$t('pageNetwork.staticDns')"> + <b-row> + <b-col lg="4" class="mb-3"> + <b-table + responsive + hover + :fields="dnsTableFields" + :items="form.dnsStaticTableItems" + :empty-text="$t('global.table.emptyMessage')" + class="mb-0" + show-empty + > + <template #cell(address)="{ item, index }"> + <b-form-input + v-model.trim="item.address" + :data-test-id="`network-input-dnsAddress-${index}`" + :aria-label=" + $t('pageNetwork.table.staticDnsRow') + ' ' + (index + 1) + " + :state=" + getValidationState( + $v.form.dnsStaticTableItems.$each.$iter[index].address + ) + " + @change=" + $v.form.dnsStaticTableItems.$each.$iter[ + index + ].address.$touch() + " + /> + <b-form-invalid-feedback role="alert"> + <div + v-if=" + !$v.form.dnsStaticTableItems.$each.$iter[index].address + .required + " + > + {{ $t('global.form.fieldRequired') }} + </div> + <div + v-if=" + !$v.form.dnsStaticTableItems.$each.$iter[index].address + .ipAddress + " + > + {{ $t('global.form.invalidFormat') }} + </div> + </b-form-invalid-feedback> + </template> + <template #cell(actions)="{ item, index }"> + <table-row-action + v-for="(action, actionIndex) in item.actions" + :key="actionIndex" + :value="action.value" + :title="action.title" + @click-table-action="onDeleteDnsTableRow($event, index)" + > + <template #icon> + <icon-trashcan v-if="action.value === 'delete'" /> + </template> + </table-row-action> + </template> + </b-table> + <b-button variant="link" @click="addDnsTableRow"> + <icon-add /> {{ $t('pageNetwork.table.addDns') }} + </b-button> + </b-col> + </b-row> + </page-section> + <b-button + variant="primary" + type="submit" + data-test-id="network-button-saveNetworkSettings" + > + {{ $t('global.action.saveSettings') }} + </b-button> + </b-form-group> + </b-form> + </b-container> +</template> + +<script> +import IconTrashcan from '@carbon/icons-vue/es/trash-can/20'; +import IconAdd from '@carbon/icons-vue/es/add--alt/20'; +import BVToastMixin from '@/components/Mixins/BVToastMixin'; +import LoadingBarMixin, { loading } from '@/components/Mixins/LoadingBarMixin'; +import PageSection from '@/components/Global/PageSection'; +import PageTitle from '@/components/Global/PageTitle'; +import TableRowAction from '@/components/Global/TableRowAction'; +import VuelidateMixin from '@/components/Mixins/VuelidateMixin'; +import { mapState } from 'vuex'; +import { + required, + helpers, + ipAddress, + macAddress, +} from 'vuelidate/lib/validators'; + +// Hostname pattern +const validateHostname = helpers.regex('validateHostname', /^\S{0,64}$/); + +export default { + name: 'Network', + components: { + PageTitle, + PageSection, + TableRowAction, + IconTrashcan, + IconAdd, + }, + mixins: [BVToastMixin, VuelidateMixin, LoadingBarMixin], + beforeRouteLeave(to, from, next) { + this.hideLoader(); + next(); + }, + data() { + return { + ipv4DhcpTableFields: [ + { + key: 'Address', + label: this.$t('pageNetwork.table.ipAddress'), + }, + { + key: 'SubnetMask', + label: this.$t('pageNetwork.table.subnet'), + }, + { key: 'actions', label: '', tdClass: 'text-right' }, + ], + ipv4StaticTableFields: [ + { + key: 'Address', + label: this.$t('pageNetwork.table.ipAddress'), + }, + { + key: 'SubnetMask', + label: this.$t('pageNetwork.table.subnet'), + }, + { key: 'actions', label: '', tdClass: 'text-right' }, + ], + dnsTableFields: [ + { + key: 'address', + label: this.$t('pageNetwork.table.ipAddress'), + }, + { key: 'actions', label: '', tdClass: 'text-right' }, + ], + selectedInterfaceIndex: 0, + selectedInterface: {}, + form: { + dhcpEnabled: null, + gateway: '', + hostname: '', + macAddress: '', + ipv4StaticTableItems: [], + ipv4DhcpTableItems: [], + dnsStaticTableItems: [], + }, + loading, + }; + }, + validations() { + return { + form: { + gateway: { required, ipAddress }, + hostname: { required, validateHostname }, + ipv4StaticTableItems: { + $each: { + Address: { + required, + ipAddress, + }, + SubnetMask: { + required, + ipAddress, + }, + }, + }, + macAddress: { required, macAddress: macAddress() }, + dnsStaticTableItems: { + $each: { + address: { + required, + ipAddress, + }, + }, + }, + }, + }; + }, + computed: { + ...mapState('network', [ + 'ethernetData', + 'interfaceOptions', + 'defaultGateway', + ]), + interfaceSelectOptions() { + return this.interfaceOptions.map((option, index) => { + return { + text: option, + value: index, + }; + }); + }, + }, + watch: { + ethernetData: function () { + this.selectInterface(); + }, + }, + created() { + this.startLoader(); + this.$store + .dispatch('network/getEthernetData') + .finally(() => this.endLoader()); + }, + methods: { + selectInterface() { + this.selectedInterface = this.ethernetData[this.selectedInterfaceIndex]; + this.getIpv4DhcpTableItems(); + this.getIpv4StaticTableItems(); + this.getDnsStaticTableItems(); + this.getInterfaceSettings(); + }, + getInterfaceSettings() { + this.form.gateway = this.defaultGateway; + this.form.hostname = this.selectedInterface.HostName; + this.form.macAddress = this.selectedInterface.MACAddress; + this.form.dhcpEnabled = this.selectedInterface.DHCPv4.DHCPEnabled; + }, + onChangeIpv4Config(value) { + this.form.dhcpEnabled = value; + }, + getDnsStaticTableItems() { + const dns = this.selectedInterface.StaticNameServers || []; + this.form.dnsStaticTableItems = dns.map((server) => { + return { + address: server, + actions: [ + { + value: 'delete', + enabled: this.form.dhcpEnabled, + title: this.$t('pageNetwork.table.deleteDns'), + }, + ], + }; + }); + }, + addDnsTableRow() { + this.$v.form.dnsStaticTableItems.$touch(); + this.form.dnsStaticTableItems.push({ + address: '', + actions: [ + { + value: 'delete', + enabled: this.form.dhcpEnabled, + title: this.$t('pageNetwork.table.deleteDns'), + }, + ], + }); + }, + deleteDnsTableRow(index) { + this.$v.form.dnsStaticTableItems.$touch(); + this.form.dnsStaticTableItems.splice(index, 1); + }, + onDeleteDnsTableRow(action, row) { + this.deleteDnsTableRow(row); + }, + getIpv4DhcpTableItems() { + const addresses = this.selectedInterface.IPv4Addresses || []; + this.form.ipv4DhcpTableItems = addresses + .filter((ipv4) => ipv4.AddressOrigin === 'DHCP') + .map((ipv4) => { + return { + Address: ipv4.Address, + SubnetMask: ipv4.SubnetMask, + actions: [ + { + value: 'delete', + enabled: false, + title: this.$t('pageNetwork.table.deleteDhcpIpv4'), + }, + ], + }; + }); + }, + getIpv4StaticTableItems() { + const addresses = this.selectedInterface.IPv4StaticAddresses || []; + this.form.ipv4StaticTableItems = addresses.map((ipv4) => { + return { + Address: ipv4.Address, + SubnetMask: ipv4.SubnetMask, + actions: [ + { + value: 'delete', + enabled: this.form.dhcpEnabled, + title: this.$t('pageNetwork.table.deleteStaticIpv4'), + }, + ], + }; + }); + }, + addIpv4StaticTableRow() { + this.$v.form.ipv4StaticTableItems.$touch(); + this.form.ipv4StaticTableItems.push({ + Address: '', + SubnetMask: '', + actions: [ + { + value: 'delete', + enabled: this.form.dhcpEnabled, + title: this.$t('pageNetwork.table.deleteStaticIpv4'), + }, + ], + }); + }, + deleteIpv4StaticTableRow(index) { + this.$v.form.ipv4StaticTableItems.$touch(); + this.form.ipv4StaticTableItems.splice(index, 1); + }, + onDeleteIpv4StaticTableRow(action, row) { + this.deleteIpv4StaticTableRow(row); + }, + submitForm() { + this.$v.$touch(); + if (this.$v.$invalid) return; + this.startLoader(); + let networkInterfaceSelected = this.selectedInterface; + let selectedInterfaceIndex = this.selectedInterfaceIndex; + let interfaceId = networkInterfaceSelected.Id; + let isDhcpEnabled = this.form.dhcpEnabled; + let macAddress = this.form.macAddress; + let hostname = this.form.hostname; + let networkSettingsForm = { + interfaceId, + hostname, + macAddress, + selectedInterfaceIndex, + }; + // Enabling DHCP without any available IP addresses will bring network down + if (this.form.ipv4DhcpTableItems.length) { + networkSettingsForm.isDhcpEnabled = isDhcpEnabled; + } else { + networkSettingsForm.isDhcpEnabled = false; + this.errorToast(this.$t('pageNetwork.toast.errorSaveDhcpSettings')); + } + networkSettingsForm.staticIpv4 = this.form.ipv4StaticTableItems.map( + (updateIpv4) => { + delete updateIpv4.actions; + updateIpv4.Gateway = this.form.gateway; + return updateIpv4; + } + ); + networkSettingsForm.staticNameServers = this.form.dnsStaticTableItems.map( + (updateDns) => { + return updateDns.address; + } + ); + this.$store + .dispatch('network/updateInterfaceSettings', networkSettingsForm) + .then((success) => { + this.successToast(success); + }) + .catch(({ message }) => this.errorToast(message)) + .finally(() => { + this.$v.form.$reset(); + this.endLoader(); + }); + }, + }, +}; +</script> diff --git a/src/views/Settings/Network/index.js b/src/views/Settings/Network/index.js new file mode 100644 index 00000000..97bf0397 --- /dev/null +++ b/src/views/Settings/Network/index.js @@ -0,0 +1,2 @@ +import Network from './Network.vue'; +export default Network; diff --git a/src/views/Settings/PowerRestorePolicy/PowerRestorePolicy.vue b/src/views/Settings/PowerRestorePolicy/PowerRestorePolicy.vue new file mode 100644 index 00000000..8589aed3 --- /dev/null +++ b/src/views/Settings/PowerRestorePolicy/PowerRestorePolicy.vue @@ -0,0 +1,80 @@ +<template> + <b-container fluid="xl"> + <page-title :description="$t('pagePowerRestorePolicy.description')" /> + + <b-row> + <b-col sm="8" md="6" xl="12"> + <b-form-group :label="$t('pagePowerRestorePolicy.powerPoliciesLabel')"> + <b-form-radio + v-for="policy in powerRestorePolicies" + :key="policy.state" + v-model="currentPowerRestorePolicy" + :value="policy.state" + name="power-restore-policy" + > + {{ policy.desc }} + </b-form-radio> + </b-form-group> + </b-col> + </b-row> + + <b-button variant="primary" type="submit" @click="submitForm"> + {{ $t('global.action.saveSettings') }} + </b-button> + </b-container> +</template> + +<script> +import PageTitle from '@/components/Global/PageTitle'; +import LoadingBarMixin from '@/components/Mixins/LoadingBarMixin'; +import VuelidateMixin from '@/components/Mixins/VuelidateMixin.js'; +import BVToastMixin from '@/components/Mixins/BVToastMixin'; + +export default { + name: 'PowerRestorePolicy', + components: { PageTitle }, + mixins: [VuelidateMixin, BVToastMixin, LoadingBarMixin], + beforeRouteLeave(to, from, next) { + this.hideLoader(); + next(); + }, + data() { + return { + policyValue: null, + }; + }, + computed: { + powerRestorePolicies() { + return this.$store.getters['powerPolicy/powerRestorePolicies']; + }, + currentPowerRestorePolicy: { + get() { + return this.$store.getters['powerPolicy/powerRestoreCurrentPolicy']; + }, + set(policy) { + this.policyValue = policy; + }, + }, + }, + created() { + this.startLoader(); + Promise.all([ + this.$store.dispatch('powerPolicy/getPowerRestorePolicies'), + this.$store.dispatch('powerPolicy/getPowerRestoreCurrentPolicy'), + ]).finally(() => this.endLoader()); + }, + methods: { + submitForm() { + this.startLoader(); + this.$store + .dispatch( + 'powerPolicy/setPowerRestorePolicy', + this.policyValue || this.currentPowerRestorePolicy + ) + .then((message) => this.successToast(message)) + .catch(({ message }) => this.errorToast(message)) + .finally(() => this.endLoader()); + }, + }, +}; +</script> diff --git a/src/views/Settings/PowerRestorePolicy/index.js b/src/views/Settings/PowerRestorePolicy/index.js new file mode 100644 index 00000000..fab0d477 --- /dev/null +++ b/src/views/Settings/PowerRestorePolicy/index.js @@ -0,0 +1,2 @@ +import PowerRestorePolicy from './PowerRestorePolicy.vue'; +export default PowerRestorePolicy; diff --git a/src/views/Settings/SecuritySettings/SecuritySettings.vue b/src/views/Settings/SecuritySettings/SecuritySettings.vue new file mode 100644 index 00000000..ec8d2589 --- /dev/null +++ b/src/views/Settings/SecuritySettings/SecuritySettings.vue @@ -0,0 +1,125 @@ +<template> + <b-container fluid="xl"> + <page-title /> + <b-row> + <b-col md="8"> + <page-section + :section-title="$t('pageSecuritySettings.networkServices')" + > + <b-row class="setting-section"> + <b-col class="d-flex align-items-center justify-content-between"> + <dl class="mr-3 w-75"> + <dt>{{ $t('pageSecuritySettings.ssh') }}</dt> + <dd> + {{ $t('pageSecuritySettings.sshDescription') }} + </dd> + </dl> + <b-form-checkbox + id="sshSwitch" + v-model="sshProtocolState" + data-test-id="security-toggle-bmcShell" + switch + @change="changeSshProtocolState" + > + <span class="sr-only"> + {{ $t('pageSecuritySettings.ssh') }} + </span> + <span v-if="sshProtocolState"> + {{ $t('global.status.enabled') }} + </span> + <span v-else>{{ $t('global.status.disabled') }}</span> + </b-form-checkbox> + </b-col> + </b-row> + <b-row class="setting-section"> + <b-col class="d-flex align-items-center justify-content-between"> + <dl class="mt-3 mr-3 w-75"> + <dt>{{ $t('pageSecuritySettings.ipmi') }}</dt> + <dd> + {{ $t('pageSecuritySettings.ipmiDescription') }} + </dd> + </dl> + <b-form-checkbox + id="ipmiSwitch" + v-model="ipmiProtocolState" + data-test-id="security-toggle-networkIpmi" + switch + @change="changeIpmiProtocolState" + > + <span class="sr-only"> + {{ $t('pageSecuritySettings.ipmi') }} + </span> + <span v-if="ipmiProtocolState"> + {{ $t('global.status.enabled') }} + </span> + <span v-else>{{ $t('global.status.disabled') }}</span> + </b-form-checkbox> + </b-col> + </b-row> + </page-section> + </b-col> + </b-row> + </b-container> +</template> + +<script> +import PageSection from '@/components/Global/PageSection'; +import PageTitle from '@/components/Global/PageTitle'; + +import LoadingBarMixin from '@/components/Mixins/LoadingBarMixin'; +import BVToastMixin from '@/components/Mixins/BVToastMixin'; + +export default { + name: 'SecuritySettings', + components: { PageTitle, PageSection }, + mixins: [LoadingBarMixin, BVToastMixin], + beforeRouteLeave(to, from, next) { + this.hideLoader(); + next(); + }, + computed: { + sshProtocolState: { + get() { + return this.$store.getters['securitySettings/sshProtocolEnabled']; + }, + set(newValue) { + return newValue; + }, + }, + ipmiProtocolState: { + get() { + return this.$store.getters['securitySettings/ipmiProtocolEnabled']; + }, + set(newValue) { + return newValue; + }, + }, + }, + created() { + this.startLoader(); + this.$store + .dispatch('securitySettings/getNetworkProtocolStatus') + .finally(() => this.endLoader()); + }, + methods: { + changeIpmiProtocolState(state) { + this.$store + .dispatch('securitySettings/saveIpmiProtocolState', state) + .then((message) => this.successToast(message)) + .catch(({ message }) => this.errorToast(message)); + }, + changeSshProtocolState(state) { + this.$store + .dispatch('securitySettings/saveSshProtocolState', state) + .then((message) => this.successToast(message)) + .catch(({ message }) => this.errorToast(message)); + }, + }, +}; +</script> + +<style lang="scss" scoped> +.setting-section { + border-bottom: 1px solid gray('300'); +} +</style> diff --git a/src/views/Settings/SecuritySettings/index.js b/src/views/Settings/SecuritySettings/index.js new file mode 100644 index 00000000..5ec2b615 --- /dev/null +++ b/src/views/Settings/SecuritySettings/index.js @@ -0,0 +1,2 @@ +import SecuritySettings from './SecuritySettings.vue'; +export default SecuritySettings; |