Network settings redesign - interface settings

First commit of the network settings redesign:

- Adds the global network settings section for DHCP settings
- Adds read only hostname, FQDN and MAC address,
modal to edit each will be done seperately
- Removes interface specific sections to refactor in next commit
- Adds tab component to display ethernet data by interface
- Ability  to edit, delete and add ipv4 addresses and DNS will
be done in separate commit

Signed-off-by: Dixsie Wolmers <dixsie@ibm.com>
Change-Id: Ibb1db6894ee697fec9e6ea1b8312d041c61faaad
diff --git a/src/views/Settings/Network/Network.vue b/src/views/Settings/Network/Network.vue
index ab5003f..918c8e9 100644
--- a/src/views/Settings/Network/Network.vue
+++ b/src/views/Settings/Network/Network.vue
@@ -1,663 +1,102 @@
 <template>
   <b-container fluid="xl">
     <page-title :description="$t('pageNetwork.pageDescription')" />
-    <page-section :section-title="$t('pageNetwork.interface')">
+    <!-- Global settings for all interfaces -->
+    <network-global-settings />
+    <!-- Interface tabs -->
+    <page-section>
       <b-row>
-        <b-col lg="3">
-          <b-form-group
-            label-for="interface-select"
-            :label="$t('pageNetwork.form.networkInterface')"
-          >
-            <b-form-select
-              id="interface-select"
-              v-model="selectedInterfaceIndex"
-              :disabled="loading"
-              data-test-id="network-select-interface"
-              :options="interfaceSelectOptions"
-              @change="selectInterface"
+        <b-col>
+          <b-card no-body>
+            <b-tabs
+              active-nav-item-class="font-weight-bold"
+              card
+              content-class="mt-3"
             >
-            </b-form-select>
-          </b-form-group>
+              <b-tab
+                v-for="(data, index) in ethernetData"
+                :key="data.Id"
+                :title="data.Id"
+                @click="getTabIndex(index)"
+              >
+                <!-- Interface settings -->
+                <network-interface-settings :tab-index="tabIndex" />
+                <!-- IPV4 table -->
+                <table-ipv-4 :tab-index="tabIndex" />
+                <!-- Static DNS table -->
+                <table-dns :tab-index="tabIndex" />
+              </b-tab>
+            </b-tabs>
+          </b-card>
         </b-col>
       </b-row>
     </page-section>
-    <b-form novalidate @submit.prevent="submitForm">
-      <b-form-group :disabled="loading">
-        <page-section :section-title="$t('pageNetwork.system')">
-          <b-row>
-            <b-col lg="3">
-              <b-form-group
-                :label="$t('pageNetwork.form.defaultGateway')"
-                label-for="default-gateway"
-              >
-                <b-form-input
-                  id="default-gateway"
-                  v-model.trim="form.gateway"
-                  data-test-id="network-input-gateway"
-                  type="text"
-                  :state="getValidationState($v.form.gateway)"
-                  @change="$v.form.gateway.$touch()"
-                />
-                <b-form-invalid-feedback role="alert">
-                  <div v-if="!$v.form.gateway.required">
-                    {{ $t('global.form.fieldRequired') }}
-                  </div>
-                  <div v-if="!$v.form.gateway.ipAddress">
-                    {{ $t('global.form.invalidFormat') }}
-                  </div>
-                </b-form-invalid-feedback>
-              </b-form-group>
-            </b-col>
-            <b-col lg="3">
-              <b-form-group
-                :label="$t('pageNetwork.form.hostname')"
-                label-for="hostname-field"
-              >
-                <b-form-input
-                  id="hostname-field"
-                  v-model.trim="form.hostname"
-                  data-test-id="network-input-hostname"
-                  type="text"
-                  :state="getValidationState($v.form.hostname)"
-                  @change="$v.form.hostname.$touch()"
-                />
-                <b-form-invalid-feedback role="alert">
-                  <div v-if="!$v.form.hostname.required">
-                    {{ $t('global.form.fieldRequired') }}
-                  </div>
-                  <div v-if="!$v.form.hostname.validateHostname">
-                    {{
-                      $t('global.form.lengthMustBeBetween', { min: 1, max: 64 })
-                    }}
-                  </div>
-                </b-form-invalid-feedback>
-              </b-form-group>
-            </b-col>
-            <b-col lg="3">
-              <b-form-group
-                :label="$t('pageNetwork.form.macAddress')"
-                label-for="mac-address"
-              >
-                <b-form-input
-                  id="mac-address"
-                  v-model.trim="form.macAddress"
-                  data-test-id="network-input-macAddress"
-                  type="text"
-                  :state="getValidationState($v.form.macAddress)"
-                  @change="$v.form.macAddress.$touch()"
-                />
-                <b-form-invalid-feedback role="alert">
-                  <div v-if="!$v.form.macAddress.required">
-                    {{ $t('global.form.fieldRequired') }}
-                  </div>
-                  <div v-if="!$v.form.macAddress.macAddress">
-                    {{ $t('global.form.invalidFormat') }}
-                  </div>
-                </b-form-invalid-feedback>
-              </b-form-group>
-            </b-col>
-          </b-row>
-        </page-section>
-        <page-section :section-title="$t('pageNetwork.ipv4')">
-          <b-form-group :label="$t('pageNetwork.ipv4Configuration')">
-            <b-form-text id="enable-secure-help-block">
-              {{ $t('pageNetwork.ipv4Helper') }}
-            </b-form-text>
-            <b-form-radio
-              v-model="form.dhcpEnabled"
-              name="dhcp-radio"
-              :value="true"
-              @change="onChangeIpv4Config"
-            >
-              {{ $t('pageNetwork.dhcp') }}
-            </b-form-radio>
-            <b-form-radio
-              v-model="form.dhcpEnabled"
-              name="static-radio"
-              :value="false"
-              @change="onChangeIpv4Config"
-            >
-              {{ $t('pageNetwork.static') }}
-            </b-form-radio>
-          </b-form-group>
-          <b-row>
-            <b-col lg="9" class="mb-3">
-              <h3 class="h4">
-                {{ $t('pageNetwork.dhcp') }}
-              </h3>
-              <b-table
-                responsive="md"
-                hover
-                :fields="ipv4DhcpTableFields"
-                :items="form.ipv4DhcpTableItems"
-                :empty-text="$t('global.table.emptyMessage')"
-                class="mb-0"
-                show-empty
-              >
-                <template #cell(Address)="{ item, index }">
-                  <b-form-input
-                    v-model.trim="item.Address"
-                    :data-test-id="`network-input-dhcpIpv4-${index}`"
-                    :aria-label="
-                      $t('pageNetwork.table.dhcpIpv4AddressRow') +
-                      ' ' +
-                      (index + 1)
-                    "
-                    readonly
-                  />
-                </template>
-                <template #cell(SubnetMask)="{ item, index }">
-                  <b-form-input
-                    v-model.trim="item.SubnetMask"
-                    :data-test-id="`network-input-subnetMask-${index}`"
-                    :aria-label="
-                      $t('pageNetwork.table.dhcpIpv4SubnetRow') +
-                      ' ' +
-                      (index + 1)
-                    "
-                    readonly
-                  />
-                </template>
-                <template #cell(actions)="{ item, index }">
-                  <table-row-action
-                    v-for="(action, actionIndex) in item.actions"
-                    :key="actionIndex"
-                    :value="action.value"
-                    :title="action.title"
-                    :enabled="false"
-                    @click-table-action="
-                      onDeleteIpv4StaticTableRow($event, index)
-                    "
-                  >
-                    <template #icon>
-                      <icon-trashcan v-if="action.value === 'delete'" />
-                    </template>
-                  </table-row-action>
-                </template>
-              </b-table>
-            </b-col>
-            <b-col lg="9" class="mb-3">
-              <h3 class="h4">
-                {{ $t('pageNetwork.static') }}
-              </h3>
-              <b-table
-                responsive="md"
-                hover
-                :fields="ipv4StaticTableFields"
-                :items="form.ipv4StaticTableItems"
-                :empty-text="$t('global.table.emptyMessage')"
-                class="mb-0"
-                show-empty
-              >
-                <template #cell(Address)="{ item, index }">
-                  <b-form-input
-                    v-model.trim="item.Address"
-                    :data-test-id="`network-input-staticIpv4-${index}`"
-                    :aria-label="
-                      $t('pageNetwork.table.staticIpv4AddressRow') +
-                      ' ' +
-                      (index + 1)
-                    "
-                    :state="
-                      getValidationState(
-                        $v.form.ipv4StaticTableItems.$each.$iter[index].Address
-                      )
-                    "
-                    @change="
-                      $v.form.ipv4StaticTableItems.$each.$iter[
-                        index
-                      ].Address.$touch()
-                    "
-                  />
-                  <b-form-invalid-feedback role="alert">
-                    <div
-                      v-if="
-                        !$v.form.ipv4StaticTableItems.$each.$iter[index].Address
-                          .required
-                      "
-                    >
-                      {{ $t('global.form.fieldRequired') }}
-                    </div>
-                    <div
-                      v-if="
-                        !$v.form.ipv4StaticTableItems.$each.$iter[index].Address
-                          .ipAddress
-                      "
-                    >
-                      {{ $t('global.form.invalidFormat') }}
-                    </div>
-                  </b-form-invalid-feedback>
-                </template>
-                <template #cell(SubnetMask)="{ item, index }">
-                  <b-form-input
-                    v-model.trim="item.SubnetMask"
-                    :data-test-id="`network-input-subnetMask-${index}`"
-                    :aria-label="
-                      $t('pageNetwork.table.staticIpv4SubnetRow') +
-                      ' ' +
-                      (index + 1)
-                    "
-                    :state="
-                      getValidationState(
-                        $v.form.ipv4StaticTableItems.$each.$iter[index]
-                          .SubnetMask
-                      )
-                    "
-                    @change="
-                      $v.form.ipv4StaticTableItems.$each.$iter[
-                        index
-                      ].SubnetMask.$touch()
-                    "
-                  />
-                  <b-form-invalid-feedback role="alert">
-                    <div
-                      v-if="
-                        !$v.form.ipv4StaticTableItems.$each.$iter[index]
-                          .SubnetMask.required
-                      "
-                    >
-                      {{ $t('global.form.fieldRequired') }}
-                    </div>
-                    <div
-                      v-if="
-                        !$v.form.ipv4StaticTableItems.$each.$iter[index]
-                          .SubnetMask.ipAddress
-                      "
-                    >
-                      {{ $t('global.form.invalidFormat') }}
-                    </div>
-                  </b-form-invalid-feedback>
-                </template>
-                <template #cell(actions)="{ item, index }">
-                  <table-row-action
-                    v-for="(action, actionIndex) in item.actions"
-                    :key="actionIndex"
-                    :value="action.value"
-                    :title="action.title"
-                    @click-table-action="
-                      onDeleteIpv4StaticTableRow($event, index)
-                    "
-                  >
-                    <template #icon>
-                      <icon-trashcan v-if="action.value === 'delete'" />
-                    </template>
-                  </table-row-action>
-                </template>
-              </b-table>
-              <b-button variant="link" @click="addIpv4StaticTableRow">
-                <icon-add />
-                {{ $t('pageNetwork.table.addStaticIpv4Address') }}
-              </b-button>
-            </b-col>
-          </b-row>
-        </page-section>
-        <page-section :section-title="$t('pageNetwork.staticDns')">
-          <b-row>
-            <b-col lg="4" class="mb-3">
-              <b-table
-                responsive
-                hover
-                :fields="dnsTableFields"
-                :items="form.dnsStaticTableItems"
-                :empty-text="$t('global.table.emptyMessage')"
-                class="mb-0"
-                show-empty
-              >
-                <template #cell(address)="{ item, index }">
-                  <b-form-input
-                    v-model.trim="item.address"
-                    :data-test-id="`network-input-dnsAddress-${index}`"
-                    :aria-label="
-                      $t('pageNetwork.table.staticDnsRow') + ' ' + (index + 1)
-                    "
-                    :state="
-                      getValidationState(
-                        $v.form.dnsStaticTableItems.$each.$iter[index].address
-                      )
-                    "
-                    @change="
-                      $v.form.dnsStaticTableItems.$each.$iter[
-                        index
-                      ].address.$touch()
-                    "
-                  />
-                  <b-form-invalid-feedback role="alert">
-                    <div
-                      v-if="
-                        !$v.form.dnsStaticTableItems.$each.$iter[index].address
-                          .required
-                      "
-                    >
-                      {{ $t('global.form.fieldRequired') }}
-                    </div>
-                    <div
-                      v-if="
-                        !$v.form.dnsStaticTableItems.$each.$iter[index].address
-                          .ipAddress
-                      "
-                    >
-                      {{ $t('global.form.invalidFormat') }}
-                    </div>
-                  </b-form-invalid-feedback>
-                </template>
-                <template #cell(actions)="{ item, index }">
-                  <table-row-action
-                    v-for="(action, actionIndex) in item.actions"
-                    :key="actionIndex"
-                    :value="action.value"
-                    :title="action.title"
-                    @click-table-action="onDeleteDnsTableRow($event, index)"
-                  >
-                    <template #icon>
-                      <icon-trashcan v-if="action.value === 'delete'" />
-                    </template>
-                  </table-row-action>
-                </template>
-              </b-table>
-              <b-button variant="link" @click="addDnsTableRow">
-                <icon-add /> {{ $t('pageNetwork.table.addDns') }}
-              </b-button>
-            </b-col>
-          </b-row>
-        </page-section>
-        <b-button
-          variant="primary"
-          type="submit"
-          data-test-id="network-button-saveNetworkSettings"
-        >
-          {{ $t('global.action.saveSettings') }}
-        </b-button>
-      </b-form-group>
-    </b-form>
   </b-container>
 </template>
 
 <script>
-import IconTrashcan from '@carbon/icons-vue/es/trash-can/20';
-import IconAdd from '@carbon/icons-vue/es/add--alt/20';
 import BVToastMixin from '@/components/Mixins/BVToastMixin';
+import DataFormatterMixin from '@/components/Mixins/DataFormatterMixin';
 import LoadingBarMixin, { loading } from '@/components/Mixins/LoadingBarMixin';
+import NetworkGlobalSettings from './NetworkGlobalSettings.vue';
+import NetworkInterfaceSettings from './NetworkInterfaceSettings.vue';
 import PageSection from '@/components/Global/PageSection';
 import PageTitle from '@/components/Global/PageTitle';
-import TableRowAction from '@/components/Global/TableRowAction';
-import VuelidateMixin from '@/components/Mixins/VuelidateMixin';
+import TableIpv4 from './TableIpv4.vue';
+import TableDns from './TableDns.vue';
 import { mapState } from 'vuex';
-import {
-  required,
-  helpers,
-  ipAddress,
-  macAddress,
-} from 'vuelidate/lib/validators';
-
-// Hostname pattern
-const validateHostname = helpers.regex('validateHostname', /^\S{0,64}$/);
 
 export default {
   name: 'Network',
   components: {
-    PageTitle,
+    NetworkGlobalSettings,
+    NetworkInterfaceSettings,
     PageSection,
-    TableRowAction,
-    IconTrashcan,
-    IconAdd,
+    PageTitle,
+    TableDns,
+    TableIpv4,
   },
-  mixins: [BVToastMixin, VuelidateMixin, LoadingBarMixin],
+  mixins: [BVToastMixin, DataFormatterMixin, LoadingBarMixin],
   beforeRouteLeave(to, from, next) {
     this.hideLoader();
     next();
   },
   data() {
     return {
-      ipv4DhcpTableFields: [
-        {
-          key: 'Address',
-          label: this.$t('pageNetwork.table.ipAddress'),
-        },
-        {
-          key: 'SubnetMask',
-          label: this.$t('pageNetwork.table.subnet'),
-        },
-        { key: 'actions', label: '', tdClass: 'text-right' },
-      ],
-      ipv4StaticTableFields: [
-        {
-          key: 'Address',
-          label: this.$t('pageNetwork.table.ipAddress'),
-        },
-        {
-          key: 'SubnetMask',
-          label: this.$t('pageNetwork.table.subnet'),
-        },
-        { key: 'actions', label: '', tdClass: 'text-right' },
-      ],
-      dnsTableFields: [
-        {
-          key: 'address',
-          label: this.$t('pageNetwork.table.ipAddress'),
-        },
-        { key: 'actions', label: '', tdClass: 'text-right' },
-      ],
-      selectedInterfaceIndex: 0,
-      selectedInterface: {},
-      form: {
-        dhcpEnabled: null,
-        gateway: '',
-        hostname: '',
-        macAddress: '',
-        ipv4StaticTableItems: [],
-        ipv4DhcpTableItems: [],
-        dnsStaticTableItems: [],
-      },
       loading,
-    };
-  },
-  validations() {
-    return {
-      form: {
-        gateway: { required, ipAddress },
-        hostname: { required, validateHostname },
-        ipv4StaticTableItems: {
-          $each: {
-            Address: {
-              required,
-              ipAddress,
-            },
-            SubnetMask: {
-              required,
-              ipAddress,
-            },
-          },
-        },
-        macAddress: { required, macAddress: macAddress() },
-        dnsStaticTableItems: {
-          $each: {
-            address: {
-              required,
-              ipAddress,
-            },
-          },
-        },
-      },
+      tabIndex: 0,
     };
   },
   computed: {
-    ...mapState('network', [
-      'ethernetData',
-      'interfaceOptions',
-      'defaultGateway',
-    ]),
-    interfaceSelectOptions() {
-      return this.interfaceOptions.map((option, index) => {
-        return {
-          text: option,
-          value: index,
-        };
-      });
-    },
-  },
-  watch: {
-    ethernetData: function () {
-      this.selectInterface();
-    },
+    ...mapState('network', ['ethernetData']),
   },
   created() {
     this.startLoader();
-    this.$store
-      .dispatch('network/getEthernetData')
-      .finally(() => this.endLoader());
+    const globalSettings = new Promise((resolve) => {
+      this.$root.$on('network-global-settings-complete', () => resolve());
+    });
+    const interfaceSettings = new Promise((resolve) => {
+      this.$root.$on('network-interface-settings-complete', () => resolve());
+    });
+    const networkTableDns = new Promise((resolve) => {
+      this.$root.$on('network-table-dns-complete', () => resolve());
+    });
+    const networkTableIpv4 = new Promise((resolve) => {
+      this.$root.$on('network-table-ipv4-complete', () => resolve());
+    });
+    // Combine all child component Promises to indicate
+    // when page data load complete
+    Promise.all([
+      this.$store.dispatch('network/getEthernetData'),
+      globalSettings,
+      interfaceSettings,
+      networkTableDns,
+      networkTableIpv4,
+    ]).finally(() => this.endLoader());
   },
   methods: {
-    selectInterface() {
-      this.selectedInterface = this.ethernetData[this.selectedInterfaceIndex];
-      this.getIpv4DhcpTableItems();
-      this.getIpv4StaticTableItems();
-      this.getDnsStaticTableItems();
-      this.getInterfaceSettings();
-    },
-    getInterfaceSettings() {
-      this.form.gateway = this.defaultGateway;
-      this.form.hostname = this.selectedInterface.HostName;
-      this.form.macAddress = this.selectedInterface.MACAddress;
-      this.form.dhcpEnabled = this.selectedInterface.DHCPv4.DHCPEnabled;
-    },
-    onChangeIpv4Config(value) {
-      this.form.dhcpEnabled = value;
-    },
-    getDnsStaticTableItems() {
-      const dns = this.selectedInterface.StaticNameServers || [];
-      this.form.dnsStaticTableItems = dns.map((server) => {
-        return {
-          address: server,
-          actions: [
-            {
-              value: 'delete',
-              enabled: this.form.dhcpEnabled,
-              title: this.$t('pageNetwork.table.deleteDns'),
-            },
-          ],
-        };
-      });
-    },
-    addDnsTableRow() {
-      this.$v.form.dnsStaticTableItems.$touch();
-      this.form.dnsStaticTableItems.push({
-        address: '',
-        actions: [
-          {
-            value: 'delete',
-            enabled: this.form.dhcpEnabled,
-            title: this.$t('pageNetwork.table.deleteDns'),
-          },
-        ],
-      });
-    },
-    deleteDnsTableRow(index) {
-      this.$v.form.dnsStaticTableItems.$touch();
-      this.form.dnsStaticTableItems.splice(index, 1);
-    },
-    onDeleteDnsTableRow(action, row) {
-      this.deleteDnsTableRow(row);
-    },
-    getIpv4DhcpTableItems() {
-      const addresses = this.selectedInterface.IPv4Addresses || [];
-      this.form.ipv4DhcpTableItems = addresses
-        .filter((ipv4) => ipv4.AddressOrigin === 'DHCP')
-        .map((ipv4) => {
-          return {
-            Address: ipv4.Address,
-            SubnetMask: ipv4.SubnetMask,
-            actions: [
-              {
-                value: 'delete',
-                enabled: false,
-                title: this.$t('pageNetwork.table.deleteDhcpIpv4'),
-              },
-            ],
-          };
-        });
-    },
-    getIpv4StaticTableItems() {
-      const addresses = this.selectedInterface.IPv4StaticAddresses || [];
-      this.form.ipv4StaticTableItems = addresses.map((ipv4) => {
-        return {
-          Address: ipv4.Address,
-          SubnetMask: ipv4.SubnetMask,
-          actions: [
-            {
-              value: 'delete',
-              enabled: this.form.dhcpEnabled,
-              title: this.$t('pageNetwork.table.deleteStaticIpv4'),
-            },
-          ],
-        };
-      });
-    },
-    addIpv4StaticTableRow() {
-      this.$v.form.ipv4StaticTableItems.$touch();
-      this.form.ipv4StaticTableItems.push({
-        Address: '',
-        SubnetMask: '',
-        actions: [
-          {
-            value: 'delete',
-            enabled: this.form.dhcpEnabled,
-            title: this.$t('pageNetwork.table.deleteStaticIpv4'),
-          },
-        ],
-      });
-    },
-    deleteIpv4StaticTableRow(index) {
-      this.$v.form.ipv4StaticTableItems.$touch();
-      this.form.ipv4StaticTableItems.splice(index, 1);
-    },
-    onDeleteIpv4StaticTableRow(action, row) {
-      this.deleteIpv4StaticTableRow(row);
-    },
-    submitForm() {
-      this.$v.$touch();
-      if (this.$v.$invalid) return;
-      this.startLoader();
-      let networkInterfaceSelected = this.selectedInterface;
-      let selectedInterfaceIndex = this.selectedInterfaceIndex;
-      let interfaceId = networkInterfaceSelected.Id;
-      let isDhcpEnabled = this.form.dhcpEnabled;
-      let macAddress = this.form.macAddress;
-      let hostname = this.form.hostname;
-      let networkSettingsForm = {
-        interfaceId,
-        hostname,
-        macAddress,
-        selectedInterfaceIndex,
-      };
-      // Enabling DHCP without any available IP addresses will bring network down
-      if (this.form.ipv4DhcpTableItems.length) {
-        networkSettingsForm.isDhcpEnabled = isDhcpEnabled;
-      } else {
-        networkSettingsForm.isDhcpEnabled = false;
-        this.errorToast(this.$t('pageNetwork.toast.errorSaveDhcpSettings'));
-      }
-      networkSettingsForm.staticIpv4 = this.form.ipv4StaticTableItems.map(
-        (updateIpv4) => {
-          delete updateIpv4.actions;
-          updateIpv4.Gateway = this.form.gateway;
-          return updateIpv4;
-        }
-      );
-      networkSettingsForm.staticNameServers = this.form.dnsStaticTableItems.map(
-        (updateDns) => {
-          return updateDns.address;
-        }
-      );
-      this.$store
-        .dispatch('network/updateInterfaceSettings', networkSettingsForm)
-        .then((success) => {
-          this.successToast(success);
-        })
-        .catch(({ message }) => this.errorToast(message))
-        .finally(() => {
-          this.$v.form.$reset();
-          this.endLoader();
-        });
+    getTabIndex(selectedIndex) {
+      this.tabIndex = selectedIndex;
     },
   },
 };
diff --git a/src/views/Settings/Network/NetworkGlobalSettings.vue b/src/views/Settings/Network/NetworkGlobalSettings.vue
new file mode 100644
index 0000000..fc82c86
--- /dev/null
+++ b/src/views/Settings/Network/NetworkGlobalSettings.vue
@@ -0,0 +1,152 @@
+<template>
+  <page-section
+    v-if="firstInterface"
+    :section-title="$t('pageNetwork.networkSettings')"
+  >
+    <b-row>
+      <b-col md="3">
+        <dl>
+          <dt>{{ $t('pageNetwork.hostname') }}</dt>
+          <dd>{{ dataFormatter(firstInterface.hostname) }}</dd>
+        </dl>
+      </b-col>
+      <b-col md="3">
+        <dl>
+          <dt>{{ $t('pageNetwork.useDomainName') }}</dt>
+          <dd>
+            <b-form-checkbox
+              id="useDomainNameSwitch"
+              v-model="useDomainNameState"
+              data-test-id="networkSettings-switch-useDomainName"
+              switch
+              @change="changeDomainNameState"
+            >
+              <span v-if="useDomainNameState">
+                {{ $t('global.status.enabled') }}
+              </span>
+              <span v-else>{{ $t('global.status.disabled') }}</span>
+            </b-form-checkbox>
+          </dd>
+        </dl>
+      </b-col>
+      <b-col md="3">
+        <dl>
+          <dt>{{ $t('pageNetwork.useDns') }}</dt>
+          <dd>
+            <b-form-checkbox
+              id="useDnsSwitch"
+              v-model="useDnsState"
+              data-test-id="networkSettings-switch-useDns"
+              switch
+              @change="changeDnsState"
+            >
+              <span v-if="useDnsState">
+                {{ $t('global.status.enabled') }}
+              </span>
+              <span v-else>{{ $t('global.status.disabled') }}</span>
+            </b-form-checkbox>
+          </dd>
+        </dl>
+      </b-col>
+      <b-col md="3">
+        <dl>
+          <dt>{{ $t('pageNetwork.useNtp') }}</dt>
+          <dd>
+            <b-form-checkbox
+              id="useNtpSwitch"
+              v-model="useNtpState"
+              data-test-id="networkSettings-switch-useNtp"
+              switch
+              @change="changeNtpState"
+            >
+              <span v-if="useNtpState">
+                {{ $t('global.status.enabled') }}
+              </span>
+              <span v-else>{{ $t('global.status.disabled') }}</span>
+            </b-form-checkbox>
+          </dd>
+        </dl>
+      </b-col>
+    </b-row>
+  </page-section>
+</template>
+
+<script>
+import BVToastMixin from '@/components/Mixins/BVToastMixin';
+import DataFormatterMixin from '@/components/Mixins/DataFormatterMixin';
+import PageSection from '@/components/Global/PageSection';
+import { mapState } from 'vuex';
+
+export default {
+  name: 'GlobalNetworkSettings',
+  components: { PageSection },
+  mixins: [BVToastMixin, DataFormatterMixin],
+
+  data() {
+    return {
+      hostname: '',
+    };
+  },
+  computed: {
+    ...mapState('network', ['ethernetData']),
+    firstInterface() {
+      return this.$store.getters['network/globalNetworkSettings'][0];
+    },
+    useDomainNameState: {
+      get() {
+        return this.$store.getters['network/globalNetworkSettings'][0]
+          .useDomainNameEnabled;
+      },
+      set(newValue) {
+        return newValue;
+      },
+    },
+    useDnsState: {
+      get() {
+        return this.$store.getters['network/globalNetworkSettings'][0]
+          .useDnsEnabled;
+      },
+      set(newValue) {
+        return newValue;
+      },
+    },
+    useNtpState: {
+      get() {
+        return this.$store.getters['network/globalNetworkSettings'][0]
+          .useNtpEnabled;
+      },
+      set(newValue) {
+        return newValue;
+      },
+    },
+  },
+  created() {
+    this.$store.dispatch('network/getEthernetData').finally(() => {
+      // Emit initial data fetch complete to parent component
+      this.$root.$emit('network-global-settings-complete');
+    });
+  },
+  methods: {
+    changeDomainNameState(state) {
+      this.$store
+        .dispatch('network/saveDomainNameState', state)
+        .then((success) => {
+          this.successToast(success);
+        })
+        .catch(({ message }) => this.errorToast(message));
+    },
+    changeDnsState(state) {
+      this.$store
+        .dispatch('network/saveDnsState', state)
+        .then((message) => this.successToast(message))
+        .catch(({ message }) => this.errorToast(message));
+    },
+    changeNtpState(state) {
+      this.$store
+        .dispatch('network/saveNtpState', state)
+        .then((message) => this.successToast(message))
+        .catch(({ message }) => this.errorToast(message));
+    },
+  },
+};
+</script>
diff --git a/src/views/Settings/Network/NetworkInterfaceSettings.vue b/src/views/Settings/Network/NetworkInterfaceSettings.vue
new file mode 100644
index 0000000..bdcba4d
--- /dev/null
+++ b/src/views/Settings/Network/NetworkInterfaceSettings.vue
@@ -0,0 +1,99 @@
+<template>
+  <div>
+    <page-section>
+      <b-row>
+        <b-col md="3">
+          <dl>
+            <dt>{{ $t('pageNetwork.linkStatus') }}</dt>
+            <dd>
+              {{ dataFormatter(linkStatus) }}
+            </dd>
+          </dl>
+        </b-col>
+        <b-col md="3">
+          <dl>
+            <dt>{{ $t('pageNetwork.speed') }}</dt>
+            <dd>
+              {{ dataFormatter(linkSpeed) }}
+            </dd>
+          </dl>
+        </b-col>
+      </b-row>
+    </page-section>
+    <page-section :section-title="$t('pageNetwork.interfaceSection')">
+      <b-row>
+        <b-col md="3">
+          <dl>
+            <dt>{{ $t('pageNetwork.fqdn') }}</dt>
+            <dd>
+              {{ dataFormatter(fqdn) }}
+            </dd>
+          </dl>
+        </b-col>
+        <b-col md="3">
+          <dl class="text-nowrap">
+            <dt>{{ $t('pageNetwork.macAddress') }}</dt>
+            <dd>
+              {{ dataFormatter(macAddress) }}
+            </dd>
+          </dl>
+        </b-col>
+      </b-row>
+    </page-section>
+  </div>
+</template>
+
+<script>
+import BVToastMixin from '@/components/Mixins/BVToastMixin';
+import PageSection from '@/components/Global/PageSection';
+import DataFormatterMixin from '@/components/Mixins/DataFormatterMixin';
+import { mapState } from 'vuex';
+
+export default {
+  name: 'Ipv4Table',
+  components: {
+    PageSection,
+  },
+  mixins: [BVToastMixin, DataFormatterMixin],
+  props: {
+    tabIndex: {
+      type: Number,
+      default: 0,
+    },
+  },
+  data() {
+    return {
+      selectedInterface: '',
+      linkStatus: '',
+      linkSpeed: '',
+      fqdn: '',
+      macAddress: '',
+    };
+  },
+  computed: {
+    ...mapState('network', ['ethernetData']),
+  },
+  watch: {
+    // Watch for change in tab index
+    tabIndex() {
+      this.getSettings();
+    },
+  },
+  created() {
+    this.getSettings();
+    this.$store.dispatch('network/getEthernetData').finally(() => {
+      // Emit initial data fetch complete to parent component
+      this.$root.$emit('network-interface-settings-complete');
+    });
+  },
+  methods: {
+    getSettings() {
+      this.selectedInterface = this.tabIndex;
+      this.linkStatus = this.ethernetData[this.selectedInterface].LinkStatus;
+      this.linkSpeed = this.ethernetData[this.selectedInterface].SpeedMbps;
+      this.fqdn = this.ethernetData[this.selectedInterface].FQDN;
+      this.macAddress = this.ethernetData[this.selectedInterface].MACAddress;
+    },
+  },
+};
+</script>
diff --git a/src/views/Settings/Network/TableDns.vue b/src/views/Settings/Network/TableDns.vue
new file mode 100644
index 0000000..2578ba3
--- /dev/null
+++ b/src/views/Settings/Network/TableDns.vue
@@ -0,0 +1,126 @@
+<template>
+  <page-section :section-title="$t('pageNetwork.staticDns')">
+    <b-row>
+      <b-col lg="6">
+        <b-table
+          responsive="md"
+          hover
+          :fields="dnsTableFields"
+          :items="form.dnsStaticTableItems"
+          :empty-text="$t('global.table.emptyMessage')"
+          class="mb-0"
+          show-empty
+        >
+          <template #cell(actions)="{ item }">
+            <table-row-action
+              v-for="(action, actionIndex) in item.actions"
+              :key="actionIndex"
+              :value="action.value"
+              :title="action.title"
+              :enabled="action.enabled"
+              @click-table-action="onDnsTableAction(action, $event, index)"
+            >
+              <template #icon>
+                <icon-edit v-if="action.value === 'edit'" />
+                <icon-trashcan v-if="action.value === 'delete'" />
+              </template>
+            </table-row-action>
+          </template>
+        </b-table>
+      </b-col>
+    </b-row>
+  </page-section>
+</template>
+
+<script>
+import BVToastMixin from '@/components/Mixins/BVToastMixin';
+import IconEdit from '@carbon/icons-vue/es/edit/20';
+import IconTrashcan from '@carbon/icons-vue/es/trash-can/20';
+import PageSection from '@/components/Global/PageSection';
+import TableRowAction from '@/components/Global/TableRowAction';
+import { mapState } from 'vuex';
+
+export default {
+  name: 'DNSTable',
+  components: {
+    IconEdit,
+    IconTrashcan,
+    PageSection,
+    TableRowAction,
+  },
+  mixins: [BVToastMixin],
+  props: {
+    tabIndex: {
+      type: Number,
+      default: 0,
+    },
+  },
+  data() {
+    return {
+      form: {
+        dnsStaticTableItems: [],
+      },
+      actions: [
+        {
+          value: 'edit',
+          title: this.$t('global.action.edit'),
+        },
+        {
+          value: 'delete',
+          title: this.$t('global.action.delete'),
+        },
+      ],
+      dnsTableFields: [
+        {
+          key: 'address',
+          label: this.$t('pageNetwork.table.ipAddress'),
+        },
+        { key: 'actions', label: '', tdClass: 'text-right' },
+      ],
+    };
+  },
+  computed: {
+    ...mapState('network', ['ethernetData']),
+  },
+  watch: {
+    // Watch for change in tab index
+    tabIndex() {
+      this.getStaticDnsItems();
+    },
+  },
+  created() {
+    this.getStaticDnsItems();
+    this.$store.dispatch('network/getEthernetData').finally(() => {
+      // Emit initial data fetch complete to parent component
+      this.$root.$emit('network-table-dns-complete');
+    });
+  },
+  methods: {
+    getStaticDnsItems() {
+      const index = this.tabIndex;
+      const dns = this.ethernetData[index].StaticNameServers || [];
+      this.form.dnsStaticTableItems = dns.map((server) => {
+        return {
+          address: server,
+          actions: [
+            {
+              value: 'edit',
+              title: this.$t('pageNetwork.table.editDns'),
+            },
+            {
+              value: 'delete',
+              title: this.$t('pageNetwork.table.deleteDns'),
+            },
+          ],
+        };
+      });
+    },
+    onDnsTableAction(action, row) {
+      if (action === 'delete') {
+        this.form.dnsStaticTableItems.splice(row, 1);
+        // TODO: delete row in store
+      }
+    },
+  },
+};
+</script>
diff --git a/src/views/Settings/Network/TableIpv4.vue b/src/views/Settings/Network/TableIpv4.vue
new file mode 100644
index 0000000..5e4bb7b
--- /dev/null
+++ b/src/views/Settings/Network/TableIpv4.vue
@@ -0,0 +1,146 @@
+<template>
+  <page-section :section-title="$t('pageNetwork.ipv4')">
+    <b-row>
+      <b-col>
+        <h3 class="h5">
+          {{ $t('pageNetwork.ipv4Addresses') }}
+        </h3>
+      </b-col>
+    </b-row>
+    <b-table
+      responsive="md"
+      hover
+      :fields="ipv4TableFields"
+      :items="form.ipv4TableItems"
+      :empty-text="$t('global.table.emptyMessage')"
+      class="mb-0"
+      show-empty
+    >
+      <template #cell(actions)="{ item }">
+        <table-row-action
+          v-for="(action, actionIndex) in item.actions"
+          :key="actionIndex"
+          :value="action.value"
+          :title="action.title"
+          :enabled="action.enabled"
+          @click-table-action="onIpv4TableAction(action, $event, index)"
+        >
+          <template #icon>
+            <icon-edit v-if="action.value === 'edit'" />
+            <icon-trashcan v-if="action.value === 'delete'" />
+          </template>
+        </table-row-action>
+      </template>
+    </b-table>
+  </page-section>
+</template>
+
+<script>
+import BVToastMixin from '@/components/Mixins/BVToastMixin';
+import IconEdit from '@carbon/icons-vue/es/edit/20';
+import IconTrashcan from '@carbon/icons-vue/es/trash-can/20';
+import PageSection from '@/components/Global/PageSection';
+import TableRowAction from '@/components/Global/TableRowAction';
+import { mapState } from 'vuex';
+
+export default {
+  name: 'Ipv4Table',
+  components: {
+    IconEdit,
+    IconTrashcan,
+    PageSection,
+    TableRowAction,
+  },
+  mixins: [BVToastMixin],
+  props: {
+    tabIndex: {
+      type: Number,
+      default: 0,
+    },
+  },
+  data() {
+    return {
+      form: {
+        ipv4TableItems: [],
+      },
+      actions: [
+        {
+          value: 'edit',
+          title: this.$t('global.action.edit'),
+        },
+        {
+          value: 'delete',
+          title: this.$t('global.action.delete'),
+        },
+      ],
+      ipv4TableFields: [
+        {
+          key: 'Address',
+          label: this.$t('pageNetwork.table.ipAddress'),
+        },
+        {
+          key: 'Gateway',
+          label: this.$t('pageNetwork.table.gateway'),
+        },
+        {
+          key: 'SubnetMask',
+          label: this.$t('pageNetwork.table.subnet'),
+        },
+        {
+          key: 'AddressOrigin',
+          label: this.$t('pageNetwork.table.addressOrigin'),
+        },
+        { key: 'actions', label: '', tdClass: 'text-right' },
+      ],
+    };
+  },
+  computed: {
+    ...mapState('network', ['ethernetData']),
+  },
+  watch: {
+    // Watch for change in tab index
+    tabIndex() {
+      this.getIpv4TableItems();
+    },
+  },
+  created() {
+    this.getIpv4TableItems();
+    this.$store.dispatch('network/getEthernetData').finally(() => {
+      // Emit initial data fetch complete to parent component
+      this.$root.$emit('network-table-ipv4-complete');
+    });
+  },
+  methods: {
+    getIpv4TableItems() {
+      const index = this.tabIndex;
+      const addresses = this.ethernetData[index].IPv4Addresses || [];
+      this.form.ipv4TableItems = addresses.map((ipv4) => {
+        return {
+          Address: ipv4.Address,
+          SubnetMask: ipv4.SubnetMask,
+          Gateway: ipv4.Gateway,
+          AddressOrigin: ipv4.AddressOrigin,
+          actions: [
+            {
+              value: 'edit',
+              title: this.$t('pageNetwork.table.editIpv4'),
+              enabled: false,
+            },
+            {
+              value: 'delete',
+              title: this.$t('pageNetwork.table.deleteIpv4'),
+              enabled: false,
+            },
+          ],
+        };
+      });
+    },
+    onIpv4TableAction(action, row) {
+      if (action === 'delete') {
+        this.form.ipv4TableItems.splice(row, 1);
+        // TODO: delete row in store
+      }
+    },
+  },
+};
+</script>