summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDixsie Wolmers <dixsie@ibm.com>2021-10-08 00:15:50 +0300
committerDixsie Wolmers <dixsiew@gmail.com>2021-12-03 23:45:37 +0300
commitc4b8757ed88ecea369e6044548d2fbe072d5bd4a (patch)
tree6999c7a29a7a3eea03f4a096ee827c8b18acc124
parentcc79a53192de73457cb4c8c3350d896d952bb106 (diff)
downloadwebui-vue-c4b8757ed88ecea369e6044548d2fbe072d5bd4a.tar.xz
Network settings redesign - interface settings
First commit of the network settings redesign: - Adds the global network settings section for DHCP settings - Adds read only hostname, FQDN and MAC address, modal to edit each will be done seperately - Removes interface specific sections to refactor in next commit - Adds tab component to display ethernet data by interface - Ability to edit, delete and add ipv4 addresses and DNS will be done in separate commit Signed-off-by: Dixsie Wolmers <dixsie@ibm.com> Change-Id: Ibb1db6894ee697fec9e6ea1b8312d041c61faaad
-rw-r--r--src/locales/en-US.json55
-rw-r--r--src/main.js2
-rw-r--r--src/store/modules/Settings/NetworkStore.js148
-rw-r--r--src/views/Settings/Network/Network.vue683
-rw-r--r--src/views/Settings/Network/NetworkGlobalSettings.vue152
-rw-r--r--src/views/Settings/Network/NetworkInterfaceSettings.vue99
-rw-r--r--src/views/Settings/Network/TableDns.vue126
-rw-r--r--src/views/Settings/Network/TableIpv4.vue146
8 files changed, 705 insertions, 706 deletions
diff --git a/src/locales/en-US.json b/src/locales/en-US.json
index 7e35d450..3bfc4a88 100644
--- a/src/locales/en-US.json
+++ b/src/locales/en-US.json
@@ -630,38 +630,43 @@
},
"pageNetwork": {
"dhcp": "DHCP",
- "ipv4": "IPV4",
- "ipv4Configuration": "IPV4 configuration",
- "ipv4Helper": "An IP address must be available to enable DHCP or Static configuration",
- "interface": "Interface",
- "pageDescription": "Configure network settings for the BMC",
- "static": "Static",
+ "domainName": "domain name",
+ "dns": "DNS server",
+ "fqdn": "FQDN",
+ "hostname": "Hostname",
+ "interfaceSection": "Interface settings",
+ "ipv4": "IPv4",
+ "ipv4Addresses": "IPv4 addresses",
+ "ipv6": "IPv6",
+ "linkStatus": "Link status",
+ "macAddress": "MAC address",
+ "networkSettings": "Network settings",
+ "ntp": "NTP server",
+ "pageDescription": "Configure BMC network settings",
+ "useDns": "Use DNS servers",
+ "useDomainName": "Use domain name",
+ "useNtp": "Use NTP servers",
+ "speed": "Speed (mbps)",
"staticDns": "Static DNS",
- "system": "System",
- "form": {
- "defaultGateway": "Default gateway",
- "hostname": "Hostname",
- "macAddress": "MAC address",
- "networkInterface": "Network interface"
+ "modal": {
+ "ipAddress": "IP address",
+ "gateway": "Gateway",
+ "subnetMask": "Subnet mask"
},
"table": {
- "addDns": "Add DNS server",
- "addStaticIpv4Address": "Add static IP",
- "deleteDhcpIpv4": "Delete IPv4 row",
- "deleteDns": "Delete DNS row",
- "deleteStaticIpv4": "Delete IPv4 row",
- "dhcpIpv4AddressRow": "DHCP IPv4 address",
- "dhcpIpv4SubnetRow": "DHCP IPV4 subnet",
+ "addIpv4Address": "Add static IPv4 address",
+ "addressOrigin": "Address origin",
+ "deleteDns": "Edit DNS address",
+ "deleteIpv4": "Edit IPv4 address",
+ "editDns": "Edit DNS address",
+ "editIpv4": "Edit IPv4 address",
+ "gateway": "Gateway",
"ipAddress": "IP address",
- "staticDnsRow": "Static DNS address",
- "staticIpv4AddressRow": "Static IPv4 address",
- "staticIpv4SubnetRow": "Static IPV4 subnet",
"subnet": "Subnet mask"
},
"toast": {
- "errorSaveDhcpSettings": "Error enabling DHCP configuration.",
- "errorSaveNetworkSettings": "Error saving network settings.",
- "successSaveNetworkSettings": "Successfully saved network settings."
+ "errorSaveNetworkSettings": "Error updating %{setting} settings.",
+ "successSaveNetworkSettings": "Successfully updated %{setting} settings."
}
},
"pagePageNotFound": {
diff --git a/src/main.js b/src/main.js
index 8fcb8089..0aae716f 100644
--- a/src/main.js
+++ b/src/main.js
@@ -34,6 +34,7 @@ import {
PaginationPlugin,
ProgressPlugin,
TablePlugin,
+ TabsPlugin,
ToastPlugin,
TooltipPlugin,
} from 'bootstrap-vue';
@@ -123,6 +124,7 @@ Vue.use(NavPlugin);
Vue.use(PaginationPlugin);
Vue.use(ProgressPlugin);
Vue.use(TablePlugin);
+Vue.use(TabsPlugin);
Vue.use(ToastPlugin);
Vue.use(TooltipPlugin);
Vue.use(Vuelidate);
diff --git a/src/store/modules/Settings/NetworkStore.js b/src/store/modules/Settings/NetworkStore.js
index 65a83b44..5b95cb00 100644
--- a/src/store/modules/Settings/NetworkStore.js
+++ b/src/store/modules/Settings/NetworkStore.js
@@ -1,43 +1,42 @@
import api from '@/store/api';
import i18n from '@/i18n';
-import { find, remove } from 'lodash';
const NetworkStore = {
namespaced: true,
state: {
- defaultGateway: '',
ethernetData: [],
- interfaceOptions: [],
+ firstInterfaceId: '', //used for setting global DHCP settings
globalNetworkSettings: [],
},
getters: {
- defaultGateway: (state) => state.defaultGateway,
ethernetData: (state) => state.ethernetData,
- interfaceOptions: (state) => state.interfaceOptions,
+ firstInterfaceId: (state) => state.firstInterfaceId,
globalNetworkSettings: (state) => state.globalNetworkSettings,
},
mutations: {
- setDefaultGateway: (state, defaultGateway) =>
- (state.defaultGateway = defaultGateway),
setEthernetData: (state, ethernetData) =>
(state.ethernetData = ethernetData),
- setInterfaceOptions: (state, interfaceOptions) =>
- (state.interfaceOptions = interfaceOptions),
+ setFirstInterfaceId: (state, firstInterfaceId) =>
+ (state.firstInterfaceId = firstInterfaceId),
setGlobalNetworkSettings: (state, data) => {
state.globalNetworkSettings = data.map(({ data }) => {
const {
+ DHCPv4,
HostName,
- LinkStatus,
- IPv4StaticAddresses,
IPv4Addresses,
+ IPv4StaticAddresses,
+ LinkStatus,
} = data;
return {
- hostname: HostName,
- linkStatus: LinkStatus,
- staticAddress: IPv4StaticAddresses[0]?.Address,
dhcpAddress: IPv4Addresses.filter(
(ipv4) => ipv4.AddressOrigin === 'DHCP'
),
+ hostname: HostName,
+ linkStatus: LinkStatus,
+ staticAddress: IPv4StaticAddresses[0]?.Address, // Display first static address on overview page
+ useDnsEnabled: DHCPv4.UseDNSServers,
+ useDomainNameEnabled: DHCPv4.UseDomainName,
+ useNtpEnabled: DHCPv4.UseNTPServers,
};
});
},
@@ -62,70 +61,101 @@ const NetworkStore = {
const ethernetData = ethernetInterfaces.map(
(ethernetInterface) => ethernetInterface.data
);
- const interfaceOptions = ethernetInterfaces.map(
- (ethernetName) => ethernetName.data.Id
- );
- const addresses = ethernetData[0].IPv4StaticAddresses;
-
- // Default gateway manually set to first gateway saved on the first interface. Default gateway property is WIP on backend
- const defaultGateway = addresses.map((ipv4) => {
- return ipv4.Gateway;
- });
+ const firstInterfaceId = ethernetData[0].Id;
- commit('setGlobalNetworkSettings', ethernetInterfaces);
- commit('setDefaultGateway', defaultGateway[0]);
commit('setEthernetData', ethernetData);
- commit('setInterfaceOptions', interfaceOptions);
+ commit('setFirstInterfaceId', firstInterfaceId);
+ commit('setGlobalNetworkSettings', ethernetInterfaces);
})
.catch((error) => {
console.log('Network Data:', error);
});
},
-
- async updateInterfaceSettings({ dispatch, state }, networkSettingsForm) {
- const updatedAddresses = networkSettingsForm.staticIpv4;
- const originalAddresses =
- state.ethernetData[networkSettingsForm.selectedInterfaceIndex]
- .IPv4StaticAddresses;
-
- const addressArray = originalAddresses.map((item) => {
- const address = item.Address;
- if (find(updatedAddresses, { Address: address })) {
- remove(updatedAddresses, (item) => {
- return item.Address === address;
+ async saveDomainNameState({ commit, state }, domainState) {
+ commit('setDomainNameState', domainState);
+ const data = {
+ DHCPv4: {
+ UseDomainName: domainState,
+ },
+ };
+ // Saving to the first interface automatically updates DHCPv4 and DHCPv6
+ // on all interfaces
+ return api
+ .patch(
+ `/redfish/v1/Managers/bmc/EthernetInterfaces/${state.firstInterfaceId}`,
+ data
+ )
+ .then(() => {
+ return i18n.t('pageNetwork.toast.successSaveNetworkSettings', {
+ setting: i18n.t('pageNetwork.domainName'),
});
- return {};
- } else {
- return null;
- }
- });
-
+ })
+ .catch((error) => {
+ console.log(error);
+ commit('setDomainNameState', !domainState);
+ throw new Error(
+ i18n.t('pageNetwork.toast.errorSaveNetworkSettings', {
+ setting: i18n.t('pageNetwork.domainName'),
+ })
+ );
+ });
+ },
+ async saveDnsState({ commit, state }, dnsState) {
+ commit('setDnsState', dnsState);
const data = {
- HostName: networkSettingsForm.hostname,
- MACAddress: networkSettingsForm.macAddress,
DHCPv4: {
- DHCPEnabled: networkSettingsForm.isDhcpEnabled,
+ UseDNSServers: dnsState,
},
};
-
- // If DHCP disabled, update static DNS or static ipv4
- if (!networkSettingsForm.isDhcpEnabled) {
- data.IPv4StaticAddresses = [...addressArray, ...updatedAddresses];
- data.StaticNameServers = networkSettingsForm.staticNameServers;
- }
-
- return await api
+ // Saving to the first interface automatically updates DHCPv4 and DHCPv6
+ // on all interfaces
+ return api
.patch(
- `/redfish/v1/Managers/bmc/EthernetInterfaces/${networkSettingsForm.interfaceId}`,
+ `/redfish/v1/Managers/bmc/EthernetInterfaces/${state.firstInterfaceId}`,
data
)
- .then(() => dispatch('getEthernetData'))
.then(() => {
- return i18n.t('pageNetwork.toast.successSaveNetworkSettings');
+ return i18n.t('pageNetwork.toast.successSaveNetworkSettings', {
+ setting: i18n.t('pageNetwork.dns'),
+ });
})
.catch((error) => {
console.log(error);
- throw new Error(i18n.t('pageNetwork.toast.errorSaveNetworkSettings'));
+ commit('setDnsState', !dnsState);
+ throw new Error(
+ i18n.t('pageNetwork.toast.errorSaveNetworkSettings', {
+ setting: i18n.t('pageNetwork.dns'),
+ })
+ );
+ });
+ },
+ async saveNtpState({ commit, state }, ntpState) {
+ commit('setNtpState', ntpState);
+ const data = {
+ DHCPv4: {
+ UseDNSServers: ntpState,
+ },
+ };
+ // Saving to the first interface automatically updates DHCPv4 and DHCPv6
+ // on all interfaces
+ return api
+ .patch(
+ `/redfish/v1/Managers/bmc/EthernetInterfaces/${state.firstInterfaceId}`,
+ data
+ )
+ .then(() => {
+ return i18n.t('pageNetwork.toast.successSaveNetworkSettings', {
+ setting: i18n.t('pageNetwork.ntp'),
+ });
+ })
+ .catch((error) => {
+ console.log(error);
+ commit('setNtpState', !ntpState);
+ throw new Error(
+ i18n.t('pageNetwork.toast.errorSaveNetworkSettings', {
+ setting: i18n.t('pageNetwork.ntp'),
+ })
+ );
});
},
},
diff --git a/src/views/Settings/Network/Network.vue b/src/views/Settings/Network/Network.vue
index ab5003f8..918c8e98 100644
--- a/src/views/Settings/Network/Network.vue
+++ b/src/views/Settings/Network/Network.vue
@@ -1,663 +1,102 @@
<template>
<b-container fluid="xl">
<page-title :description="$t('pageNetwork.pageDescription')" />
- <page-section :section-title="$t('pageNetwork.interface')">
+ <!-- Global settings for all interfaces -->
+ <network-global-settings />
+ <!-- Interface tabs -->
+ <page-section>
<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-col>
+ <b-card no-body>
+ <b-tabs
+ active-nav-item-class="font-weight-bold"
+ card
+ content-class="mt-3"
>
- </b-form-select>
- </b-form-group>
+ <b-tab
+ v-for="(data, index) in ethernetData"
+ :key="data.Id"
+ :title="data.Id"
+ @click="getTabIndex(index)"
+ >
+ <!-- Interface settings -->
+ <network-interface-settings :tab-index="tabIndex" />
+ <!-- IPV4 table -->
+ <table-ipv-4 :tab-index="tabIndex" />
+ <!-- Static DNS table -->
+ <table-dns :tab-index="tabIndex" />
+ </b-tab>
+ </b-tabs>
+ </b-card>
</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 DataFormatterMixin from '@/components/Mixins/DataFormatterMixin';
import LoadingBarMixin, { loading } from '@/components/Mixins/LoadingBarMixin';
+import NetworkGlobalSettings from './NetworkGlobalSettings.vue';
+import NetworkInterfaceSettings from './NetworkInterfaceSettings.vue';
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 TableIpv4 from './TableIpv4.vue';
+import TableDns from './TableDns.vue';
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,
+ NetworkGlobalSettings,
+ NetworkInterfaceSettings,
PageSection,
- TableRowAction,
- IconTrashcan,
- IconAdd,
+ PageTitle,
+ TableDns,
+ TableIpv4,
},
- mixins: [BVToastMixin, VuelidateMixin, LoadingBarMixin],
+ mixins: [BVToastMixin, DataFormatterMixin, 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,
- },
- },
- },
- },
+ tabIndex: 0,
};
},
computed: {
- ...mapState('network', [
- 'ethernetData',
- 'interfaceOptions',
- 'defaultGateway',
- ]),
- interfaceSelectOptions() {
- return this.interfaceOptions.map((option, index) => {
- return {
- text: option,
- value: index,
- };
- });
- },
- },
- watch: {
- ethernetData: function () {
- this.selectInterface();
- },
+ ...mapState('network', ['ethernetData']),
},
created() {
this.startLoader();
- this.$store
- .dispatch('network/getEthernetData')
- .finally(() => this.endLoader());
+ const globalSettings = new Promise((resolve) => {
+ this.$root.$on('network-global-settings-complete', () => resolve());
+ });
+ const interfaceSettings = new Promise((resolve) => {
+ this.$root.$on('network-interface-settings-complete', () => resolve());
+ });
+ const networkTableDns = new Promise((resolve) => {
+ this.$root.$on('network-table-dns-complete', () => resolve());
+ });
+ const networkTableIpv4 = new Promise((resolve) => {
+ this.$root.$on('network-table-ipv4-complete', () => resolve());
+ });
+ // Combine all child component Promises to indicate
+ // when page data load complete
+ Promise.all([
+ this.$store.dispatch('network/getEthernetData'),
+ globalSettings,
+ interfaceSettings,
+ networkTableDns,
+ networkTableIpv4,
+ ]).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();
- });
+ getTabIndex(selectedIndex) {
+ this.tabIndex = selectedIndex;
},
},
};
diff --git a/src/views/Settings/Network/NetworkGlobalSettings.vue b/src/views/Settings/Network/NetworkGlobalSettings.vue
new file mode 100644
index 00000000..fc82c86d
--- /dev/null
+++ b/src/views/Settings/Network/NetworkGlobalSettings.vue
@@ -0,0 +1,152 @@
+<template>
+ <page-section
+ v-if="firstInterface"
+ :section-title="$t('pageNetwork.networkSettings')"
+ >
+ <b-row>
+ <b-col md="3">
+ <dl>
+ <dt>{{ $t('pageNetwork.hostname') }}</dt>
+ <dd>{{ dataFormatter(firstInterface.hostname) }}</dd>
+ </dl>
+ </b-col>
+ <b-col md="3">
+ <dl>
+ <dt>{{ $t('pageNetwork.useDomainName') }}</dt>
+ <dd>
+ <b-form-checkbox
+ id="useDomainNameSwitch"
+ v-model="useDomainNameState"
+ data-test-id="networkSettings-switch-useDomainName"
+ switch
+ @change="changeDomainNameState"
+ >
+ <span v-if="useDomainNameState">
+ {{ $t('global.status.enabled') }}
+ </span>
+ <span v-else>{{ $t('global.status.disabled') }}</span>
+ </b-form-checkbox>
+ </dd>
+ </dl>
+ </b-col>
+ <b-col md="3">
+ <dl>
+ <dt>{{ $t('pageNetwork.useDns') }}</dt>
+ <dd>
+ <b-form-checkbox
+ id="useDnsSwitch"
+ v-model="useDnsState"
+ data-test-id="networkSettings-switch-useDns"
+ switch
+ @change="changeDnsState"
+ >
+ <span v-if="useDnsState">
+ {{ $t('global.status.enabled') }}
+ </span>
+ <span v-else>{{ $t('global.status.disabled') }}</span>
+ </b-form-checkbox>
+ </dd>
+ </dl>
+ </b-col>
+ <b-col md="3">
+ <dl>
+ <dt>{{ $t('pageNetwork.useNtp') }}</dt>
+ <dd>
+ <b-form-checkbox
+ id="useNtpSwitch"
+ v-model="useNtpState"
+ data-test-id="networkSettings-switch-useNtp"
+ switch
+ @change="changeNtpState"
+ >
+ <span v-if="useNtpState">
+ {{ $t('global.status.enabled') }}
+ </span>
+ <span v-else>{{ $t('global.status.disabled') }}</span>
+ </b-form-checkbox>
+ </dd>
+ </dl>
+ </b-col>
+ </b-row>
+ </page-section>
+</template>
+
+<script>
+import BVToastMixin from '@/components/Mixins/BVToastMixin';
+import DataFormatterMixin from '@/components/Mixins/DataFormatterMixin';
+import PageSection from '@/components/Global/PageSection';
+import { mapState } from 'vuex';
+
+export default {
+ name: 'GlobalNetworkSettings',
+ components: { PageSection },
+ mixins: [BVToastMixin, DataFormatterMixin],
+
+ data() {
+ return {
+ hostname: '',
+ };
+ },
+ computed: {
+ ...mapState('network', ['ethernetData']),
+ firstInterface() {
+ return this.$store.getters['network/globalNetworkSettings'][0];
+ },
+ useDomainNameState: {
+ get() {
+ return this.$store.getters['network/globalNetworkSettings'][0]
+ .useDomainNameEnabled;
+ },
+ set(newValue) {
+ return newValue;
+ },
+ },
+ useDnsState: {
+ get() {
+ return this.$store.getters['network/globalNetworkSettings'][0]
+ .useDnsEnabled;
+ },
+ set(newValue) {
+ return newValue;
+ },
+ },
+ useNtpState: {
+ get() {
+ return this.$store.getters['network/globalNetworkSettings'][0]
+ .useNtpEnabled;
+ },
+ set(newValue) {
+ return newValue;
+ },
+ },
+ },
+ created() {
+ this.$store.dispatch('network/getEthernetData').finally(() => {
+ // Emit initial data fetch complete to parent component
+ this.$root.$emit('network-global-settings-complete');
+ });
+ },
+ methods: {
+ changeDomainNameState(state) {
+ this.$store
+ .dispatch('network/saveDomainNameState', state)
+ .then((success) => {
+ this.successToast(success);
+ })
+ .catch(({ message }) => this.errorToast(message));
+ },
+ changeDnsState(state) {
+ this.$store
+ .dispatch('network/saveDnsState', state)
+ .then((message) => this.successToast(message))
+ .catch(({ message }) => this.errorToast(message));
+ },
+ changeNtpState(state) {
+ this.$store
+ .dispatch('network/saveNtpState', state)
+ .then((message) => this.successToast(message))
+ .catch(({ message }) => this.errorToast(message));
+ },
+ },
+};
+</script>
diff --git a/src/views/Settings/Network/NetworkInterfaceSettings.vue b/src/views/Settings/Network/NetworkInterfaceSettings.vue
new file mode 100644
index 00000000..bdcba4d8
--- /dev/null
+++ b/src/views/Settings/Network/NetworkInterfaceSettings.vue
@@ -0,0 +1,99 @@
+<template>
+ <div>
+ <page-section>
+ <b-row>
+ <b-col md="3">
+ <dl>
+ <dt>{{ $t('pageNetwork.linkStatus') }}</dt>
+ <dd>
+ {{ dataFormatter(linkStatus) }}
+ </dd>
+ </dl>
+ </b-col>
+ <b-col md="3">
+ <dl>
+ <dt>{{ $t('pageNetwork.speed') }}</dt>
+ <dd>
+ {{ dataFormatter(linkSpeed) }}
+ </dd>
+ </dl>
+ </b-col>
+ </b-row>
+ </page-section>
+ <page-section :section-title="$t('pageNetwork.interfaceSection')">
+ <b-row>
+ <b-col md="3">
+ <dl>
+ <dt>{{ $t('pageNetwork.fqdn') }}</dt>
+ <dd>
+ {{ dataFormatter(fqdn) }}
+ </dd>
+ </dl>
+ </b-col>
+ <b-col md="3">
+ <dl class="text-nowrap">
+ <dt>{{ $t('pageNetwork.macAddress') }}</dt>
+ <dd>
+ {{ dataFormatter(macAddress) }}
+ </dd>
+ </dl>
+ </b-col>
+ </b-row>
+ </page-section>
+ </div>
+</template>
+
+<script>
+import BVToastMixin from '@/components/Mixins/BVToastMixin';
+import PageSection from '@/components/Global/PageSection';
+import DataFormatterMixin from '@/components/Mixins/DataFormatterMixin';
+import { mapState } from 'vuex';
+
+export default {
+ name: 'Ipv4Table',
+ components: {
+ PageSection,
+ },
+ mixins: [BVToastMixin, DataFormatterMixin],
+ props: {
+ tabIndex: {
+ type: Number,
+ default: 0,
+ },
+ },
+ data() {
+ return {
+ selectedInterface: '',
+ linkStatus: '',
+ linkSpeed: '',
+ fqdn: '',
+ macAddress: '',
+ };
+ },
+ computed: {
+ ...mapState('network', ['ethernetData']),
+ },
+ watch: {
+ // Watch for change in tab index
+ tabIndex() {
+ this.getSettings();
+ },
+ },
+ created() {
+ this.getSettings();
+ this.$store.dispatch('network/getEthernetData').finally(() => {
+ // Emit initial data fetch complete to parent component
+ this.$root.$emit('network-interface-settings-complete');
+ });
+ },
+ methods: {
+ getSettings() {
+ this.selectedInterface = this.tabIndex;
+ this.linkStatus = this.ethernetData[this.selectedInterface].LinkStatus;
+ this.linkSpeed = this.ethernetData[this.selectedInterface].SpeedMbps;
+ this.fqdn = this.ethernetData[this.selectedInterface].FQDN;
+ this.macAddress = this.ethernetData[this.selectedInterface].MACAddress;
+ },
+ },
+};
+</script>
diff --git a/src/views/Settings/Network/TableDns.vue b/src/views/Settings/Network/TableDns.vue
new file mode 100644
index 00000000..2578ba36
--- /dev/null
+++ b/src/views/Settings/Network/TableDns.vue
@@ -0,0 +1,126 @@
+<template>
+ <page-section :section-title="$t('pageNetwork.staticDns')">
+ <b-row>
+ <b-col lg="6">
+ <b-table
+ responsive="md"
+ hover
+ :fields="dnsTableFields"
+ :items="form.dnsStaticTableItems"
+ :empty-text="$t('global.table.emptyMessage')"
+ class="mb-0"
+ show-empty
+ >
+ <template #cell(actions)="{ item }">
+ <table-row-action
+ v-for="(action, actionIndex) in item.actions"
+ :key="actionIndex"
+ :value="action.value"
+ :title="action.title"
+ :enabled="action.enabled"
+ @click-table-action="onDnsTableAction(action, $event, index)"
+ >
+ <template #icon>
+ <icon-edit v-if="action.value === 'edit'" />
+ <icon-trashcan v-if="action.value === 'delete'" />
+ </template>
+ </table-row-action>
+ </template>
+ </b-table>
+ </b-col>
+ </b-row>
+ </page-section>
+</template>
+
+<script>
+import BVToastMixin from '@/components/Mixins/BVToastMixin';
+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';
+import TableRowAction from '@/components/Global/TableRowAction';
+import { mapState } from 'vuex';
+
+export default {
+ name: 'DNSTable',
+ components: {
+ IconEdit,
+ IconTrashcan,
+ PageSection,
+ TableRowAction,
+ },
+ mixins: [BVToastMixin],
+ props: {
+ tabIndex: {
+ type: Number,
+ default: 0,
+ },
+ },
+ data() {
+ return {
+ form: {
+ dnsStaticTableItems: [],
+ },
+ actions: [
+ {
+ value: 'edit',
+ title: this.$t('global.action.edit'),
+ },
+ {
+ value: 'delete',
+ title: this.$t('global.action.delete'),
+ },
+ ],
+ dnsTableFields: [
+ {
+ key: 'address',
+ label: this.$t('pageNetwork.table.ipAddress'),
+ },
+ { key: 'actions', label: '', tdClass: 'text-right' },
+ ],
+ };
+ },
+ computed: {
+ ...mapState('network', ['ethernetData']),
+ },
+ watch: {
+ // Watch for change in tab index
+ tabIndex() {
+ this.getStaticDnsItems();
+ },
+ },
+ created() {
+ this.getStaticDnsItems();
+ this.$store.dispatch('network/getEthernetData').finally(() => {
+ // Emit initial data fetch complete to parent component
+ this.$root.$emit('network-table-dns-complete');
+ });
+ },
+ methods: {
+ getStaticDnsItems() {
+ const index = this.tabIndex;
+ const dns = this.ethernetData[index].StaticNameServers || [];
+ this.form.dnsStaticTableItems = dns.map((server) => {
+ return {
+ address: server,
+ actions: [
+ {
+ value: 'edit',
+ title: this.$t('pageNetwork.table.editDns'),
+ },
+ {
+ value: 'delete',
+ title: this.$t('pageNetwork.table.deleteDns'),
+ },
+ ],
+ };
+ });
+ },
+ onDnsTableAction(action, row) {
+ if (action === 'delete') {
+ this.form.dnsStaticTableItems.splice(row, 1);
+ // TODO: delete row in store
+ }
+ },
+ },
+};
+</script>
diff --git a/src/views/Settings/Network/TableIpv4.vue b/src/views/Settings/Network/TableIpv4.vue
new file mode 100644
index 00000000..5e4bb7b5
--- /dev/null
+++ b/src/views/Settings/Network/TableIpv4.vue
@@ -0,0 +1,146 @@
+<template>
+ <page-section :section-title="$t('pageNetwork.ipv4')">
+ <b-row>
+ <b-col>
+ <h3 class="h5">
+ {{ $t('pageNetwork.ipv4Addresses') }}
+ </h3>
+ </b-col>
+ </b-row>
+ <b-table
+ responsive="md"
+ hover
+ :fields="ipv4TableFields"
+ :items="form.ipv4TableItems"
+ :empty-text="$t('global.table.emptyMessage')"
+ class="mb-0"
+ show-empty
+ >
+ <template #cell(actions)="{ item }">
+ <table-row-action
+ v-for="(action, actionIndex) in item.actions"
+ :key="actionIndex"
+ :value="action.value"
+ :title="action.title"
+ :enabled="action.enabled"
+ @click-table-action="onIpv4TableAction(action, $event, index)"
+ >
+ <template #icon>
+ <icon-edit v-if="action.value === 'edit'" />
+ <icon-trashcan v-if="action.value === 'delete'" />
+ </template>
+ </table-row-action>
+ </template>
+ </b-table>
+ </page-section>
+</template>
+
+<script>
+import BVToastMixin from '@/components/Mixins/BVToastMixin';
+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';
+import TableRowAction from '@/components/Global/TableRowAction';
+import { mapState } from 'vuex';
+
+export default {
+ name: 'Ipv4Table',
+ components: {
+ IconEdit,
+ IconTrashcan,
+ PageSection,
+ TableRowAction,
+ },
+ mixins: [BVToastMixin],
+ props: {
+ tabIndex: {
+ type: Number,
+ default: 0,
+ },
+ },
+ data() {
+ return {
+ form: {
+ ipv4TableItems: [],
+ },
+ actions: [
+ {
+ value: 'edit',
+ title: this.$t('global.action.edit'),
+ },
+ {
+ value: 'delete',
+ title: this.$t('global.action.delete'),
+ },
+ ],
+ ipv4TableFields: [
+ {
+ key: 'Address',
+ label: this.$t('pageNetwork.table.ipAddress'),
+ },
+ {
+ key: 'Gateway',
+ label: this.$t('pageNetwork.table.gateway'),
+ },
+ {
+ key: 'SubnetMask',
+ label: this.$t('pageNetwork.table.subnet'),
+ },
+ {
+ key: 'AddressOrigin',
+ label: this.$t('pageNetwork.table.addressOrigin'),
+ },
+ { key: 'actions', label: '', tdClass: 'text-right' },
+ ],
+ };
+ },
+ computed: {
+ ...mapState('network', ['ethernetData']),
+ },
+ watch: {
+ // Watch for change in tab index
+ tabIndex() {
+ this.getIpv4TableItems();
+ },
+ },
+ created() {
+ this.getIpv4TableItems();
+ this.$store.dispatch('network/getEthernetData').finally(() => {
+ // Emit initial data fetch complete to parent component
+ this.$root.$emit('network-table-ipv4-complete');
+ });
+ },
+ methods: {
+ getIpv4TableItems() {
+ const index = this.tabIndex;
+ const addresses = this.ethernetData[index].IPv4Addresses || [];
+ this.form.ipv4TableItems = addresses.map((ipv4) => {
+ return {
+ Address: ipv4.Address,
+ SubnetMask: ipv4.SubnetMask,
+ Gateway: ipv4.Gateway,
+ 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
+ }
+ },
+ },
+};
+</script>