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>
diff --git a/src/locales/en-US.json b/src/locales/en-US.json
index 8ba7ac9..44a63de 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 7f24e19..a249d22 100644
--- a/src/store/modules/Settings/NetworkStore.js
+++ b/src/store/modules/Settings/NetworkStore.js
@@ -29,29 +29,47 @@
       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 @@
           );
         });
     },
-    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 @@
         })
         .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 @@
           );
         });
     },
-    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 @@
         })
         .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 @@
           );
         });
     },
-    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 @@
         })
         .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 @@
           );
         });
     },
+    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 @@
           );
         });
     },
+    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 0000000..48c05c1
--- /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 0000000..f707a77
--- /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 f731c25..0279cbe 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 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 @@
   components: {
     ModalHostname,
     ModalMacAddress,
+    ModalDefaultGateway,
     ModalIpv4,
+    ModalIpv6,
     ModalDns,
     NetworkGlobalSettings,
     NetworkInterfaceSettings,
@@ -68,6 +80,7 @@
     PageTitle,
     TableDns,
     TableIpv4,
+    TableIpv6,
   },
   mixins: [BVToastMixin, DataFormatterMixin, LoadingBarMixin],
   beforeRouteLeave(to, from, next) {
@@ -79,6 +92,7 @@
       currentHostname: '',
       currentMacAddress: '',
       defaultGateway: '',
+      ipv6DefaultGateway: '',
       loading,
       tabIndex: 0,
     };
@@ -105,6 +119,9 @@
     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 @@
       interfaceSettings,
       networkTableDns,
       networkTableIpv4,
+      networkTableIpv6,
     ]).finally(() => this.endLoader());
   },
   methods: {
@@ -131,6 +149,10 @@
         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 @@
         .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 3028767..db83404 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 @@
         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 @@
   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 @@
     },
     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 0000000..5a16e9d
--- /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>