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