summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean Zhang <xiazhang@nvidia.com>2024-06-12 10:28:41 +0300
committerSean Zhang <xiazhang@nvidia.com>2024-07-06 05:02:17 +0300
commitdb47b7e1f4b8174698b1894edabf57d022fac609 (patch)
treee27e9e923037859dee7e7e814c4bc46a3e7b4ed3
parent8841b7d463a5272a87faaa14cb103f778a772770 (diff)
downloadwebui-vue-db47b7e1f4b8174698b1894edabf57d022fac609.tar.xz
Add support for IPv6 network setting
Add IPv6 setting in network setting page. - Add IPv6 domain name, DNS servers, NTP servers enable/disable - Add DHCPv6 enable/disable - Add IPv6 default gateway - Add IPv6 addresses - Add IPv6 static addresses - Add IPv6 static addresses adding and deleting Tested: - IPv6 domain name, DNS servers, NTP servers enable/disable function - DHCPv6 enable/disable function - Verified the IPv6 default gateway - IPv6 addresses adding and deleting - Verified the IPv6 addresses in IPv6 table Change-Id: I9eebf6ef5f7de748f79779d8168b8dcfcdda2495 Signed-off-by: Sean Zhang <xiazhang@nvidia.com>
-rw-r--r--src/locales/en-US.json11
-rw-r--r--src/store/modules/Settings/NetworkStore.js172
-rw-r--r--src/views/Settings/Network/ModalDefaultGateway.vue114
-rw-r--r--src/views/Settings/Network/ModalIpv6.vue133
-rw-r--r--src/views/Settings/Network/Network.vue30
-rw-r--r--src/views/Settings/Network/NetworkGlobalSettings.vue140
-rw-r--r--src/views/Settings/Network/TableIpv6.vue289
7 files changed, 859 insertions, 30 deletions
diff --git a/src/locales/en-US.json b/src/locales/en-US.json
index 8ba7ac94..44a63de3 100644
--- a/src/locales/en-US.json
+++ b/src/locales/en-US.json
@@ -703,16 +703,21 @@
},
"pageNetwork": {
"dhcp": "DHCP",
+ "dhcp6": "DHCPv6",
"domainName": "domain name",
"dns": "DNS server",
"fqdn": "FQDN",
"hostname": "Hostname",
+ "ipVersion": "Version of IP",
"interfaceSection": "Interface settings",
"ipv4": "IPv4",
"ipv4Addresses": "IPv4 addresses",
"ipv6": "IPv6",
+ "ipv6Addresses": "IPv6 addresses",
"linkStatus": "Link status",
"macAddress": "MAC address",
+ "gateway": "Gateway",
+ "ipv6DefaultGateway": "IPv6 Default Gateway",
"network": "network",
"networkSettings": "Network settings",
"ntp": "NTP server",
@@ -728,7 +733,9 @@
"dhcpConfirmTitle": "%{dhcpState} DHCP",
"editHostnameTitle": "Edit hostname",
"editMacAddressTitle": "Edit MAC address",
+ "editIPv6DefaultGatewayTitle": "Edit IPv6 Default Gateway",
"gateway": "Gateway",
+ "prefixLength": "Prefix Length",
"ipAddress": "IP address",
"staticDns": "Static DNS",
"subnetMask": "Subnet mask"
@@ -736,11 +743,15 @@
"table": {
"addDnsAddress": "Add IP address",
"addIpv4Address": "Add static IPv4 address",
+ "addIpv6Address": "Add static IPv6 address",
"addressOrigin": "Address origin",
+ "prefixLength": "Prefix Length",
"deleteDns": "Delete DNS address",
"deleteIpv4": "Delete IPv4 address",
+ "deleteIpv6": "Delete IPv6 address",
"editDns": "Edit DNS address",
"editIpv4": "Edit IPv4 address",
+ "editIpv6": "Edit IPv6 address",
"gateway": "Gateway",
"ipAddress": "IP address",
"subnet": "Subnet mask"
diff --git a/src/store/modules/Settings/NetworkStore.js b/src/store/modules/Settings/NetworkStore.js
index 7f24e198..a249d22b 100644
--- a/src/store/modules/Settings/NetworkStore.js
+++ b/src/store/modules/Settings/NetworkStore.js
@@ -29,29 +29,47 @@ const NetworkStore = {
state.globalNetworkSettings = data.map(({ data }) => {
const {
DHCPv4,
+ DHCPv6,
HostName,
IPv4Addresses,
IPv4StaticAddresses,
+ IPv6Addresses,
+ IPv6StaticAddresses,
LinkStatus,
MACAddress,
+ IPv6DefaultGateway,
} = data;
return {
defaultGateway: IPv4StaticAddresses[0]?.Gateway, //First static gateway is the default gateway
+ ipv6DefaultGateway: IPv6DefaultGateway,
dhcpAddress: IPv4Addresses.filter(
(ipv4) => ipv4.AddressOrigin === 'DHCP',
),
+ dhcpv6Address: IPv6Addresses.filter(
+ (ipv6) =>
+ ipv6.AddressOrigin === 'SLAAC' || ipv6.AddressOrigin === 'DHCPv6',
+ ),
dhcpEnabled: DHCPv4.DHCPEnabled,
+ dhcp6Enabled: DHCPv6.OperatingMode,
hostname: HostName,
macAddress: MACAddress,
linkStatus: LinkStatus,
staticAddress: IPv4StaticAddresses[0]?.Address, // Display first static address on overview page
+ ipv6StaticAddress: IPv6StaticAddresses[0]?.Address,
useDnsEnabled: DHCPv4.UseDNSServers,
useDomainNameEnabled: DHCPv4.UseDomainName,
useNtpEnabled: DHCPv4.UseNTPServers,
+ useDnsEnabledIpv6: DHCPv6.UseDNSServers,
+ useDomainNameEnabledIpv6: DHCPv6.UseDomainName,
+ useNtpEnabledIpv6: DHCPv6.UseNTPServers,
};
});
},
setNtpState: (state, ntpState) => (state.ntpState = ntpState),
+ setDomainNameStateIpv6: (state, domainState) =>
+ (state.domainStateIpv6 = domainState),
+ setDnsStateIpv6: (state, dnsState) => (state.dnsStateIpv6 = dnsState),
+ setNtpStateIpv6: (state, ntpState) => (state.ntpStateIpv6 = ntpState),
setSelectedInterfaceId: (state, selectedInterfaceId) =>
(state.selectedInterfaceId = selectedInterfaceId),
setSelectedInterfaceIndex: (state, selectedInterfaceIndex) =>
@@ -114,13 +132,49 @@ const NetworkStore = {
);
});
},
- async saveDomainNameState({ commit, state }, domainState) {
- commit('setDomainNameState', domainState);
+ async saveDhcp6EnabledState({ state, dispatch }, dhcpState) {
const data = {
- DHCPv4: {
- UseDomainName: domainState,
+ DHCPv6: {
+ OperatingMode: dhcpState ? 'Enabled' : 'Disabled',
},
};
+ return api
+ .patch(
+ `${await this.dispatch('global/getBmcPath')}/EthernetInterfaces/${state.selectedInterfaceId}`,
+ data,
+ )
+ .then(dispatch('getEthernetData'))
+ .then(() => {
+ return i18n.t('pageNetwork.toast.successSaveNetworkSettings', {
+ setting: i18n.t('pageNetwork.dhcp6'),
+ });
+ })
+ .catch((error) => {
+ console.log(error);
+ throw new Error(
+ i18n.t('pageNetwork.toast.errorSaveNetworkSettings', {
+ setting: i18n.t('pageNetwork.dhcp6'),
+ }),
+ );
+ });
+ },
+ async saveDomainNameState({ commit, state }, { domainState, ipVersion }) {
+ var data;
+ if (ipVersion === 'IPv4') {
+ commit('setDomainNameState', domainState);
+ data = {
+ DHCPv4: {
+ UseDomainName: domainState,
+ },
+ };
+ } else if (ipVersion === 'IPv6') {
+ commit('setDomainNameStateIpv6', domainState);
+ data = {
+ DHCPv6: {
+ UseDomainName: domainState,
+ },
+ };
+ }
// Saving to the first interface automatically updates DHCPv4 and DHCPv6
// on all interfaces
return api
@@ -135,7 +189,9 @@ const NetworkStore = {
})
.catch((error) => {
console.log(error);
- commit('setDomainNameState', !domainState);
+ if (ipVersion === 'IPv4') commit('setDomainNameState', !domainState);
+ else if (ipVersion === 'IPv6')
+ commit('setDomainNameStateIpv6', !domainState);
throw new Error(
i18n.t('pageNetwork.toast.errorSaveNetworkSettings', {
setting: i18n.t('pageNetwork.domainName'),
@@ -143,13 +199,23 @@ const NetworkStore = {
);
});
},
- async saveDnsState({ commit, state }, dnsState) {
- commit('setDnsState', dnsState);
- const data = {
- DHCPv4: {
- UseDNSServers: dnsState,
- },
- };
+ async saveDnsState({ commit, state }, { dnsState, ipVersion }) {
+ var data;
+ if (ipVersion === 'IPv4') {
+ commit('setDnsState', dnsState);
+ data = {
+ DHCPv4: {
+ UseDNSServers: dnsState,
+ },
+ };
+ } else if (ipVersion === 'IPv6') {
+ commit('setDnsStateIpv6', dnsState);
+ data = {
+ DHCPv6: {
+ UseDNSServers: dnsState,
+ },
+ };
+ }
// Saving to the first interface automatically updates DHCPv4 and DHCPv6
// on all interfaces
return api
@@ -164,7 +230,8 @@ const NetworkStore = {
})
.catch((error) => {
console.log(error);
- commit('setDnsState', !dnsState);
+ if (ipVersion === 'IPv4') commit('setDnsState', !dnsState);
+ else if (ipVersion === 'IPv6') commit('setDnsStateIpv6', !dnsState);
throw new Error(
i18n.t('pageNetwork.toast.errorSaveNetworkSettings', {
setting: i18n.t('pageNetwork.dns'),
@@ -172,13 +239,23 @@ const NetworkStore = {
);
});
},
- async saveNtpState({ commit, state }, ntpState) {
- commit('setNtpState', ntpState);
- const data = {
- DHCPv4: {
- UseNTPServers: ntpState,
- },
- };
+ async saveNtpState({ commit, state }, { ntpState, ipVersion }) {
+ var data;
+ if (ipVersion === 'IPv4') {
+ commit('setNtpState', ntpState);
+ data = {
+ DHCPv4: {
+ UseNTPServers: ntpState,
+ },
+ };
+ } else if (ipVersion === 'IPv6') {
+ commit('setNtpStateIpv6', ntpState);
+ data = {
+ DHCPv6: {
+ UseNTPServers: ntpState,
+ },
+ };
+ }
// Saving to the first interface automatically updates DHCPv4 and DHCPv6
// on all interfaces
return api
@@ -193,7 +270,8 @@ const NetworkStore = {
})
.catch((error) => {
console.log(error);
- commit('setNtpState', !ntpState);
+ if (ipVersion === 'IPv4') commit('setNtpState', !ntpState);
+ else if (ipVersion === 'IPv6') commit('setNtpStateIpv6', !ntpState);
throw new Error(
i18n.t('pageNetwork.toast.errorSaveNetworkSettings', {
setting: i18n.t('pageNetwork.ntp'),
@@ -239,6 +317,37 @@ const NetworkStore = {
);
});
},
+ async saveIpv6Address({ dispatch, state }, ipv6Form) {
+ const originalAddresses = state.ethernetData[
+ state.selectedInterfaceIndex
+ ].IPv6StaticAddresses.map((ipv6) => {
+ const { Address, PrefixLength } = ipv6;
+ return {
+ Address,
+ PrefixLength,
+ };
+ });
+ const newAddress = [ipv6Form];
+ return api
+ .patch(
+ `${await this.dispatch('global/getBmcPath')}/EthernetInterfaces/${state.selectedInterfaceId}`,
+ { IPv6StaticAddresses: originalAddresses.concat(newAddress) },
+ )
+ .then(dispatch('getEthernetData'))
+ .then(() => {
+ return i18n.t('pageNetwork.toast.successSaveNetworkSettings', {
+ setting: i18n.t('pageNetwork.ipv6'),
+ });
+ })
+ .catch((error) => {
+ console.log(error);
+ throw new Error(
+ i18n.t('pageNetwork.toast.errorSaveNetworkSettings', {
+ setting: i18n.t('pageNetwork.ipv6'),
+ }),
+ );
+ });
+ },
async editIpv4Address({ dispatch, state }, ipv4TableData) {
return api
.patch(
@@ -260,6 +369,27 @@ const NetworkStore = {
);
});
},
+ async editIpv6Address({ dispatch, state }, ipv6TableData) {
+ return api
+ .patch(
+ `${await this.dispatch('global/getBmcPath')}/EthernetInterfaces/${state.selectedInterfaceId}`,
+ { IPv6StaticAddresses: ipv6TableData },
+ )
+ .then(dispatch('getEthernetData'))
+ .then(() => {
+ return i18n.t('pageNetwork.toast.successSaveNetworkSettings', {
+ setting: i18n.t('pageNetwork.ipv6'),
+ });
+ })
+ .catch((error) => {
+ console.log(error);
+ throw new Error(
+ i18n.t('pageNetwork.toast.errorSaveNetworkSettings', {
+ setting: i18n.t('pageNetwork.ipv6'),
+ }),
+ );
+ });
+ },
async saveSettings({ state, dispatch }, interfaceSettingsForm) {
return api
.patch(
diff --git a/src/views/Settings/Network/ModalDefaultGateway.vue b/src/views/Settings/Network/ModalDefaultGateway.vue
new file mode 100644
index 00000000..48c05c1d
--- /dev/null
+++ b/src/views/Settings/Network/ModalDefaultGateway.vue
@@ -0,0 +1,114 @@
+<template>
+ <b-modal
+ id="modal-default-gateway"
+ ref="modal"
+ :title="$t('pageNetwork.modal.editIPv6DefaultGatewayTitle')"
+ @hidden="resetForm"
+ >
+ <b-form id="gateway-settings" @submit.prevent="handleSubmit">
+ <b-row>
+ <b-col sm="6">
+ <b-form-group
+ :label="$t('pageNetwork.gateway')"
+ label-for="defaultGateway"
+ >
+ <b-form-input
+ id="defaultGateway"
+ v-model.trim="form.defaultGateway"
+ data-test-id="network-input-gateway"
+ type="text"
+ :state="getValidationState($v.form.defaultGateway)"
+ @change="$v.form.defaultGateway.$touch()"
+ />
+ <b-form-invalid-feedback role="alert">
+ <div v-if="!$v.form.defaultGateway.required">
+ {{ $t('global.form.fieldRequired') }}
+ </div>
+ <div v-if="!$v.form.defaultGateway.validateGateway">
+ {{ $t('global.form.invalidFormat') }}
+ </div>
+ </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="gateway-settings"
+ 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 { required, helpers } from 'vuelidate/lib/validators';
+
+const validateGateway = helpers.regex(
+ 'validateGateway',
+ /^((?:[a-fA-F0-9]{1,4}:){7}[a-fA-F0-9]{1,4}|(?:[a-fA-F0-9]{1,4}:){1,7}:|(?:[a-fA-F0-9]{1,4}:){1,6}:[a-fA-F0-9]{1,4}|(?:[a-fA-F0-9]{1,4}:){1,5}(?::[a-fA-F0-9]{1,4}){1,2}|(?:[a-fA-F0-9]{1,4}:){1,4}(?::[a-fA-F0-9]{1,4}){1,3}|(?:[a-fA-F0-9]{1,4}:){1,3}(?::[a-fA-F0-9]{1,4}){1,4}|(?:[a-fA-F0-9]{1,4}:){1,2}(?::[a-fA-F0-9]{1,4}){1,5}|[a-fA-F0-9]{1,4}:(?::[a-fA-F0-9]{1,4}){1,6}|:(?::[a-fA-F0-9]{1,4}){1,7}|::|(?:[a-fA-F0-9]{1,4}:){6}(?:[0-9]{1,3}\.){3}[0-9]{1,3}|::(?:[a-fA-F0-9]{1,4}:){0,5}(?:[0-9]{1,3}\.){3}[0-9]{1,3}|(?:[a-fA-F0-9]{1,4}:){1,5}:(?:[0-9]{1,3}\.){3}[0-9]{1,3}|(?:[a-fA-F0-9]{1,4}:){1,4}:(?:[0-9]{1,3}\.){3}[0-9]{1,3}|(?:[a-fA-F0-9]{1,4}:){1,3}:(?:[0-9]{1,3}\.){3}[0-9]{1,3}|(?:[a-fA-F0-9]{1,4}:){1,2}:(?:[0-9]{1,3}\.){3}[0-9]{1,3}|[a-fA-F0-9]{1,4}:(?:[0-9]{1,3}\.){3}[0-9]{1,3}|::(?:[0-9]{1,3}\.){3}[0-9]{1,3})$/,
+);
+
+export default {
+ mixins: [VuelidateMixin],
+ props: {
+ defaultGateway: {
+ type: String,
+ default: '',
+ },
+ },
+ data() {
+ return {
+ form: {
+ defaultGateway: '',
+ },
+ };
+ },
+ watch: {
+ defaultGateway() {
+ this.form.defaultGateway = this.defaultGateway;
+ },
+ },
+ validations() {
+ return {
+ form: {
+ defaultGateway: {
+ required,
+ validateGateway,
+ },
+ },
+ };
+ },
+ methods: {
+ handleSubmit() {
+ this.$v.$touch();
+ if (this.$v.$invalid) return;
+ this.$emit('ok', { IPv6DefaultGateway: this.form.defaultGateway });
+ this.closeModal();
+ },
+ closeModal() {
+ this.$nextTick(() => {
+ this.$refs.modal.hide();
+ });
+ },
+ resetForm() {
+ this.form.defaultGateway = this.defaultGateway;
+ this.$v.$reset();
+ this.$emit('hidden');
+ },
+ onOk(bvModalEvt) {
+ // prevent modal close
+ bvModalEvt.preventDefault();
+ this.handleSubmit();
+ },
+ },
+};
+</script>
diff --git a/src/views/Settings/Network/ModalIpv6.vue b/src/views/Settings/Network/ModalIpv6.vue
new file mode 100644
index 00000000..f707a774
--- /dev/null
+++ b/src/views/Settings/Network/ModalIpv6.vue
@@ -0,0 +1,133 @@
+<template>
+ <b-modal
+ id="modal-add-ipv6"
+ ref="modal"
+ :title="$t('pageNetwork.table.addIpv6Address')"
+ @hidden="resetForm"
+ >
+ <b-form id="form-ipv6" @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.validateIpv6">
+ {{ $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.prefixLength')"
+ label-for="prefixLength"
+ >
+ <b-form-input
+ id="prefixLength"
+ v-model="form.prefixLength"
+ type="text"
+ :state="getValidationState($v.form.prefixLength)"
+ @input="$v.form.prefixLength.$touch()"
+ />
+ <b-form-invalid-feedback role="alert">
+ <template v-if="!$v.form.prefixLength.required">
+ {{ $t('global.form.fieldRequired') }}
+ </template>
+ <template v-if="!$v.form.prefixLength.validatePrefixLength">
+ {{ $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-ipv6" 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 { required, helpers } from 'vuelidate/lib/validators';
+
+const validateIpv6 = helpers.regex(
+ 'validateIpv6',
+ /^((?:[a-fA-F0-9]{1,4}:){7}[a-fA-F0-9]{1,4}|(?:[a-fA-F0-9]{1,4}:){1,7}:|(?:[a-fA-F0-9]{1,4}:){1,6}:[a-fA-F0-9]{1,4}|(?:[a-fA-F0-9]{1,4}:){1,5}(?::[a-fA-F0-9]{1,4}){1,2}|(?:[a-fA-F0-9]{1,4}:){1,4}(?::[a-fA-F0-9]{1,4}){1,3}|(?:[a-fA-F0-9]{1,4}:){1,3}(?::[a-fA-F0-9]{1,4}){1,4}|(?:[a-fA-F0-9]{1,4}:){1,2}(?::[a-fA-F0-9]{1,4}){1,5}|[a-fA-F0-9]{1,4}:(?::[a-fA-F0-9]{1,4}){1,6}|:(?::[a-fA-F0-9]{1,4}){1,7}|::|(?:[a-fA-F0-9]{1,4}:){6}(?:[0-9]{1,3}\.){3}[0-9]{1,3}|::(?:[a-fA-F0-9]{1,4}:){0,5}(?:[0-9]{1,3}\.){3}[0-9]{1,3}|(?:[a-fA-F0-9]{1,4}:){1,5}:(?:[0-9]{1,3}\.){3}[0-9]{1,3}|(?:[a-fA-F0-9]{1,4}:){1,4}:(?:[0-9]{1,3}\.){3}[0-9]{1,3}|(?:[a-fA-F0-9]{1,4}:){1,3}:(?:[0-9]{1,3}\.){3}[0-9]{1,3}|(?:[a-fA-F0-9]{1,4}:){1,2}:(?:[0-9]{1,3}\.){3}[0-9]{1,3}|[a-fA-F0-9]{1,4}:(?:[0-9]{1,3}\.){3}[0-9]{1,3}|::(?:[0-9]{1,3}\.){3}[0-9]{1,3})$/,
+);
+
+const validatePrefixLength = helpers.regex(
+ 'validatePrefixLength',
+ /^(12[0-8]|1[0-9]|[1-9][0-9]|[0-9])$/,
+);
+
+export default {
+ mixins: [VuelidateMixin],
+ data() {
+ return {
+ form: {
+ ipAddress: '',
+ prefixLength: '',
+ },
+ };
+ },
+ validations() {
+ return {
+ form: {
+ ipAddress: {
+ required,
+ validateIpv6,
+ },
+ prefixLength: {
+ required,
+ validatePrefixLength,
+ },
+ },
+ };
+ },
+ methods: {
+ handleSubmit() {
+ this.$v.$touch();
+ if (this.$v.$invalid) return;
+ this.$emit('ok', {
+ Address: this.form.ipAddress,
+ PrefixLength: parseInt(this.form.prefixLength),
+ });
+ this.closeModal();
+ },
+ closeModal() {
+ this.$nextTick(() => {
+ this.$refs.modal.hide();
+ });
+ },
+ resetForm() {
+ this.form.ipAddress = null;
+ this.form.prefixLength = 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 f731c25f..0279cbe6 100644
--- a/src/views/Settings/Network/Network.vue
+++ b/src/views/Settings/Network/Network.vue
@@ -23,6 +23,8 @@
<network-interface-settings :tab-index="tabIndex" />
<!-- IPV4 table -->
<table-ipv-4 :tab-index="tabIndex" />
+ <!-- IPV6 table -->
+ <table-ipv-6 :tab-index="tabIndex" />
<!-- Static DNS table -->
<table-dns :tab-index="tabIndex" />
</b-tab>
@@ -33,9 +35,14 @@
</page-section>
<!-- Modals -->
<modal-ipv4 :default-gateway="defaultGateway" @ok="saveIpv4Address" />
+ <modal-ipv6 @ok="saveIpv6Address" />
<modal-dns @ok="saveDnsAddress" />
<modal-hostname :hostname="currentHostname" @ok="saveSettings" />
<modal-mac-address :mac-address="currentMacAddress" @ok="saveSettings" />
+ <modal-default-gateway
+ :default-gateway="ipv6DefaultGateway"
+ @ok="saveSettings"
+ />
</b-container>
</template>
@@ -44,14 +51,17 @@ import BVToastMixin from '@/components/Mixins/BVToastMixin';
import DataFormatterMixin from '@/components/Mixins/DataFormatterMixin';
import LoadingBarMixin, { loading } from '@/components/Mixins/LoadingBarMixin';
import ModalMacAddress from './ModalMacAddress.vue';
+import ModalDefaultGateway from './ModalDefaultGateway.vue';
import ModalHostname from './ModalHostname.vue';
import ModalIpv4 from './ModalIpv4.vue';
+import ModalIpv6 from './ModalIpv6.vue';
import ModalDns from './ModalDns.vue';
import NetworkGlobalSettings from './NetworkGlobalSettings.vue';
import NetworkInterfaceSettings from './NetworkInterfaceSettings.vue';
import PageSection from '@/components/Global/PageSection';
import PageTitle from '@/components/Global/PageTitle';
import TableIpv4 from './TableIpv4.vue';
+import TableIpv6 from './TableIpv6.vue';
import TableDns from './TableDns.vue';
import { mapState } from 'vuex';
@@ -60,7 +70,9 @@ export default {
components: {
ModalHostname,
ModalMacAddress,
+ ModalDefaultGateway,
ModalIpv4,
+ ModalIpv6,
ModalDns,
NetworkGlobalSettings,
NetworkInterfaceSettings,
@@ -68,6 +80,7 @@ export default {
PageTitle,
TableDns,
TableIpv4,
+ TableIpv6,
},
mixins: [BVToastMixin, DataFormatterMixin, LoadingBarMixin],
beforeRouteLeave(to, from, next) {
@@ -79,6 +92,7 @@ export default {
currentHostname: '',
currentMacAddress: '',
defaultGateway: '',
+ ipv6DefaultGateway: '',
loading,
tabIndex: 0,
};
@@ -105,6 +119,9 @@ export default {
const networkTableIpv4 = new Promise((resolve) => {
this.$root.$on('network-table-ipv4-complete', () => resolve());
});
+ const networkTableIpv6 = new Promise((resolve) => {
+ this.$root.$on('network-table-ipv6-complete', () => resolve());
+ });
// Combine all child component Promises to indicate
// when page data load complete
Promise.all([
@@ -113,6 +130,7 @@ export default {
interfaceSettings,
networkTableDns,
networkTableIpv4,
+ networkTableIpv6,
]).finally(() => this.endLoader());
},
methods: {
@@ -131,6 +149,10 @@ export default {
this.$store.getters['network/globalNetworkSettings'][
this.tabIndex
].macAddress;
+ this.ipv6DefaultGateway =
+ this.$store.getters['network/globalNetworkSettings'][
+ this.tabIndex
+ ].ipv6DefaultGateway;
},
getTabIndex(selectedIndex) {
this.tabIndex = selectedIndex;
@@ -149,6 +171,14 @@ export default {
.catch(({ message }) => this.errorToast(message))
.finally(() => this.endLoader());
},
+ saveIpv6Address(modalFormData) {
+ this.startLoader();
+ this.$store
+ .dispatch('network/saveIpv6Address', modalFormData)
+ .then((message) => this.successToast(message))
+ .catch(({ message }) => this.errorToast(message))
+ .finally(() => this.endLoader());
+ },
saveDnsAddress(modalFormData) {
this.startLoader();
this.$store
diff --git a/src/views/Settings/Network/NetworkGlobalSettings.vue b/src/views/Settings/Network/NetworkGlobalSettings.vue
index 30287673..db834047 100644
--- a/src/views/Settings/Network/NetworkGlobalSettings.vue
+++ b/src/views/Settings/Network/NetworkGlobalSettings.vue
@@ -4,7 +4,7 @@
:section-title="$t('pageNetwork.networkSettings')"
>
<b-row>
- <b-col md="3">
+ <b-col md="2">
<dl>
<dt>
{{ $t('pageNetwork.hostname') }}
@@ -15,7 +15,14 @@
<dd>{{ dataFormatter(firstInterface.hostname) }}</dd>
</dl>
</b-col>
- <b-col md="3">
+ <b-col md="2">
+ <dl>
+ <dt>{{ $t('pageNetwork.ipVersion') }}</dt>
+ <dd>{{ $t('pageNetwork.ipv4') }}</dd>
+ <dd>{{ $t('pageNetwork.ipv6') }}</dd>
+ </dl>
+ </b-col>
+ <b-col md="2">
<dl>
<dt>{{ $t('pageNetwork.useDomainName') }}</dt>
<dd>
@@ -32,9 +39,23 @@
<span v-else>{{ $t('global.status.disabled') }}</span>
</b-form-checkbox>
</dd>
+ <dd>
+ <b-form-checkbox
+ id="useDomainNameSwitchIpv6"
+ v-model="useDomainNameStateIpv6"
+ data-test-id="networkSettings-switch-useDomainNameIpv6"
+ switch
+ @change="changeDomainNameStateIpv6"
+ >
+ <span v-if="useDomainNameStateIpv6">
+ {{ $t('global.status.enabled') }}
+ </span>
+ <span v-else>{{ $t('global.status.disabled') }}</span>
+ </b-form-checkbox>
+ </dd>
</dl>
</b-col>
- <b-col md="3">
+ <b-col md="2">
<dl>
<dt>{{ $t('pageNetwork.useDns') }}</dt>
<dd>
@@ -51,9 +72,23 @@
<span v-else>{{ $t('global.status.disabled') }}</span>
</b-form-checkbox>
</dd>
+ <dd>
+ <b-form-checkbox
+ id="useDnsSwitchIpv6"
+ v-model="useDnsStateIpv6"
+ data-test-id="networkSettings-switch-useDnsIpv6"
+ switch
+ @change="changeDnsStateIpv6"
+ >
+ <span v-if="useDnsStateIpv6">
+ {{ $t('global.status.enabled') }}
+ </span>
+ <span v-else>{{ $t('global.status.disabled') }}</span>
+ </b-form-checkbox>
+ </dd>
</dl>
</b-col>
- <b-col md="3">
+ <b-col md="2">
<dl>
<dt>{{ $t('pageNetwork.useNtp') }}</dt>
<dd>
@@ -70,6 +105,20 @@
<span v-else>{{ $t('global.status.disabled') }}</span>
</b-form-checkbox>
</dd>
+ <dd>
+ <b-form-checkbox
+ id="useNtpSwitchIpv6"
+ v-model="useNtpStateIpv6"
+ data-test-id="networkSettings-switch-useNtpIpv6"
+ switch
+ @change="changeNtpStateIpv6"
+ >
+ <span v-if="useNtpStateIpv6">
+ {{ $t('global.status.enabled') }}
+ </span>
+ <span v-else>{{ $t('global.status.disabled') }}</span>
+ </b-form-checkbox>
+ </dd>
</dl>
</b-col>
</b-row>
@@ -125,6 +174,33 @@ export default {
return newValue;
},
},
+ useDomainNameStateIpv6: {
+ get() {
+ return this.$store.getters['network/globalNetworkSettings'][0]
+ .useDomainNameEnabledIpv6;
+ },
+ set(newValue) {
+ return newValue;
+ },
+ },
+ useDnsStateIpv6: {
+ get() {
+ return this.$store.getters['network/globalNetworkSettings'][0]
+ .useDnsEnabledIpv6v6;
+ },
+ set(newValue) {
+ return newValue;
+ },
+ },
+ useNtpStateIpv6: {
+ get() {
+ return this.$store.getters['network/globalNetworkSettings'][0]
+ .useNtpEnabledIpv6;
+ },
+ set(newValue) {
+ return newValue;
+ },
+ },
},
created() {
this.$store.dispatch('network/getEthernetData').finally(() => {
@@ -135,7 +211,10 @@ export default {
methods: {
changeDomainNameState(state) {
this.$store
- .dispatch('network/saveDomainNameState', state)
+ .dispatch('network/saveDomainNameState', {
+ domainState: state,
+ ipVersion: 'IPv4',
+ })
.then((success) => {
this.successToast(success);
})
@@ -143,14 +222,57 @@ export default {
},
changeDnsState(state) {
this.$store
- .dispatch('network/saveDnsState', state)
- .then((message) => this.successToast(message))
+ .dispatch('network/saveDnsState', {
+ dnsState: state,
+ ipVersion: 'IPv4',
+ })
+ .then((message) => {
+ this.successToast(message);
+ })
.catch(({ message }) => this.errorToast(message));
},
changeNtpState(state) {
this.$store
- .dispatch('network/saveNtpState', state)
- .then((message) => this.successToast(message))
+ .dispatch('network/saveNtpState', {
+ ntpState: state,
+ ipVersion: 'IPv4',
+ })
+ .then((message) => {
+ this.successToast(message);
+ })
+ .catch(({ message }) => this.errorToast(message));
+ },
+ changeDomainNameStateIpv6(state) {
+ this.$store
+ .dispatch('network/saveDomainNameState', {
+ domainState: state,
+ ipVersion: 'IPv6',
+ })
+ .then((success) => {
+ this.successToast(success);
+ })
+ .catch(({ message }) => this.errorToast(message));
+ },
+ changeDnsStateIpv6(state) {
+ this.$store
+ .dispatch('network/saveDnsState', {
+ dnsState: state,
+ ipVersion: 'IPv6',
+ })
+ .then((message) => {
+ this.successToast(message);
+ })
+ .catch(({ message }) => this.errorToast(message));
+ },
+ changeNtpStateIpv6(state) {
+ this.$store
+ .dispatch('network/saveNtpState', {
+ ntpState: state,
+ ipVersion: 'IPv6',
+ })
+ .then((message) => {
+ this.successToast(message);
+ })
.catch(({ message }) => this.errorToast(message));
},
initSettingsModal() {
diff --git a/src/views/Settings/Network/TableIpv6.vue b/src/views/Settings/Network/TableIpv6.vue
new file mode 100644
index 00000000..5a16e9dc
--- /dev/null
+++ b/src/views/Settings/Network/TableIpv6.vue
@@ -0,0 +1,289 @@
+<template>
+ <page-section :section-title="$t('pageNetwork.ipv6')">
+ <b-row class="mb-4">
+ <b-col lg="2" md="6">
+ <dl>
+ <dt>{{ $t('pageNetwork.dhcp6') }}</dt>
+ <dd>
+ <b-form-checkbox
+ id="dhcp6Switch"
+ v-model="dhcp6EnabledState"
+ data-test-id="networkSettings-switch-dhcp6Enabled"
+ switch
+ @change="changeDhcp6EnabledState"
+ >
+ <span v-if="dhcp6EnabledState">
+ {{ $t('global.status.enabled') }}
+ </span>
+ <span v-else>{{ $t('global.status.disabled') }}</span>
+ </b-form-checkbox>
+ </dd>
+ </dl>
+ </b-col>
+ <b-col lg="2" md="6">
+ <dl class="text-nowrap">
+ <dt>
+ {{ $t('pageNetwork.ipv6DefaultGateway') }}
+ <b-button
+ v-if="defaultGatewayEditable"
+ variant="link"
+ class="p-1"
+ @click="initDefaultGatewayModal()"
+ >
+ <icon-edit
+ :title="$t('pageNetwork.modal.editIPv6DefaultGatewayTitle')"
+ />
+ </b-button>
+ </dt>
+ <dd>
+ {{ dataFormatter(defaultGateway) }}
+ </dd>
+ </dl>
+ </b-col>
+ </b-row>
+ <b-row>
+ <b-col>
+ <h3 class="h5">
+ {{ $t('pageNetwork.ipv6Addresses') }}
+ </h3>
+ </b-col>
+ <b-col class="text-right">
+ <b-button variant="primary" @click="initAddIpv6Address()">
+ <icon-add />
+ {{ $t('pageNetwork.table.addIpv6Address') }}
+ </b-button>
+ </b-col>
+ </b-row>
+ <b-table
+ responsive="md"
+ hover
+ :fields="ipv6TableFields"
+ :items="form.ipv6TableItems"
+ :empty-text="$t('global.table.emptyMessage')"
+ class="mb-0"
+ show-empty
+ >
+ <template #cell(actions)="{ item, index }">
+ <table-row-action
+ v-for="(action, actionIndex) in filteredActions(item)"
+ :key="actionIndex"
+ :value="action.value"
+ :title="action.title"
+ :enabled="action.enabled"
+ @click-table-action="onIpv6TableAction(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 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 DataFormatterMixin from '@/components/Mixins/DataFormatterMixin';
+import { mapState } from 'vuex';
+
+export default {
+ name: 'Ipv6Table',
+ components: {
+ IconAdd,
+ IconEdit,
+ IconTrashcan,
+ PageSection,
+ TableRowAction,
+ },
+ mixins: [BVToastMixin, LoadingBarMixin, DataFormatterMixin],
+ props: {
+ tabIndex: {
+ type: Number,
+ default: 0,
+ },
+ },
+ data() {
+ return {
+ form: {
+ ipv6TableItems: [],
+ },
+ actions: [
+ {
+ value: 'edit',
+ title: this.$t('global.action.edit'),
+ },
+ {
+ value: 'delete',
+ title: this.$t('global.action.delete'),
+ },
+ ],
+ ipv6TableFields: [
+ {
+ key: 'Address',
+ label: this.$t('pageNetwork.table.ipAddress'),
+ },
+ {
+ key: 'PrefixLength',
+ label: this.$t('pageNetwork.table.prefixLength'),
+ },
+ {
+ key: 'AddressOrigin',
+ label: this.$t('pageNetwork.table.addressOrigin'),
+ },
+ { key: 'actions', label: '', tdClass: 'text-right' },
+ ],
+ defaultGateway: '',
+ defaultGatewayEditable:
+ process.env.VUE_APP_ENV_NAME !== 'nvidia-bluefield',
+ };
+ },
+ computed: {
+ ...mapState('network', ['ethernetData']),
+ selectedInterface() {
+ return this.$store.getters['network/selectedInterfaceIndex'];
+ },
+ dhcp6EnabledState: {
+ get() {
+ return (
+ this.$store.getters['network/globalNetworkSettings'][
+ this.selectedInterface
+ ].dhcp6Enabled === 'Enabled'
+ );
+ },
+ set(newValue) {
+ return newValue;
+ },
+ },
+ filteredActions() {
+ return (item) => {
+ if (item.AddressOrigin === 'DHCPv6' || item.AddressOrigin === 'SLAAC') {
+ return item.actions.filter((action) => action.value !== 'delete');
+ } else {
+ return item.actions;
+ }
+ };
+ },
+ },
+ watch: {
+ // Watch for change in tab index
+ tabIndex() {
+ this.getIpv6TableItems();
+ this.getDefaultGateway();
+ },
+ ethernetData() {
+ this.getIpv6TableItems();
+ this.getDefaultGateway();
+ },
+ },
+ created() {
+ this.getIpv6TableItems();
+ this.getDefaultGateway();
+ this.$store.dispatch('network/getEthernetData').finally(() => {
+ // Emit initial data fetch complete to parent component
+ this.$root.$emit('network-table-ipv6-complete');
+ });
+ },
+ methods: {
+ getDefaultGateway() {
+ this.defaultGateway = this.ethernetData[this.tabIndex].IPv6DefaultGateway;
+ },
+ getIpv6TableItems() {
+ const index = this.tabIndex;
+ const addresses =
+ this.ethernetData[index].IPv6Addresses.filter(
+ (ipv6) =>
+ ipv6.AddressOrigin === 'LinkLocal' ||
+ ipv6.AddressOrigin === 'Static' ||
+ ipv6.AddressOrigin === 'SLAAC' ||
+ ipv6.AddressOrigin === 'DHCPv6',
+ ) || [];
+ this.form.ipv6TableItems = addresses.map((ipv6) => {
+ return {
+ Address: ipv6.Address,
+ PrefixLength: ipv6.PrefixLength,
+ AddressOrigin: ipv6.AddressOrigin,
+ actions: [
+ {
+ value: 'delete',
+ title: this.$t('pageNetwork.table.deleteIpv6'),
+ },
+ ],
+ };
+ });
+ },
+ onIpv6TableAction(action, $event, index) {
+ if ($event === 'delete') {
+ this.deleteIpv6TableRow(index);
+ }
+ },
+ deleteIpv6TableRow(index) {
+ const AddressOrigin = this.form.ipv6TableItems[index].AddressOrigin;
+ this.form.ipv6TableItems.splice(index, 1);
+ const newIpv6Array = this.form.ipv6TableItems.map((ipv6) => {
+ const { Address, PrefixLength } = ipv6;
+ return {
+ Address,
+ PrefixLength,
+ };
+ });
+ if (
+ newIpv6Array.length == 0 &&
+ (AddressOrigin === 'Static' || AddressOrigin === 'LinkLocal')
+ ) {
+ this.$store
+ .dispatch('network/saveDhcp6EnabledState', true)
+ .then((message) => this.successToast(message))
+ .catch(({ message }) => this.errorToast(message));
+ }
+ this.$store
+ .dispatch('network/editIpv6Address', newIpv6Array)
+ .then((message) => this.successToast(message))
+ .catch(({ message }) => this.errorToast(message));
+ },
+ initAddIpv6Address() {
+ this.$bvModal.show('modal-add-ipv6');
+ },
+ changeDhcp6EnabledState(state) {
+ this.$bvModal
+ .msgBoxConfirm(
+ state
+ ? this.$t('pageNetwork.modal.confirmEnableDhcp')
+ : this.$t('pageNetwork.modal.confirmDisableDhcp'),
+ {
+ title: this.$t('pageNetwork.modal.dhcpConfirmTitle', {
+ dhcpState: state
+ ? this.$t('global.action.enable')
+ : this.$t('global.action.disable'),
+ }),
+ okTitle: state
+ ? this.$t('global.action.enable')
+ : this.$t('global.action.disable'),
+ okVariant: 'danger',
+ cancelTitle: this.$t('global.action.cancel'),
+ },
+ )
+ .then((dhcpEnableConfirmed) => {
+ if (dhcpEnableConfirmed) {
+ this.$store
+ .dispatch('network/saveDhcp6EnabledState', state)
+ .then((message) => this.successToast(message))
+ .catch(({ message }) => this.errorToast(message));
+ } else {
+ let onDhcpCancel = document.getElementById('dhcp6Switch');
+ onDhcpCancel.checked = !state;
+ }
+ });
+ },
+ initDefaultGatewayModal() {
+ this.$bvModal.show('modal-default-gateway');
+ },
+ },
+};
+</script>