summaryrefslogtreecommitdiff
path: root/src/views/Settings
diff options
context:
space:
mode:
authorSandeepa Singh <sandeepa.singh@ibm.com>2021-07-19 15:34:18 +0300
committerDerick Montague <derick.montague@ibm.com>2021-08-10 22:20:42 +0300
commitf67f769f2304bca64d2b9758e22c21203960eef9 (patch)
tree167d6ac96a90b098887f9cf1dc4e96a895e2754c /src/views/Settings
parent68cbbe9014cbdcf7229a878f564d38f6d6199f25 (diff)
downloadwebui-vue-f67f769f2304bca64d2b9758e22c21203960eef9.tar.xz
IA update: Update configuration to settings
This is the fourth update to information architecture changes and has the following changes: - The configuration section is updated to settings - The date and time settings page is updated to date and time - The network settings page is updated to network - The power restore policy page in operations section is moved to settings section Signed-off-by: Sandeepa Singh <sandeepa.singh@ibm.com> Change-Id: I6f5ab25f5227530be430bd39a4d9629b3bf09d8b
Diffstat (limited to 'src/views/Settings')
-rw-r--r--src/views/Settings/DateTime/DateTime.vue405
-rw-r--r--src/views/Settings/DateTime/index.js2
-rw-r--r--src/views/Settings/Network/Network.vue664
-rw-r--r--src/views/Settings/Network/index.js2
-rw-r--r--src/views/Settings/PowerRestorePolicy/PowerRestorePolicy.vue80
-rw-r--r--src/views/Settings/PowerRestorePolicy/index.js2
-rw-r--r--src/views/Settings/SecuritySettings/SecuritySettings.vue125
-rw-r--r--src/views/Settings/SecuritySettings/index.js2
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;