Migrate to Bootstrap 5 and remove Vue compat plugin

Complete migration from Bootstrap 4 (bootstrap-vue) to Bootstrap 5
(bootstrap-vue-next) and remove the @vue/compat plugin to finalize
the Vue 3 migration.

Bundle size impact:
- Before (Bootstrap 4 + bootstrap-vue): 535 KiB gzipped
- After (Bootstrap 5 + bootstrap-vue-next): 511 KiB gzipped
- Reduction: 24 KiB (4.5% smaller)

Package updates:
- Update bootstrap 4.6.2 -> 5.3.8
- Update bootstrap-vue 2.23.1 -> bootstrap-vue-next 0.40.8
- Remove @vue/compat plugin
- Update vue 3.4.29 -> 3.5.24 and related packages
- Add mitt 3.0.1 for global event bus
- Add vue-demi 0.14.10 for library compatibility

Bootstrap 5 CSS updates:
- Replace directional classes: ml/mr/pl/pr -> ms/me/ps/pe
- Replace text-left/right -> text-start/end
- Replace sr-only -> visually-hidden / visually-hidden-focusable
- Update media breakpoint xs -> sm (Bootstrap 5 removed xs)
- Update color functions: gray("700") -> $gray-700
- Add form-switch border-radius for curved toggles
- Update alert, table, toast, form, and button styles

Bootstrap-Vue-Next API changes:
- Use createBootstrap() for plugin registration
- Update modal footer slots: #modal-footer -> #footer
- Fix form select events: @change -> @update:model-value
- Add v-model bindings to modals instead of manual show()/hide()
- Update toast system with custom plugin wrapping useToast()
- Register components and directives explicitly

Vue 3 specific updates:
- Replace $root.$emit with mitt event bus (eventBus.js)
- Update render function from h(App) to createApp(App)
- Add emits option to components
- Use h() instead of $createElement in mixins
- Add Vue 3 compile-time feature flags with documentation
- Update event listeners: $on/$off to eventBus methods
- Add beforeUnmount cleanup for event listeners

New components and significant additions:
- src/plugins/toast.js - Custom toast plugin wrapping useToast() for
  Options API compatibility
- src/components/Global/ConfirmModal.vue - Global confirmation dialog
  shim to replace Bootstrap 4's removed bvModal.msgBoxConfirm
- src/eventBus.js - mitt-based event bus with Vue 2-compatible API
- Navigation state preservation on page refresh implemented

Critical fixes:
- Add global API interceptor to strip Vue reactivity from payloads
- Preserve binary data (File, Blob, FormData) in API requests
- Fix Generate CSR modal v-model binding for proper open/close
- Remove debug logging and fix jest configuration
- Fix responsive text visibility in AppHeader
- Update BVTableSelectableMixin for proper row selection
- Fix BVToastMixin VNode rendering for Vue 3

Vue 3 modal fixes (lazy-loaded components):
- Add v-model support to network modals (ModalIpv4, ModalIpv6, ModalDns,
  ModalHostname, ModalMacAddress, ModalDefaultGateway) by adding
  modelValue prop, watcher on modelValue that triggers show(), and
  update:modelValue emit in resetForm
- Remove lazy loading from TableIpv4, TableIpv6, TableDns to ensure
  modal component refs are available when v-model triggers
- Fix modal title accessibility by adding title prop to modals
  (ModalAddDestination, ModalUser, ModalAddRoleGroup, etc.)

i18n fixes (computed properties):
- Fix computed properties using i18n translations in ModalAddRoleGroup,
  ModalUser, and ModalUploadCertificate
- Move useI18n() call from data() to setup() and return i18n object
- Use i18n.t() instead of $t in computed properties and templates
- Prevents "this.$t is not a function" and "_ctx.$t is not a function"
  errors in Vue 3

Toast notification fixes:
- Fix toast progress bar visibility by setting progressProps to
  undefined (documented way to opt-out) instead of false
- Change modelValue prop to interval for auto-dismiss timing
- Remove temporary CSS display:none hack from _toasts.scss

Network settings fixes:
- Fix checkbox @change event sending Vue reactive proxy object instead
  of boolean by casting with !! operator in changeDomainNameState and
  related methods in NetworkGlobalSettings.vue
- Ensures API receives plain boolean values in PATCH requests

Navigation fixes:
- Fix nav-link styling for navigation items without children by
  replacing b-nav-item with router-link in AppNavigation.vue
- Prevents blue font color from .nav-link CSS class

Configuration updates:
- Remove vue-compat webpack configuration
- Add Vue 3 feature flags (__VUE_OPTIONS_API__, etc.)
- Add .cursor to .gitignore

Accessibility improvements:
- Add autocomplete attributes to password and credential inputs
- Add modal title props for screen reader support

Build completes successfully and UI behavior matches pre-migration.

Extracted features (to be submitted in follow-up PRs):
The following features were removed from this migration PR to keep it
focused on the Bootstrap 5 upgrade. They will be submitted separately:
1. UnresponsiveModal - Server connectivity watchdog with auto-retry
2. Auth token persistence - sessionStorage support for X-Auth-Token
3. Hardware store error handling - try/catch, dynamic discovery
4. Login page connecting indicator - Backend polling with spinner
5. Test updates - Jest setup and snapshot updates for
   Bootstrap-Vue-Next
6. Documentation updates - Vue 3 and Vue I18n v9+ API documentation
7. Enhanced ConfirmModal - Feature-rich confirmation dialog with
   custom actions

Change-Id: Ib76a58f324b3c926cf536e6e4626e4271639de38
Signed-off-by: Jason Westover <jwestover@nvidia.com>
diff --git a/src/views/HardwareStatus/Inventory/Inventory.vue b/src/views/HardwareStatus/Inventory/Inventory.vue
index a3f4d23..d99a792 100644
--- a/src/views/HardwareStatus/Inventory/Inventory.vue
+++ b/src/views/HardwareStatus/Inventory/Inventory.vue
@@ -153,33 +153,56 @@
   created() {
     this.startLoader();
     const bmcManagerTablePromise = new Promise((resolve) => {
-      this.$root.$on('hardware-status-bmc-manager-complete', () => resolve());
+      require('@/eventBus').default.$on(
+        'hardware-status-bmc-manager-complete',
+        () => resolve(),
+      );
     });
     const chassisTablePromise = new Promise((resolve) => {
-      this.$root.$on('hardware-status-chassis-complete', () => resolve());
+      require('@/eventBus').default.$on(
+        'hardware-status-chassis-complete',
+        () => resolve(),
+      );
     });
     const dimmSlotTablePromise = new Promise((resolve) => {
-      this.$root.$on('hardware-status-dimm-slot-complete', () => resolve());
+      require('@/eventBus').default.$on(
+        'hardware-status-dimm-slot-complete',
+        () => resolve(),
+      );
     });
     const fansTablePromise = new Promise((resolve) => {
-      this.$root.$on('hardware-status-fans-complete', () => resolve());
-    });
-    const powerSuppliesTablePromise = new Promise((resolve) => {
-      this.$root.$on('hardware-status-power-supplies-complete', () =>
+      require('@/eventBus').default.$on('hardware-status-fans-complete', () =>
         resolve(),
       );
     });
+    const powerSuppliesTablePromise = new Promise((resolve) => {
+      require('@/eventBus').default.$on(
+        'hardware-status-power-supplies-complete',
+        () => resolve(),
+      );
+    });
     const processorsTablePromise = new Promise((resolve) => {
-      this.$root.$on('hardware-status-processors-complete', () => resolve());
+      require('@/eventBus').default.$on(
+        'hardware-status-processors-complete',
+        () => resolve(),
+      );
     });
     const serviceIndicatorPromise = new Promise((resolve) => {
-      this.$root.$on('hardware-status-service-complete', () => resolve());
+      require('@/eventBus').default.$on(
+        'hardware-status-service-complete',
+        () => resolve(),
+      );
     });
     const systemTablePromise = new Promise((resolve) => {
-      this.$root.$on('hardware-status-system-complete', () => resolve());
+      require('@/eventBus').default.$on('hardware-status-system-complete', () =>
+        resolve(),
+      );
     });
     const assemblyTablePromise = new Promise((resolve) => {
-      this.$root.$on('hardware-status-assembly-complete', () => resolve());
+      require('@/eventBus').default.$on(
+        'hardware-status-assembly-complete',
+        () => resolve(),
+      );
     });
     // Combine all child component Promises to indicate
     // when page data load complete
diff --git a/src/views/HardwareStatus/Inventory/InventoryServiceIndicator.vue b/src/views/HardwareStatus/Inventory/InventoryServiceIndicator.vue
index 5b19b42..0a1a84b 100644
--- a/src/views/HardwareStatus/Inventory/InventoryServiceIndicator.vue
+++ b/src/views/HardwareStatus/Inventory/InventoryServiceIndicator.vue
@@ -2,11 +2,13 @@
   <page-section
     :section-title="$t('pageInventory.systemIndicator.sectionTitle')"
   >
-    <div class="form-background pl-4 pt-4 pb-1">
+    <div class="form-background ps-4 pt-4 pb-1">
       <b-row>
         <b-col sm="6" md="3">
           <dl>
-            <dt>{{ $t('pageInventory.systemIndicator.powerStatus') }}</dt>
+            <dt>
+              {{ $t('pageInventory.systemIndicator.powerStatus') }}
+            </dt>
             <dd>
               {{ $t(powerStatus) }}
             </dd>
@@ -68,7 +70,7 @@
   created() {
     this.$store.dispatch('system/getSystem').finally(() => {
       // Emit initial data fetch complete to parent component
-      this.$root.$emit('hardware-status-service-complete');
+      require('@/eventBus').default.$emit('hardware-status-service-complete');
     });
   },
   methods: {
diff --git a/src/views/HardwareStatus/Inventory/InventoryTableAssembly.vue b/src/views/HardwareStatus/Inventory/InventoryTableAssembly.vue
index 68bee05..e267f7f 100644
--- a/src/views/HardwareStatus/Inventory/InventoryTableAssembly.vue
+++ b/src/views/HardwareStatus/Inventory/InventoryTableAssembly.vue
@@ -2,9 +2,10 @@
   <page-section :section-title="$t('pageInventory.assemblies')">
     <b-table
       sort-icon-left
-      no-sort-reset
+      must-sort
       hover
       responsive="md"
+      thead-class="table-light"
       :items="items"
       :fields="fields"
       show-empty
@@ -18,10 +19,11 @@
           data-test-id="hardwareStatus-button-expandAssembly"
           :title="expandRowLabel"
           class="btn-icon-only"
+          :class="{ collapsed: !row.detailsShowing }"
           @click="toggleRowDetails(row)"
         >
           <icon-chevron />
-          <span class="sr-only">{{ expandRowLabel }}</span>
+          <span class="visually-hidden">{{ expandRowLabel }}</span>
         </b-button>
       </template>
 
@@ -134,7 +136,7 @@
   created() {
     this.$store.dispatch('assemblies/getAssemblyInfo').finally(() => {
       // Emit initial data fetch complete to parent component
-      this.$root.$emit('hardware-status-assembly-complete');
+      require('@/eventBus').default.$emit('hardware-status-assembly-complete');
       this.isBusy = false;
     });
   },
diff --git a/src/views/HardwareStatus/Inventory/InventoryTableBmcManager.vue b/src/views/HardwareStatus/Inventory/InventoryTableBmcManager.vue
index 4ee4eba..8a0671e 100644
--- a/src/views/HardwareStatus/Inventory/InventoryTableBmcManager.vue
+++ b/src/views/HardwareStatus/Inventory/InventoryTableBmcManager.vue
@@ -3,6 +3,7 @@
     <b-table
       responsive="md"
       hover
+      thead-class="table-light"
       :items="items"
       :fields="fields"
       show-empty
@@ -16,10 +17,11 @@
           data-test-id="hardwareStatus-button-expandBmc"
           :title="expandRowLabel"
           class="btn-icon-only"
+          :class="{ collapsed: !row.detailsShowing }"
           @click="toggleRowDetails(row)"
         >
           <icon-chevron />
-          <span class="sr-only">{{ expandRowLabel }}</span>
+          <span class="visually-hidden">{{ expandRowLabel }}</span>
         </b-button>
       </template>
 
@@ -62,7 +64,9 @@
                 <dd>{{ dataFormatter(item.serialNumber) }}</dd>
                 <!-- Spare part number -->
                 <dt>{{ $t('pageInventory.table.sparePartNumber') }}:</dt>
-                <dd>{{ dataFormatter(item.sparePartNumber) }}</dd>
+                <dd>
+                  {{ dataFormatter(item.sparePartNumber) }}
+                </dd>
                 <!-- Model -->
                 <dt>{{ $t('pageInventory.table.model') }}:</dt>
                 <dd>{{ dataFormatter(item.model) }}</dd>
@@ -71,7 +75,9 @@
                 <dd>{{ dataFormatter(item.uuid) }}</dd>
                 <!-- Service entry point UUID -->
                 <dt>{{ $t('pageInventory.table.serviceEntryPointUuid') }}:</dt>
-                <dd>{{ dataFormatter(item.serviceEntryPointUuid) }}</dd>
+                <dd>
+                  {{ dataFormatter(item.serviceEntryPointUuid) }}
+                </dd>
               </dl>
             </b-col>
             <b-col class="mt-2" sm="6" xl="6">
@@ -125,7 +131,7 @@
               <p class="mt-1 mb-2 h6 float-none m-0">
                 {{ $t('pageInventory.table.graphicalConsole') }}
               </p>
-              <dl class="ml-4">
+              <dl class="ms-4">
                 <dt>{{ $t('pageInventory.table.connectTypesSupported') }}:</dt>
                 <dd>
                   {{ dataFormatterArray(item.graphicalConsoleConnectTypes) }}
@@ -211,7 +217,9 @@
   created() {
     this.$store.dispatch('bmc/getBmcInfo').finally(() => {
       // Emit initial data fetch complete to parent component
-      this.$root.$emit('hardware-status-bmc-manager-complete');
+      require('@/eventBus').default.$emit(
+        'hardware-status-bmc-manager-complete',
+      );
       this.isBusy = false;
     });
   },
diff --git a/src/views/HardwareStatus/Inventory/InventoryTableChassis.vue b/src/views/HardwareStatus/Inventory/InventoryTableChassis.vue
index 4458e33..8278e52 100644
--- a/src/views/HardwareStatus/Inventory/InventoryTableChassis.vue
+++ b/src/views/HardwareStatus/Inventory/InventoryTableChassis.vue
@@ -3,6 +3,7 @@
     <b-table
       responsive="md"
       hover
+      thead-class="table-light"
       :items="chassis"
       :fields="fields"
       show-empty
@@ -16,10 +17,11 @@
           data-test-id="hardwareStatus-button-expandChassis"
           :title="expandRowLabel"
           class="btn-icon-only"
+          :class="{ collapsed: !row.detailsShowing }"
           @click="toggleRowDetails(row)"
         >
           <icon-chevron />
-          <span class="sr-only">{{ expandRowLabel }}</span>
+          <span class="visually-hidden">{{ expandRowLabel }}</span>
         </b-button>
       </template>
 
@@ -178,7 +180,7 @@
   created() {
     this.$store.dispatch('chassis/getChassisInfo').finally(() => {
       // Emit initial data fetch complete to parent component
-      this.$root.$emit('hardware-status-chassis-complete');
+      require('@/eventBus').default.$emit('hardware-status-chassis-complete');
       this.isBusy = false;
     });
   },
diff --git a/src/views/HardwareStatus/Inventory/InventoryTableDimmSlot.vue b/src/views/HardwareStatus/Inventory/InventoryTableDimmSlot.vue
index f4a850b..c767726 100644
--- a/src/views/HardwareStatus/Inventory/InventoryTableDimmSlot.vue
+++ b/src/views/HardwareStatus/Inventory/InventoryTableDimmSlot.vue
@@ -16,15 +16,15 @@
     </b-row>
     <b-table
       sort-icon-left
-      no-sort-reset
+      must-sort
       hover
-      sort-by="health"
+      thead-class="table-light"
+      :sort-by="['health']"
       responsive="md"
       show-empty
       :items="dimms"
       :fields="fields"
-      :sort-desc="true"
-      :sort-compare="sortCompare"
+      :sort-desc="[true]"
       :filter="searchFilter"
       :empty-text="$t('global.table.emptyMessage')"
       :empty-filtered-text="$t('global.table.emptySearchMessage')"
@@ -35,13 +35,14 @@
       <template #cell(expandRow)="row">
         <b-button
           variant="link"
-          data-test-id="hardwareStatus-button-expandDimms"
+          data-test-id="hardwareStatus-button-expandDimmSlot"
           :title="expandRowLabel"
           class="btn-icon-only"
+          :class="{ collapsed: !row.detailsShowing }"
           @click="toggleRowDetails(row)"
         >
           <icon-chevron />
-          <span class="sr-only">{{ expandRowLabel }}</span>
+          <span class="visually-hidden">{{ expandRowLabel }}</span>
         </b-button>
       </template>
 
@@ -95,7 +96,9 @@
               <dl>
                 <!-- Spare Part Number -->
                 <dt>{{ $t('pageInventory.table.sparePartNumber') }}:</dt>
-                <dd>{{ dataFormatter(item.sparePartNumber) }}</dd>
+                <dd>
+                  {{ dataFormatter(item.sparePartNumber) }}
+                </dd>
               </dl>
               <dl>
                 <!-- Model -->
@@ -145,7 +148,9 @@
               <dl>
                 <!-- Base Module Type -->
                 <dt>{{ $t('pageInventory.table.baseModuleType') }}:</dt>
-                <dd>{{ dataFormatter(item.baseModuleType) }}</dd>
+                <dd>
+                  {{ dataFormatter(item.baseModuleType) }}
+                </dd>
               </dl>
             </b-col>
             <b-col sm="6" xl="6">
@@ -176,7 +181,9 @@
               <dl>
                 <!-- Error Correction -->
                 <dt>{{ $t('pageInventory.table.errorCorrection') }}:</dt>
-                <dd>{{ dataFormatter(item.errorCorrection) }}</dd>
+                <dd>
+                  {{ dataFormatter(item.errorCorrection) }}
+                </dd>
               </dl>
             </b-col>
           </b-row>
@@ -207,7 +214,13 @@
 import i18n from '@/i18n';
 
 export default {
-  components: { IconChevron, PageSection, StatusIcon, Search, TableCellCount },
+  components: {
+    IconChevron,
+    PageSection,
+    StatusIcon,
+    Search,
+    TableCellCount,
+  },
   mixins: [
     BVToastMixin,
     TableRowExpandMixin,
@@ -271,7 +284,7 @@
   created() {
     this.$store.dispatch('memory/getDimms').finally(() => {
       // Emit initial data fetch complete to parent component
-      this.$root.$emit('hardware-status-dimm-slot-complete');
+      require('@/eventBus').default.$emit('hardware-status-dimm-slot-complete');
       this.isBusy = false;
     });
   },
diff --git a/src/views/HardwareStatus/Inventory/InventoryTableFans.vue b/src/views/HardwareStatus/Inventory/InventoryTableFans.vue
index 4a540e0..9f4dffa 100644
--- a/src/views/HardwareStatus/Inventory/InventoryTableFans.vue
+++ b/src/views/HardwareStatus/Inventory/InventoryTableFans.vue
@@ -16,15 +16,15 @@
     </b-row>
     <b-table
       sort-icon-left
-      no-sort-reset
+      must-sort
       hover
       responsive="md"
-      sort-by="health"
+      thead-class="table-light"
+      :sort-by="['health']"
       show-empty
       :items="fans"
       :fields="fields"
-      :sort-desc="true"
-      :sort-compare="sortCompare"
+      :sort-desc="[true]"
       :filter="searchFilter"
       :empty-text="$t('global.table.emptyMessage')"
       :empty-filtered-text="$t('global.table.emptySearchMessage')"
@@ -38,10 +38,11 @@
           data-test-id="hardwareStatus-button-expandFans"
           :title="expandRowLabel"
           class="btn-icon-only"
+          :class="{ collapsed: !row.detailsShowing }"
           @click="toggleRowDetails(row)"
         >
           <icon-chevron />
-          <span class="sr-only">{{ expandRowLabel }}</span>
+          <span class="visually-hidden">{{ expandRowLabel }}</span>
         </b-button>
       </template>
 
@@ -123,7 +124,13 @@
 import i18n from '@/i18n';
 
 export default {
-  components: { IconChevron, PageSection, StatusIcon, Search, TableCellCount },
+  components: {
+    IconChevron,
+    PageSection,
+    StatusIcon,
+    Search,
+    TableCellCount,
+  },
   mixins: [
     TableRowExpandMixin,
     DataFormatterMixin,
@@ -190,7 +197,7 @@
   created() {
     this.$store.dispatch('fan/getFanInfo').finally(() => {
       // Emit initial data fetch complete to parent component
-      this.$root.$emit('hardware-status-fans-complete');
+      require('@/eventBus').default.$emit('hardware-status-fans-complete');
       this.isBusy = false;
     });
   },
diff --git a/src/views/HardwareStatus/Inventory/InventoryTablePowerSupplies.vue b/src/views/HardwareStatus/Inventory/InventoryTablePowerSupplies.vue
index ddc3333..f494514 100644
--- a/src/views/HardwareStatus/Inventory/InventoryTablePowerSupplies.vue
+++ b/src/views/HardwareStatus/Inventory/InventoryTablePowerSupplies.vue
@@ -16,15 +16,15 @@
     </b-row>
     <b-table
       sort-icon-left
-      no-sort-reset
+      must-sort
       hover
       responsive="md"
-      sort-by="health"
+      thead-class="table-light"
+      :sort-by="['health']"
       show-empty
       :items="powerSupplies"
       :fields="fields"
-      :sort-desc="true"
-      :sort-compare="sortCompare"
+      :sort-desc="[true]"
       :filter="searchFilter"
       :empty-text="$t('global.table.emptyMessage')"
       :empty-filtered-text="$t('global.table.emptySearchMessage')"
@@ -38,10 +38,11 @@
           data-test-id="hardwareStatus-button-expandPowerSupplies"
           :title="expandRowLabel"
           class="btn-icon-only"
+          :class="{ collapsed: !row.detailsShowing }"
           @click="toggleRowDetails(row)"
         >
           <icon-chevron />
-          <span class="sr-only">{{ expandRowLabel }}</span>
+          <span class="visually-hidden">{{ expandRowLabel }}</span>
         </b-button>
       </template>
 
@@ -73,7 +74,9 @@
                 <dd>{{ dataFormatter(item.serialNumber) }}</dd>
                 <!-- Spare part number -->
                 <dt>{{ $t('pageInventory.table.sparePartNumber') }}:</dt>
-                <dd>{{ dataFormatter(item.sparePartNumber) }}</dd>
+                <dd>
+                  {{ dataFormatter(item.sparePartNumber) }}
+                </dd>
                 <!-- Model -->
                 <dt>{{ $t('pageInventory.table.model') }}:</dt>
                 <dd>{{ dataFormatter(item.model) }}</dd>
@@ -115,7 +118,9 @@
               <dl>
                 <!-- Firmware version -->
                 <dt>{{ $t('pageInventory.table.firmwareVersion') }}:</dt>
-                <dd>{{ dataFormatter(item.firmwareVersion) }}</dd>
+                <dd>
+                  {{ dataFormatter(item.firmwareVersion) }}
+                </dd>
               </dl>
             </b-col>
           </b-row>
@@ -144,7 +149,13 @@
 import i18n from '@/i18n';
 
 export default {
-  components: { IconChevron, PageSection, StatusIcon, Search, TableCellCount },
+  components: {
+    IconChevron,
+    PageSection,
+    StatusIcon,
+    Search,
+    TableCellCount,
+  },
   mixins: [
     TableRowExpandMixin,
     DataFormatterMixin,
@@ -211,7 +222,9 @@
   created() {
     this.$store.dispatch('powerSupply/getAllPowerSupplies').finally(() => {
       // Emit initial data fetch complete to parent component
-      this.$root.$emit('hardware-status-power-supplies-complete');
+      require('@/eventBus').default.$emit(
+        'hardware-status-power-supplies-complete',
+      );
       this.isBusy = false;
     });
   },
diff --git a/src/views/HardwareStatus/Inventory/InventoryTableProcessors.vue b/src/views/HardwareStatus/Inventory/InventoryTableProcessors.vue
index 4bdff54..5290133 100644
--- a/src/views/HardwareStatus/Inventory/InventoryTableProcessors.vue
+++ b/src/views/HardwareStatus/Inventory/InventoryTableProcessors.vue
@@ -17,13 +17,15 @@
     </b-row>
     <b-table
       sort-icon-left
-      no-sort-reset
+      must-sort
       hover
       responsive="md"
+      thead-class="table-light"
+      :sort-by="['health']"
       show-empty
       :items="processors"
       :fields="fields"
-      :sort-desc="true"
+      :sort-desc="[true]"
       :filter="searchFilter"
       :empty-text="$t('global.table.emptyMessage')"
       :empty-filtered-text="$t('global.table.emptySearchMessage')"
@@ -37,10 +39,11 @@
           data-test-id="hardwareStatus-button-expandProcessors"
           :title="expandRowLabel"
           class="btn-icon-only"
+          :class="{ collapsed: !row.detailsShowing }"
           @click="toggleRowDetails(row)"
         >
           <icon-chevron />
-          <span class="sr-only">{{ expandRowLabel }}</span>
+          <span class="visually-hidden">{{ expandRowLabel }}</span>
         </b-button>
       </template>
       <!-- Health -->
@@ -87,7 +90,9 @@
                 <dd>{{ dataFormatter(item.serialNumber) }}</dd>
                 <!-- Spare Part Number -->
                 <dt>{{ $t('pageInventory.table.sparePartNumber') }}:</dt>
-                <dd>{{ dataFormatter(item.sparePartNumber) }}</dd>
+                <dd>
+                  {{ dataFormatter(item.sparePartNumber) }}
+                </dd>
                 <!-- Model -->
                 <dt>{{ $t('pageInventory.table.model') }}:</dt>
                 <dd>{{ dataFormatter(item.model) }}</dd>
@@ -119,10 +124,14 @@
                 <dd>{{ dataFormatter(item.processorType) }}</dd>
                 <!-- Processor Architecture -->
                 <dt>{{ $t('pageInventory.table.processorArchitecture') }}:</dt>
-                <dd>{{ dataFormatter(item.processorArchitecture) }}</dd>
+                <dd>
+                  {{ dataFormatter(item.processorArchitecture) }}
+                </dd>
                 <!-- Instruction Set -->
                 <dt>{{ $t('pageInventory.table.instructionSet') }}:</dt>
-                <dd>{{ dataFormatter(item.instructionSet) }}</dd>
+                <dd>
+                  {{ dataFormatter(item.instructionSet) }}
+                </dd>
                 <!-- Version -->
                 <dt>{{ $t('pageInventory.table.version') }}:</dt>
                 <dd>{{ dataFormatter(item.version) }}</dd>
@@ -176,7 +185,13 @@
 import i18n from '@/i18n';
 
 export default {
-  components: { IconChevron, PageSection, StatusIcon, Search, TableCellCount },
+  components: {
+    IconChevron,
+    PageSection,
+    StatusIcon,
+    Search,
+    TableCellCount,
+  },
   mixins: [
     BVToastMixin,
     TableRowExpandMixin,
@@ -246,7 +261,9 @@
   created() {
     this.$store.dispatch('processors/getProcessorsInfo').finally(() => {
       // Emit initial data fetch complete to parent component
-      this.$root.$emit('hardware-status-processors-complete');
+      require('@/eventBus').default.$emit(
+        'hardware-status-processors-complete',
+      );
       this.isBusy = false;
     });
   },
diff --git a/src/views/HardwareStatus/Inventory/InventoryTableSystem.vue b/src/views/HardwareStatus/Inventory/InventoryTableSystem.vue
index 23beb47..a3cb0cf 100644
--- a/src/views/HardwareStatus/Inventory/InventoryTableSystem.vue
+++ b/src/views/HardwareStatus/Inventory/InventoryTableSystem.vue
@@ -4,6 +4,7 @@
       responsive="md"
       hover
       show-empty
+      thead-class="table-light"
       :items="systems"
       :fields="fields"
       :empty-text="$t('global.table.emptyMessage')"
@@ -16,10 +17,11 @@
           data-test-id="hardwareStatus-button-expandSystem"
           :title="expandRowLabel"
           class="btn-icon-only"
+          :class="{ collapsed: !row.detailsShowing }"
           @click="toggleRowDetails(row)"
         >
           <icon-chevron />
-          <span class="sr-only">{{ expandRowLabel }}</span>
+          <span class="visually-hidden">{{ expandRowLabel }}</span>
         </b-button>
       </template>
 
@@ -103,7 +105,7 @@
               <p class="mt-1 mb-2 h6 float-none m-0">
                 {{ $t('pageInventory.table.memorySummary') }}
               </p>
-              <dl class="ml-4">
+              <dl class="ms-4">
                 <!-- Total system memory -->
                 <dt>{{ $t('pageInventory.table.totalSystemMemoryGiB') }}:</dt>
                 <dd>
@@ -115,23 +117,31 @@
               <p class="mt-1 mb-2 h6 float-none m-0">
                 {{ $t('pageInventory.table.processorSummary') }}
               </p>
-              <dl class="ml-4">
+              <dl class="ms-4">
                 <!-- Count -->
                 <dt>{{ $t('pageInventory.table.count') }}:</dt>
-                <dd>{{ dataFormatter(item.processorSummaryCount) }}</dd>
+                <dd>
+                  {{ dataFormatter(item.processorSummaryCount) }}
+                </dd>
                 <!-- Core Count -->
                 <dt>{{ $t('pageInventory.table.coreCount') }}:</dt>
-                <dd>{{ dataFormatter(item.processorSummaryCoreCount) }}</dd>
+                <dd>
+                  {{ dataFormatter(item.processorSummaryCoreCount) }}
+                </dd>
               </dl>
               <!-- Serial console -->
               <p class="mt-1 mb-2 h6 float-none m-0">
                 {{ $t('pageInventory.table.serialConsole') }}
               </p>
-              <dl class="ml-4">
+              <dl class="ms-4">
                 <dt>{{ $t('pageInventory.table.maxConcurrentSessions') }}:</dt>
-                <dd>{{ dataFormatter(item.serialConsoleMaxSessions) }}</dd>
+                <dd>
+                  {{ dataFormatter(item.serialConsoleMaxSessions) }}
+                </dd>
                 <dt>{{ $t('pageInventory.table.serviceEnabled') }}:</dt>
-                <dd>{{ dataFormatter(item.serialConsoleEnabled) }}</dd>
+                <dd>
+                  {{ dataFormatter(item.serialConsoleEnabled) }}
+                </dd>
               </dl>
             </b-col>
           </b-row>
@@ -207,7 +217,7 @@
   created() {
     this.$store.dispatch('system/getSystem').finally(() => {
       // Emit initial data fetch complete to parent component
-      this.$root.$emit('hardware-status-system-complete');
+      require('@/eventBus').default.$emit('hardware-status-system-complete');
       this.isBusy = false;
     });
   },
diff --git a/src/views/HardwareStatus/Sensors/Sensors.vue b/src/views/HardwareStatus/Sensors/Sensors.vue
index ac70e40..120cf66 100644
--- a/src/views/HardwareStatus/Sensors/Sensors.vue
+++ b/src/views/HardwareStatus/Sensors/Sensors.vue
@@ -16,7 +16,7 @@
           :total-number-of-cells="allSensors.length"
         ></table-cell-count>
       </b-col>
-      <b-col sm="3" md="4" xl="6" class="text-right">
+      <b-col sm="3" md="4" xl="6" class="text-end">
         <table-filter :filters="tableFilters" @filter-change="onFilterChange" />
       </b-col>
     </b-row>
@@ -24,7 +24,9 @@
       <b-col xl="12">
         <table-toolbar
           ref="toolbar"
-          :selected-items-count="selectedRows.length"
+          :selected-items-count="
+            Array.isArray(selectedRows) ? selectedRows.length : 0
+          "
           @clear-selected="clearSelectedRows($refs.table)"
         >
           <template #toolbar-buttons>
@@ -41,15 +43,14 @@
           no-select-on-click
           sort-icon-left
           hover
-          no-sort-reset
+          must-sort
           sticky-header="75vh"
-          sort-by="status"
+          thead-class="table-light"
+          :sort-by="['status']"
           show-empty
-          :no-border-collapse="true"
           :items="filteredSensors"
           :fields="fields"
-          :sort-desc="true"
-          :sort-compare="sortCompare"
+          :sort-desc="[true]"
           :filter="searchFilter"
           :empty-text="$t('global.table.emptyMessage')"
           :empty-filtered-text="$t('global.table.emptySearchMessage')"
@@ -62,9 +63,11 @@
             <b-form-checkbox
               v-model="tableHeaderCheckboxModel"
               :indeterminate="tableHeaderCheckboxIndeterminate"
-              @change="onChangeHeaderCheckbox($refs.table)"
+              @change="onChangeHeaderCheckbox($refs.table, $event)"
             >
-              <span class="sr-only">{{ $t('global.table.selectAll') }}</span>
+              <span class="visually-hidden-focusable">
+                {{ $t('global.table.selectAll') }}
+              </span>
             </b-form-checkbox>
           </template>
           <template #cell(checkbox)="row">
@@ -72,7 +75,9 @@
               v-model="row.rowSelected"
               @change="toggleSelectRow($refs.table, row.index)"
             >
-              <span class="sr-only">{{ $t('global.table.selectItem') }}</span>
+              <span class="visually-hidden-focusable">
+                {{ $t('global.table.selectItem') }}
+              </span>
             </b-form-checkbox>
           </template>
 
@@ -235,11 +240,6 @@
     });
   },
   methods: {
-    sortCompare(a, b, key) {
-      if (key === 'status') {
-        return this.sortStatus(a, b, key);
-      }
-    },
     onFilterChange({ activeFilters }) {
       this.activeFilters = activeFilters;
     },