diff options
author | Dixsie Wolmers <dixsie@ibm.com> | 2021-11-03 06:06:35 +0300 |
---|---|---|
committer | Dixsie Wolmers <dixsiew@gmail.com> | 2021-12-03 23:45:37 +0300 |
commit | b34349d4139230fb4ca99bf89a6b0e1f707e58e2 (patch) | |
tree | 06313bfae3aa3d8b2aa60ff98a89c066cb4daea8 | |
parent | c4b8757ed88ecea369e6044548d2fbe072d5bd4a (diff) | |
download | webui-vue-b34349d4139230fb4ca99bf89a6b0e1f707e58e2.tar.xz |
Network Settings: Add and Delete IPV4 and DNS address
Adds ability to add or delete static ipv4 and dns
addesses per interface.
Signed-off-by: Dixsie Wolmers <dixsie@ibm.com>
Change-Id: Ie143ded2f173dd48f137471a684ba0d35ab0bf69
-rw-r--r-- | src/locales/en-US.json | 2 | ||||
-rw-r--r-- | src/store/modules/Settings/NetworkStore.js | 119 | ||||
-rw-r--r-- | src/views/Settings/Network/ModalDns.vue | 92 | ||||
-rw-r--r-- | src/views/Settings/Network/ModalIpv4.vue | 165 | ||||
-rw-r--r-- | src/views/Settings/Network/Network.vue | 44 | ||||
-rw-r--r-- | src/views/Settings/Network/TableDns.vue | 37 | ||||
-rw-r--r-- | src/views/Settings/Network/TableIpv4.vue | 47 |
7 files changed, 484 insertions, 22 deletions
diff --git a/src/locales/en-US.json b/src/locales/en-US.json index 3bfc4a88..acf01192 100644 --- a/src/locales/en-US.json +++ b/src/locales/en-US.json @@ -651,9 +651,11 @@ "modal": { "ipAddress": "IP address", "gateway": "Gateway", + "staticDns": "Static DNS", "subnetMask": "Subnet mask" }, "table": { + "addDnsAddress": "Add IP address", "addIpv4Address": "Add static IPv4 address", "addressOrigin": "Address origin", "deleteDns": "Edit DNS address", diff --git a/src/store/modules/Settings/NetworkStore.js b/src/store/modules/Settings/NetworkStore.js index 5b95cb00..176fcd74 100644 --- a/src/store/modules/Settings/NetworkStore.js +++ b/src/store/modules/Settings/NetworkStore.js @@ -7,13 +7,20 @@ const NetworkStore = { ethernetData: [], firstInterfaceId: '', //used for setting global DHCP settings globalNetworkSettings: [], + selectedInterfaceId: '', // which tab is selected + selectedInterfaceIndex: 0, // which tab is selected }, getters: { ethernetData: (state) => state.ethernetData, firstInterfaceId: (state) => state.firstInterfaceId, globalNetworkSettings: (state) => state.globalNetworkSettings, + selectedInterfaceId: (state) => state.selectedInterfaceId, + selectedInterfaceIndex: (state) => state.selectedInterfaceIndex, }, mutations: { + setDomainNameState: (state, domainState) => + (state.domainState = domainState), + setDnsState: (state, dnsState) => (state.dnsState = dnsState), setEthernetData: (state, ethernetData) => (state.ethernetData = ethernetData), setFirstInterfaceId: (state, firstInterfaceId) => @@ -28,6 +35,7 @@ const NetworkStore = { LinkStatus, } = data; return { + defaultGateway: IPv4StaticAddresses[0]?.Gateway, //First static gateway is the default gateway dhcpAddress: IPv4Addresses.filter( (ipv4) => ipv4.AddressOrigin === 'DHCP' ), @@ -40,6 +48,11 @@ const NetworkStore = { }; }); }, + setNtpState: (state, ntpState) => (state.ntpState = ntpState), + setSelectedInterfaceId: (state, selectedInterfaceId) => + (state.selectedInterfaceId = selectedInterfaceId), + setSelectedInterfaceIndex: (state, selectedInterfaceIndex) => + (state.selectedInterfaceIndex = selectedInterfaceIndex), }, actions: { async getEthernetData({ commit }) { @@ -65,6 +78,7 @@ const NetworkStore = { commit('setEthernetData', ethernetData); commit('setFirstInterfaceId', firstInterfaceId); + commit('setSelectedInterfaceId', firstInterfaceId); commit('setGlobalNetworkSettings', ethernetInterfaces); }) .catch((error) => { @@ -158,6 +172,111 @@ const NetworkStore = { ); }); }, + async setSelectedTabIndex({ commit }, tabIndex) { + commit('setSelectedInterfaceIndex', tabIndex); + }, + async setSelectedTabId({ commit }, tabId) { + commit('setSelectedInterfaceId', tabId); + }, + async saveIpv4Address({ dispatch, state }, ipv4Form) { + const originalAddresses = state.ethernetData[ + state.selectedInterfaceIndex + ].IPv4StaticAddresses.map((ipv4) => { + const { Address, SubnetMask, Gateway } = ipv4; + return { + Address, + SubnetMask, + Gateway, + }; + }); + const newAddress = [ipv4Form]; + return api + .patch( + `/redfish/v1/Managers/bmc/EthernetInterfaces/${state.selectedInterfaceId}`, + { IPv4StaticAddresses: originalAddresses.concat(newAddress) } + ) + .then(dispatch('getEthernetData')) + .then(() => { + return i18n.t('pageNetwork.toast.successSaveNetworkSettings', { + setting: i18n.t('pageNetwork.ipv4'), + }); + }) + .catch((error) => { + console.log(error); + throw new Error( + i18n.t('pageNetwork.toast.errorSaveNetworkSettings', { + setting: i18n.t('pageNetwork.ipv4'), + }) + ); + }); + }, + async editIpv4Address({ dispatch, state }, ipv4TableData) { + return api + .patch( + `/redfish/v1/Managers/bmc/EthernetInterfaces/${state.selectedInterfaceId}`, + { IPv4StaticAddresses: ipv4TableData } + ) + .then(dispatch('getEthernetData')) + .then(() => { + return i18n.t('pageNetwork.toast.successSaveNetworkSettings', { + setting: i18n.t('pageNetwork.ipv4'), + }); + }) + .catch((error) => { + console.log(error); + throw new Error( + i18n.t('pageNetwork.toast.errorSaveNetworkSettings', { + setting: i18n.t('pageNetwork.ipv4'), + }) + ); + }); + }, + async saveDnsAddress({ dispatch, state }, dnsForm) { + const newAddress = dnsForm; + const originalAddresses = + state.ethernetData[state.selectedInterfaceIndex].StaticNameServers; + const newDnsArray = originalAddresses.concat(newAddress); + return api + .patch( + `/redfish/v1/Managers/bmc/EthernetInterfaces/${state.selectedInterfaceId}`, + { StaticNameServers: newDnsArray } + ) + .then(dispatch('getEthernetData')) + .then(() => { + return i18n.t('pageNetwork.toast.successSaveNetworkSettings', { + setting: i18n.t('pageNetwork.dns'), + }); + }) + .catch((error) => { + console.log(error); + throw new Error( + i18n.t('pageNetwork.toast.errorSaveNetworkSettings', { + setting: i18n.t('pageNetwork.dns'), + }) + ); + }); + }, + async editDnsAddress({ dispatch, state }, dnsTableData) { + return api + .patch( + `/redfish/v1/Managers/bmc/EthernetInterfaces/${state.selectedInterfaceId}`, + { StaticNameServers: dnsTableData } + ) + .then(dispatch('getEthernetData')) + .then(() => { + return i18n.t('pageNetwork.toast.successSaveNetworkSettings', { + setting: i18n.t('pageNetwork.dns'), + }); + }) + .catch((error) => { + console.log(error); + throw new Error( + i18n.t('pageNetwork.toast.errorSaveNetworkSettings', { + setting: i18n.t('pageNetwork.dns'), + }) + ); + }); + }, }, }; diff --git a/src/views/Settings/Network/ModalDns.vue b/src/views/Settings/Network/ModalDns.vue new file mode 100644 index 00000000..7f127173 --- /dev/null +++ b/src/views/Settings/Network/ModalDns.vue @@ -0,0 +1,92 @@ +<template> + <b-modal + id="modal-dns" + ref="modal" + :title="$t('pageNetwork.table.addDnsAddress')" + @hidden="resetForm" + > + <b-form id="form-dns" @submit.prevent="handleSubmit"> + <b-row> + <b-col sm="6"> + <b-form-group + :label="$t('pageNetwork.modal.staticDns')" + label-for="staticDns" + > + <b-form-input + id="staticDns" + v-model="form.staticDns" + type="text" + :state="getValidationState($v.form.staticDns)" + @input="$v.form.staticDns.$touch()" + /> + <b-form-invalid-feedback role="alert"> + <template v-if="!$v.form.staticDns.required"> + {{ $t('global.form.fieldRequired') }} + </template> + <template v-if="!$v.form.staticDns.ipAddress"> + {{ $t('global.form.invalidFormat') }} + </template> + </b-form-invalid-feedback> + </b-form-group> + </b-col> + </b-row> + </b-form> + <template #modal-footer="{ cancel }"> + <b-button variant="secondary" @click="cancel()"> + {{ $t('global.action.cancel') }} + </b-button> + <b-button form="form-dns" type="submit" variant="primary" @click="onOk"> + {{ $t('global.action.add') }} + </b-button> + </template> + </b-modal> +</template> + +<script> +import VuelidateMixin from '@/components/Mixins/VuelidateMixin.js'; +import { ipAddress, required } from 'vuelidate/lib/validators'; + +export default { + mixins: [VuelidateMixin], + data() { + return { + form: { + staticDns: null, + }, + }; + }, + validations() { + return { + form: { + staticDns: { + required, + ipAddress, + }, + }, + }; + }, + methods: { + handleSubmit() { + this.$v.$touch(); + if (this.$v.$invalid) return; + this.$emit('ok', [this.form.staticDns]); + this.closeModal(); + }, + closeModal() { + this.$nextTick(() => { + this.$refs.modal.hide(); + }); + }, + resetForm() { + this.form.staticDns = null; + this.$v.$reset(); + this.$emit('hidden'); + }, + onOk(bvModalEvt) { + // prevent modal close + bvModalEvt.preventDefault(); + this.handleSubmit(); + }, + }, +}; +</script> diff --git a/src/views/Settings/Network/ModalIpv4.vue b/src/views/Settings/Network/ModalIpv4.vue new file mode 100644 index 00000000..dcf4a579 --- /dev/null +++ b/src/views/Settings/Network/ModalIpv4.vue @@ -0,0 +1,165 @@ +<template> + <b-modal + id="modal-add-ipv4" + ref="modal" + :title="$t('pageNetwork.table.addIpv4Address')" + @hidden="resetForm" + > + <b-form id="form-ipv4" @submit.prevent="handleSubmit"> + <b-row> + <b-col sm="6"> + <b-form-group + :label="$t('pageNetwork.modal.ipAddress')" + label-for="ipAddress" + > + <b-form-input + id="ipAddress" + v-model="form.ipAddress" + type="text" + :state="getValidationState($v.form.ipAddress)" + @input="$v.form.ipAddress.$touch()" + /> + <b-form-invalid-feedback role="alert"> + <template v-if="!$v.form.ipAddress.required"> + {{ $t('global.form.fieldRequired') }} + </template> + <template v-if="!$v.form.ipAddress.ipAddress"> + {{ $t('global.form.invalidFormat') }} + </template> + </b-form-invalid-feedback> + </b-form-group> + </b-col> + <b-col sm="6"> + <b-form-group + :label="$t('pageNetwork.modal.gateway')" + label-for="gateway" + > + <b-form-input + id="gateway" + v-model="form.gateway" + type="text" + :state="getValidationState($v.form.gateway)" + @input="$v.form.gateway.$touch()" + /> + <b-form-invalid-feedback role="alert"> + <template v-if="!$v.form.gateway.required"> + {{ $t('global.form.fieldRequired') }} + </template> + <template v-if="!$v.form.gateway.ipAddress"> + {{ $t('global.form.invalidFormat') }} + </template> + </b-form-invalid-feedback> + </b-form-group> + </b-col> + </b-row> + <b-row> + <b-col sm="6"> + <b-form-group + :label="$t('pageNetwork.modal.subnetMask')" + label-for="subnetMask" + > + <b-form-input + id="subnetMask" + v-model="form.subnetMask" + type="text" + :state="getValidationState($v.form.subnetMask)" + @input="$v.form.subnetMask.$touch()" + /> + <b-form-invalid-feedback role="alert"> + <template v-if="!$v.form.subnetMask.required"> + {{ $t('global.form.fieldRequired') }} + </template> + <template v-if="!$v.form.subnetMask.ipAddress"> + {{ $t('global.form.invalidFormat') }} + </template> + </b-form-invalid-feedback> + </b-form-group> + </b-col> + </b-row> + </b-form> + <template #modal-footer="{ cancel }"> + <b-button variant="secondary" @click="cancel()"> + {{ $t('global.action.cancel') }} + </b-button> + <b-button form="form-ipv4" type="submit" variant="primary" @click="onOk"> + {{ $t('global.action.add') }} + </b-button> + </template> + </b-modal> +</template> + +<script> +import VuelidateMixin from '@/components/Mixins/VuelidateMixin.js'; +import { ipAddress, required } from 'vuelidate/lib/validators'; + +export default { + mixins: [VuelidateMixin], + props: { + defaultGateway: { + type: String, + default: '', + }, + }, + data() { + return { + form: { + ipAddress: '', + gateway: '', + subnetMask: '', + }, + }; + }, + watch: { + defaultGateway() { + this.form.gateway = this.defaultGateway; + }, + }, + validations() { + return { + form: { + ipAddress: { + required, + ipAddress, + }, + gateway: { + required, + ipAddress, + }, + subnetMask: { + required, + ipAddress, + }, + }, + }; + }, + methods: { + handleSubmit() { + this.$v.$touch(); + if (this.$v.$invalid) return; + this.$emit('ok', { + Address: this.form.ipAddress, + Gateway: this.form.gateway, + SubnetMask: this.form.subnetMask, + }); + this.closeModal(); + }, + closeModal() { + this.$nextTick(() => { + this.$refs.modal.hide(); + }); + }, + resetForm() { + this.form.ipAddress = null; + this.form.gateway = this.defaultGateway; + this.form.subnetMask = null; + this.$v.$reset(); + this.$emit('hidden'); + }, + onOk(bvModalEvt) { + // prevent modal close + bvModalEvt.preventDefault(); + this.handleSubmit(); + }, + }, +}; +</script> diff --git a/src/views/Settings/Network/Network.vue b/src/views/Settings/Network/Network.vue index 918c8e98..729a7a3c 100644 --- a/src/views/Settings/Network/Network.vue +++ b/src/views/Settings/Network/Network.vue @@ -4,7 +4,7 @@ <!-- Global settings for all interfaces --> <network-global-settings /> <!-- Interface tabs --> - <page-section> + <page-section v-if="ethernetData"> <b-row> <b-col> <b-card no-body> @@ -31,6 +31,9 @@ </b-col> </b-row> </page-section> + <!-- Modals --> + <modal-ipv4 :default-gateway="defaultGateway" @ok="saveIpv4Address" /> + <modal-dns @ok="saveDnsAddress" /> </b-container> </template> @@ -38,6 +41,8 @@ import BVToastMixin from '@/components/Mixins/BVToastMixin'; import DataFormatterMixin from '@/components/Mixins/DataFormatterMixin'; import LoadingBarMixin, { loading } from '@/components/Mixins/LoadingBarMixin'; +import ModalIpv4 from './ModalIpv4.vue'; +import ModalDns from './ModalDns.vue'; import NetworkGlobalSettings from './NetworkGlobalSettings.vue'; import NetworkInterfaceSettings from './NetworkInterfaceSettings.vue'; import PageSection from '@/components/Global/PageSection'; @@ -49,6 +54,8 @@ import { mapState } from 'vuex'; export default { name: 'Network', components: { + ModalIpv4, + ModalDns, NetworkGlobalSettings, NetworkInterfaceSettings, PageSection, @@ -63,6 +70,7 @@ export default { }, data() { return { + defaultGateway: '', loading, tabIndex: 0, }; @@ -70,6 +78,11 @@ export default { computed: { ...mapState('network', ['ethernetData']), }, + watch: { + ethernetData() { + this.getGateway(); + }, + }, created() { this.startLoader(); const globalSettings = new Promise((resolve) => { @@ -95,8 +108,37 @@ export default { ]).finally(() => this.endLoader()); }, methods: { + getGateway() { + this.defaultGateway = this.$store.getters[ + 'network/globalNetworkSettings' + ][this.tabIndex].defaultGateway; + }, getTabIndex(selectedIndex) { this.tabIndex = selectedIndex; + this.$store.dispatch('network/setSelectedTabIndex', this.tabIndex); + this.$store.dispatch( + 'network/setSelectedTabId', + this.ethernetData[selectedIndex].Id + ); + this.defaultGateway = this.$store.getters[ + 'network/globalNetworkSettings' + ][this.tabIndex].defaultGateway; + }, + saveIpv4Address(modalFormData) { + this.startLoader(); + this.$store + .dispatch('network/saveIpv4Address', modalFormData) + .then((message) => this.successToast(message)) + .catch(({ message }) => this.errorToast(message)) + .finally(() => this.endLoader()); + }, + saveDnsAddress(modalFormData) { + this.startLoader(); + this.$store + .dispatch('network/saveDnsAddress', modalFormData) + .then((message) => this.successToast(message)) + .catch(({ message }) => this.errorToast(message)) + .finally(() => this.endLoader()); }, }, }; diff --git a/src/views/Settings/Network/TableDns.vue b/src/views/Settings/Network/TableDns.vue index 2578ba36..569109f1 100644 --- a/src/views/Settings/Network/TableDns.vue +++ b/src/views/Settings/Network/TableDns.vue @@ -2,6 +2,12 @@ <page-section :section-title="$t('pageNetwork.staticDns')"> <b-row> <b-col lg="6"> + <div class="text-right"> + <b-button variant="primary" @click="initDnsModal()"> + <icon-add /> + {{ $t('pageNetwork.table.addDnsAddress') }} + </b-button> + </div> <b-table responsive="md" hover @@ -11,7 +17,7 @@ class="mb-0" show-empty > - <template #cell(actions)="{ item }"> + <template #cell(actions)="{ item, index }"> <table-row-action v-for="(action, actionIndex) in item.actions" :key="actionIndex" @@ -34,6 +40,7 @@ <script> import BVToastMixin from '@/components/Mixins/BVToastMixin'; +import IconAdd from '@carbon/icons-vue/es/add--alt/20'; import IconEdit from '@carbon/icons-vue/es/edit/20'; import IconTrashcan from '@carbon/icons-vue/es/trash-can/20'; import PageSection from '@/components/Global/PageSection'; @@ -43,6 +50,7 @@ import { mapState } from 'vuex'; export default { name: 'DNSTable', components: { + IconAdd, IconEdit, IconTrashcan, PageSection, @@ -87,6 +95,9 @@ export default { tabIndex() { this.getStaticDnsItems(); }, + ethernetData() { + this.getStaticDnsItems(); + }, }, created() { this.getStaticDnsItems(); @@ -104,10 +115,6 @@ export default { address: server, actions: [ { - value: 'edit', - title: this.$t('pageNetwork.table.editDns'), - }, - { value: 'delete', title: this.$t('pageNetwork.table.deleteDns'), }, @@ -115,12 +122,24 @@ export default { }; }); }, - onDnsTableAction(action, row) { - if (action === 'delete') { - this.form.dnsStaticTableItems.splice(row, 1); - // TODO: delete row in store + onDnsTableAction(action, $event, index) { + if ($event === 'delete') { + this.deleteDnsTableRow(index); } }, + deleteDnsTableRow(index) { + this.form.dnsStaticTableItems.splice(index, 1); + const newDnsArray = this.form.dnsStaticTableItems.map((dns) => { + return dns.address; + }); + this.$store + .dispatch('network/editDnsAddress', newDnsArray) + .then((message) => this.successToast(message)) + .catch(({ message }) => this.errorToast(message)); + }, + initDnsModal() { + this.$bvModal.show('modal-dns'); + }, }, }; </script> diff --git a/src/views/Settings/Network/TableIpv4.vue b/src/views/Settings/Network/TableIpv4.vue index 5e4bb7b5..75870031 100644 --- a/src/views/Settings/Network/TableIpv4.vue +++ b/src/views/Settings/Network/TableIpv4.vue @@ -6,6 +6,12 @@ {{ $t('pageNetwork.ipv4Addresses') }} </h3> </b-col> + <b-col class="text-right"> + <b-button variant="primary" @click="initAddIpv4Address()"> + <icon-add /> + {{ $t('pageNetwork.table.addIpv4Address') }} + </b-button> + </b-col> </b-row> <b-table responsive="md" @@ -16,7 +22,7 @@ class="mb-0" show-empty > - <template #cell(actions)="{ item }"> + <template #cell(actions)="{ item, index }"> <table-row-action v-for="(action, actionIndex) in item.actions" :key="actionIndex" @@ -37,8 +43,10 @@ <script> import BVToastMixin from '@/components/Mixins/BVToastMixin'; +import IconAdd from '@carbon/icons-vue/es/add--alt/20'; import IconEdit from '@carbon/icons-vue/es/edit/20'; import IconTrashcan from '@carbon/icons-vue/es/trash-can/20'; +import LoadingBarMixin from '@/components/Mixins/LoadingBarMixin'; import PageSection from '@/components/Global/PageSection'; import TableRowAction from '@/components/Global/TableRowAction'; import { mapState } from 'vuex'; @@ -46,12 +54,13 @@ import { mapState } from 'vuex'; export default { name: 'Ipv4Table', components: { + IconAdd, IconEdit, IconTrashcan, PageSection, TableRowAction, }, - mixins: [BVToastMixin], + mixins: [BVToastMixin, LoadingBarMixin], props: { tabIndex: { type: Number, @@ -102,6 +111,9 @@ export default { tabIndex() { this.getIpv4TableItems(); }, + ethernetData() { + this.getIpv4TableItems(); + }, }, created() { this.getIpv4TableItems(); @@ -122,25 +134,36 @@ export default { AddressOrigin: ipv4.AddressOrigin, actions: [ { - value: 'edit', - title: this.$t('pageNetwork.table.editIpv4'), - enabled: false, - }, - { value: 'delete', title: this.$t('pageNetwork.table.deleteIpv4'), - enabled: false, }, ], }; }); }, - onIpv4TableAction(action, row) { - if (action === 'delete') { - this.form.ipv4TableItems.splice(row, 1); - // TODO: delete row in store + onIpv4TableAction(action, $event, index) { + if ($event === 'delete') { + this.deleteIpv4TableRow(index); } }, + deleteIpv4TableRow(index) { + this.form.ipv4TableItems.splice(index, 1); + const newIpv4Array = this.form.ipv4TableItems.map((ipv4) => { + const { Address, SubnetMask, Gateway } = ipv4; + return { + Address, + SubnetMask, + Gateway, + }; + }); + this.$store + .dispatch('network/editIpv4Address', newIpv4Array) + .then((message) => this.successToast(message)) + .catch(({ message }) => this.errorToast(message)); + }, + initAddIpv4Address() { + this.$bvModal.show('modal-add-ipv4'); + }, }, }; </script> |