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/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>