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/ChangePassword/ChangePassword.vue b/src/views/ChangePassword/ChangePassword.vue
index 18d9dcc..f829229 100644
--- a/src/views/ChangePassword/ChangePassword.vue
+++ b/src/views/ChangePassword/ChangePassword.vue
@@ -4,7 +4,9 @@
<p v-if="changePasswordError">
{{ $t('pageChangePassword.changePasswordError') }}
</p>
- <p v-else>{{ $t('pageChangePassword.changePasswordAlertMessage') }}</p>
+ <p v-else>
+ {{ $t('pageChangePassword.changePasswordAlertMessage') }}
+ </p>
</alert>
<div class="change-password__form-container">
<dl>
@@ -22,6 +24,7 @@
v-model="form.password"
autofocus="autofocus"
type="password"
+ autocomplete="new-password"
:state="getValidationState(v$.form.password)"
class="form-control-with-button"
@change="v$.form.password.$touch()"
@@ -43,6 +46,7 @@
id="password-confirm"
v-model="form.passwordConfirm"
type="password"
+ autocomplete="new-password"
:state="getValidationState(v$.form.passwordConfirm)"
class="form-control-with-button"
@change="v$.form.passwordConfirm.$touch()"
@@ -60,7 +64,7 @@
</b-form-invalid-feedback>
</input-password-toggle>
</b-form-group>
- <div class="text-right">
+ <div class="text-end">
<b-button type="button" variant="link" @click="goBack">
{{ $t('pageChangePassword.goBack') }}
</b-button>
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;
},
diff --git a/src/views/Login/Login.vue b/src/views/Login/Login.vue
index 5212f92..028d80c 100644
--- a/src/views/Login/Login.vue
+++ b/src/views/Login/Login.vue
@@ -20,6 +20,7 @@
aria-describedby="login-error-alert username-required"
:state="getValidationState(v$.userInfo.username)"
type="text"
+ autocomplete="username"
autofocus="autofocus"
data-test-id="login-input-username"
@input="v$.userInfo.username.$touch()"
@@ -32,7 +33,9 @@
</b-form-invalid-feedback>
</b-form-group>
<div class="login-form__section mb-3">
- <label for="password">{{ $t('pageLogin.password') }}</label>
+ <label for="password" class="d-block">
+ {{ $t('pageLogin.password') }}
+ </label>
<input-password-toggle>
<b-form-input
id="password"
@@ -40,6 +43,7 @@
aria-describedby="login-error-alert password-required"
:state="getValidationState(v$.userInfo.password)"
type="password"
+ autocomplete="current-password"
data-test-id="login-input-password"
class="form-control-with-button"
@input="v$.userInfo.password.$touch()"
@@ -117,15 +121,17 @@
return this.$store.getters['authentication/authError'];
},
},
- validations: {
- userInfo: {
- username: {
- required,
+ validations() {
+ return {
+ userInfo: {
+ username: {
+ required,
+ },
+ password: {
+ required,
+ },
},
- password: {
- required,
- },
- },
+ };
},
methods: {
login: function () {
diff --git a/src/views/Logs/Dumps/Dumps.vue b/src/views/Logs/Dumps/Dumps.vue
index 4783f82..d500c36 100644
--- a/src/views/Logs/Dumps/Dumps.vue
+++ b/src/views/Logs/Dumps/Dumps.vue
@@ -18,7 +18,7 @@
@change-search="onChangeSearchInput"
@clear-search="onClearSearchInput"
/>
- <div class="ml-sm-4">
+ <div class="ms-sm-4">
<table-cell-count
:filtered-items-count="filteredRows"
:total-number-of-cells="allDumps.length"
@@ -30,7 +30,7 @@
</b-col>
</b-row>
<b-row>
- <b-col class="text-right">
+ <b-col class="text-end">
<table-filter
:filters="tableFilters"
@filter-change="onFilterChange"
@@ -38,7 +38,9 @@
</b-col>
</b-row>
<table-toolbar
- :selected-items-count="selectedRows.length"
+ :selected-items-count="
+ Array.isArray(selectedRows) ? selectedRows.length : 0
+ "
:actions="batchActions"
@clear-selected="clearSelectedRows($refs.table)"
@batch-action="onTableBatchAction"
@@ -48,12 +50,13 @@
show-empty
hover
sort-icon-left
- no-sort-reset
- sort-desc
+ must-sort
+ thead-class="table-light"
+ :sort-desc="[true]"
selectable
no-select-on-click
responsive="md"
- sort-by="dateTime"
+ :sort-by="['dateTime']"
:fields="fields"
:items="filteredDumps"
:empty-text="$t('global.table.emptyMessage')"
@@ -61,16 +64,18 @@
:filter="searchFilter"
:busy="isBusy"
@filtered="onFiltered"
- @row-selected="onRowSelected($event, filteredTableItems.length)"
+ @row-selected="onRowSelected($event, filteredRows)"
>
<!-- Checkbox column -->
<template #head(checkbox)>
<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">
@@ -78,7 +83,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>
@@ -135,7 +142,7 @@
first-number
last-number
:per-page="perPage"
- :total-rows="getTotalRowCount()"
+ :total-rows="getTotalRowCount(filteredRows)"
aria-controls="table-dump-entries"
/>
</b-col>
@@ -173,6 +180,7 @@
import TableFilterMixin from '@/components/Mixins/TableFilterMixin';
import i18n from '@/i18n';
import { useI18n } from 'vue-i18n';
+import { useModal } from 'bootstrap-vue-next';
export default {
components: {
@@ -202,6 +210,10 @@
this.hideLoader();
next();
},
+ setup() {
+ const bvModal = useModal();
+ return { bvModal };
+ },
data() {
return {
$t: useI18n().t,
@@ -235,7 +247,7 @@
key: 'actions',
sortable: false,
label: '',
- tdClass: 'text-right text-nowrap',
+ tdClass: 'text-end text-nowrap',
},
],
batchActions: [
@@ -328,78 +340,60 @@
this.filterStartDate = fromDate;
this.filterEndDate = toDate;
},
- onTableRowAction(action, dump) {
+ async onTableRowAction(action, item) {
if (action === 'delete') {
- this.$bvModal
- .msgBoxConfirm(
- i18n.global.t('pageDumps.modal.deleteDumpConfirmation'),
- {
- title: i18n.global.t('pageDumps.modal.deleteDump'),
- okTitle: i18n.global.t('pageDumps.modal.deleteDump'),
- cancelTitle: i18n.global.t('global.action.cancel'),
- autoFocusButton: 'ok',
- },
- )
- .then((deleteConfrimed) => {
- if (deleteConfrimed) {
- this.$store
- .dispatch('dumps/deleteDumps', [dump])
- .then((messages) => {
- messages.forEach(({ type, message }) => {
- if (type === 'success') {
- this.successToast(message);
- } else if (type === 'error') {
- this.errorToast(message);
- }
- });
- });
- }
+ const ok = await this.confirmDialog(
+ i18n.global.t('pageDumps.modal.deleteDumpConfirmation', 1),
+ {
+ title: i18n.global.t('pageDumps.modal.deleteDump', 1),
+ okTitle: i18n.global.t('pageDumps.modal.deleteDump', 1),
+ cancelTitle: i18n.global.t('global.action.cancel'),
+ },
+ );
+ if (ok)
+ this.$store.dispatch('dumps/deleteDumps', [item]).then((messages) => {
+ messages.forEach(({ type, message }) => {
+ if (type === 'success') {
+ this.successToast(message);
+ } else if (type === 'error') {
+ this.errorToast(message);
+ }
+ });
});
}
},
- onTableBatchAction(action) {
+ async onTableBatchAction(action) {
if (action === 'delete') {
- this.$bvModal
- .msgBoxConfirm(
- i18n.global.t(
- 'pageDumps.modal.deleteDumpConfirmation',
- this.selectedRows.length,
- ),
- {
- title: i18n.global.t(
- 'pageDumps.modal.deleteDump',
- this.selectedRows.length,
- ),
- okTitle: i18n.global.t(
- 'pageDumps.modal.deleteDump',
- this.selectedRows.length,
- ),
- cancelTitle: i18n.global.t('global.action.cancel'),
- autoFocusButton: 'ok',
- },
- )
- .then((deleteConfrimed) => {
- if (deleteConfrimed) {
- if (this.selectedRows.length === this.dumps.length) {
- this.$store
- .dispatch('dumps/deleteAllDumps')
- .then((success) => this.successToast(success))
- .catch(({ message }) => this.errorToast(message));
- } else {
- this.$store
- .dispatch('dumps/deleteDumps', this.selectedRows)
- .then((messages) => {
- messages.forEach(({ type, message }) => {
- if (type === 'success') {
- this.successToast(message);
- } else if (type === 'error') {
- this.errorToast(message);
- }
- });
- });
- }
- }
- });
+ const ids = (this.selectedRows || []).map((r) => r.Id);
+ const count = ids.length;
+ const ok = await this.confirmDialog(
+ i18n.global.t('pageDumps.modal.deleteDumpConfirmation', count),
+ {
+ title: i18n.global.t('pageDumps.modal.deleteDump', count),
+ okTitle: i18n.global.t('pageDumps.modal.deleteDump', count),
+ cancelTitle: i18n.global.t('global.action.cancel'),
+ },
+ );
+ if (ok) {
+ if (this.selectedRows.length === this.dumps.length) {
+ this.$store
+ .dispatch('dumps/deleteAllDumps')
+ .then((success) => this.successToast(success))
+ .catch(({ message }) => this.errorToast(message));
+ } else {
+ this.$store
+ .dispatch('dumps/deleteDumps', this.selectedRows)
+ .then((messages) => {
+ messages.forEach(({ type, message }) => {
+ if (type === 'success') {
+ this.successToast(message);
+ } else if (type === 'error') {
+ this.errorToast(message);
+ }
+ });
+ });
+ }
+ }
}
},
exportFileName(row) {
@@ -407,6 +401,9 @@
filename = filename.replace(RegExp(' ', 'g'), '_');
return filename;
},
+ confirmDialog(message, options = {}) {
+ return this.$confirm({ message, ...options });
+ },
},
};
</script>
diff --git a/src/views/Logs/Dumps/DumpsForm.vue b/src/views/Logs/Dumps/DumpsForm.vue
index b27aeba..af9df83 100644
--- a/src/views/Logs/Dumps/DumpsForm.vue
+++ b/src/views/Logs/Dumps/DumpsForm.vue
@@ -24,14 +24,16 @@
<alert variant="info" class="mb-3" :show="selectedDumpType === 'system'">
{{ $t('pageDumps.form.systemDumpInfo') }}
</alert>
- <b-button variant="primary" type="submit" form="form-new-dump">
+ <b-button
+ variant="primary"
+ type="submit"
+ form="form-new-dump"
+ class="mt-3"
+ >
{{ $t('pageDumps.form.initiateDump') }}
</b-button>
</b-form>
- <modal-confirmation
- :require-confirmation="selectedDumpType === 'system'"
- @ok="createSystemDump"
- />
+ <modal-confirmation v-model="showConfirmation" @ok="createSystemDump" />
</div>
</template>
@@ -44,19 +46,23 @@
import VuelidateMixin from '@/components/Mixins/VuelidateMixin.js';
import i18n from '@/i18n';
import { useI18n } from 'vue-i18n';
+import { useModal } from 'bootstrap-vue-next';
export default {
components: { Alert, ModalConfirmation },
mixins: [BVToastMixin, VuelidateMixin],
setup() {
+ const bvModal = useModal();
return {
v$: useVuelidate(),
+ bvModal,
};
},
data() {
return {
$t: useI18n().t,
selectedDumpType: null,
+ showConfirmation: false,
dumpTypeOptions: [
{ value: 'bmc', text: i18n.global.t('pageDumps.dumpTypes.bmcDump') },
{
@@ -99,7 +105,7 @@
}
},
showConfirmationModal() {
- this.$bvModal.show('modal-confirmation');
+ this.showConfirmation = true;
},
createSystemDump() {
this.$store
diff --git a/src/views/Logs/Dumps/DumpsModalConfirmation.vue b/src/views/Logs/Dumps/DumpsModalConfirmation.vue
index 77f03b0..466f6ce 100644
--- a/src/views/Logs/Dumps/DumpsModalConfirmation.vue
+++ b/src/views/Logs/Dumps/DumpsModalConfirmation.vue
@@ -26,7 +26,7 @@
>
{{ $t('global.form.required') }}
</b-form-invalid-feedback>
- <template #modal-footer="{ cancel }">
+ <template #footer="{ cancel }">
<b-button variant="secondary" @click="cancel()">
{{ $t('global.action.cancel') }}
</b-button>
diff --git a/src/views/Logs/EventLogs/EventLogs.vue b/src/views/Logs/EventLogs/EventLogs.vue
index 392125c..e149739 100644
--- a/src/views/Logs/EventLogs/EventLogs.vue
+++ b/src/views/Logs/EventLogs/EventLogs.vue
@@ -9,7 +9,7 @@
@change-search="onChangeSearchInput"
@clear-search="onClearSearchInput"
/>
- <div class="ml-sm-4">
+ <div class="ms-sm-4">
<table-cell-count
:filtered-items-count="filteredRows"
:total-number-of-cells="allLogs.length"
@@ -21,7 +21,7 @@
</b-col>
</b-row>
<b-row>
- <b-col class="text-right">
+ <b-col class="text-end">
<table-filter :filters="tableFilters" @filter-change="onFilterChange" />
<b-button
variant="link"
@@ -74,13 +74,13 @@
no-select-on-click
sort-icon-left
hover
- no-sort-reset
- sort-desc
+ must-sort
+ thead-class="table-light"
+ :sort-desc="[true]"
show-empty
- sort-by="id"
+ :sort-by="['id']"
:fields="fields"
:items="filteredLogs"
- :sort-compare="onSortCompare"
:empty-text="$t('global.table.emptyMessage')"
:empty-filtered-text="$t('global.table.emptySearchMessage')"
:per-page="perPage"
@@ -96,9 +96,11 @@
v-model="tableHeaderCheckboxModel"
data-test-id="eventLogs-checkbox-selectAll"
: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">
@@ -107,7 +109,9 @@
:data-test-id="`eventLogs-checkbox-selectRow-${row.index}`"
@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>
@@ -120,7 +124,8 @@
class="btn-icon-only"
@click="toggleRowDetails(row)"
>
- <icon-chevron />
+ <icon-chevron v-if="!row.detailsShowing" />
+ <icon-chevron-up v-else />
</b-button>
</template>
@@ -181,7 +186,9 @@
<span v-if="row.item.status">
{{ $t('pageEventLogs.resolved') }}
</span>
- <span v-else> {{ $t('pageEventLogs.unresolved') }} </span>
+ <span v-else>
+ {{ $t('pageEventLogs.unresolved') }}
+ </span>
</b-form-checkbox>
</template>
<template #cell(filterByStatus)="{ value }">
@@ -244,6 +251,7 @@
import IconTrashcan from '@carbon/icons-vue/es/trash-can/20';
import IconExport from '@carbon/icons-vue/es/document--export/20';
import IconChevron from '@carbon/icons-vue/es/chevron--down/20';
+import IconChevronUp from '@carbon/icons-vue/es/chevron--up/20';
import IconDownload from '@carbon/icons-vue/es/download/20';
import { omit } from 'lodash';
@@ -280,6 +288,7 @@
} from '@/components/Mixins/SearchFilterMixin';
import { useI18n } from 'vue-i18n';
import i18n from '@/i18n';
+import { useModal } from 'bootstrap-vue-next';
export default {
components: {
@@ -287,6 +296,7 @@
IconExport,
IconTrashcan,
IconChevron,
+ IconChevronUp,
IconDownload,
PageTitle,
Search,
@@ -315,6 +325,10 @@
this.hideLoader();
next();
},
+ setup() {
+ const bvModal = useModal();
+ return { bvModal };
+ },
data() {
return {
$t: useI18n().t,
@@ -361,7 +375,7 @@
key: 'actions',
sortable: false,
label: '',
- tdClass: 'text-right text-nowrap',
+ tdClass: 'text-end text-nowrap',
},
],
tableFilters:
@@ -495,23 +509,23 @@
})
.catch(({ message }) => this.errorToast(message));
},
- deleteAllLogs() {
- this.$bvModal
- .msgBoxConfirm(i18n.global.t('pageEventLogs.modal.deleteAllMessage'), {
+ async deleteAllLogs() {
+ const ok = await this.confirmDialog(
+ i18n.global.t('pageEventLogs.modal.deleteAllMessage'),
+ {
title: i18n.global.t('pageEventLogs.modal.deleteAllTitle'),
okTitle: i18n.global.t('global.action.delete'),
okVariant: 'danger',
cancelTitle: i18n.global.t('global.action.cancel'),
autoFocusButton: 'cancel',
- })
- .then((deleteConfirmed) => {
- if (deleteConfirmed) {
- this.$store
- .dispatch('eventLog/deleteAllEventLogs', this.allLogs)
- .then((message) => this.successToast(message))
- .catch(({ message }) => this.errorToast(message));
- }
- });
+ },
+ );
+ if (ok) {
+ this.$store
+ .dispatch('eventLog/deleteAllEventLogs', this.allLogs)
+ .then((message) => this.successToast(message))
+ .catch(({ message }) => this.errorToast(message));
+ }
},
deleteLogs(uris) {
this.$store
@@ -537,66 +551,48 @@
onFilterChange({ activeFilters }) {
this.activeFilters = activeFilters;
},
- onSortCompare(a, b, key) {
- if (key === 'severity') {
- return this.sortStatus(a, b, key);
- }
- },
onTableRowAction(action, { uri }) {
if (action === 'delete') {
- this.$bvModal
- .msgBoxConfirm(i18n.global.t('pageEventLogs.modal.deleteMessage'), {
- title: i18n.global.t('pageEventLogs.modal.deleteTitle'),
+ this.confirmDialog(i18n.global.t('pageEventLogs.modal.deleteMessage'), {
+ title: i18n.global.t('pageEventLogs.modal.deleteTitle'),
+ okTitle: i18n.global.t('global.action.delete'),
+ cancelTitle: i18n.global.t('global.action.cancel'),
+ autoFocusButton: 'ok',
+ }).then((deleteConfirmed) => {
+ if (deleteConfirmed) this.deleteLogs([uri]);
+ });
+ }
+ },
+ async onBatchAction(action) {
+ if (action === 'delete') {
+ const uris = this.selectedRows.map((row) => row.uri);
+ const count = this.selectedRows.length;
+ const ok = await this.confirmDialog(
+ i18n.global.t('pageEventLogs.modal.deleteMessage', count),
+ {
+ title: i18n.global.t('pageEventLogs.modal.deleteTitle', count),
okTitle: i18n.global.t('global.action.delete'),
cancelTitle: i18n.global.t('global.action.cancel'),
autoFocusButton: 'ok',
- })
- .then((deleteConfirmed) => {
- if (deleteConfirmed) this.deleteLogs([uri]);
- });
- }
- },
- onBatchAction(action) {
- if (action === 'delete') {
- const uris = this.selectedRows.map((row) => row.uri);
- this.$bvModal
- .msgBoxConfirm(
- i18n.global.t(
- 'pageEventLogs.modal.deleteMessage',
- this.selectedRows.length,
- ),
- {
- title: i18n.global.t(
- 'pageEventLogs.modal.deleteTitle',
- this.selectedRows.length,
- ),
- okTitle: i18n.global.t('global.action.delete'),
- cancelTitle: i18n.global.t('global.action.cancel'),
- autoFocusButton: 'ok',
- },
- )
- .then((deleteConfirmed) => {
- if (deleteConfirmed) {
- if (this.selectedRows.length === this.allLogs.length) {
- this.$store
- .dispatch(
- 'eventLog/deleteAllEventLogs',
- this.selectedRows.length,
- )
- .then(() => {
- this.successToast(
- i18n.global.t(
- 'pageEventLogs.toast.successDelete',
- uris.length,
- ),
- );
- })
- .catch(({ message }) => this.errorToast(message));
- } else {
- this.deleteLogs(uris);
- }
- }
- });
+ },
+ );
+ if (ok) {
+ if (this.selectedRows.length === this.allLogs.length) {
+ this.$store
+ .dispatch('eventLog/deleteAllEventLogs', this.selectedRows.length)
+ .then(() => {
+ this.successToast(
+ i18n.global.t(
+ 'pageEventLogs.toast.successDelete',
+ uris.length,
+ ),
+ );
+ })
+ .catch(({ message }) => this.errorToast(message));
+ } else {
+ this.deleteLogs(uris);
+ }
+ }
}
},
onChangeDateTimeFilter({ fromDate, toDate }) {
@@ -647,6 +643,9 @@
});
});
},
+ confirmDialog(message, options = {}) {
+ return this.$confirm({ message, ...options });
+ },
},
};
</script>
diff --git a/src/views/Logs/PostCodeLogs/PostCodeLogs.vue b/src/views/Logs/PostCodeLogs/PostCodeLogs.vue
index 6d8ff90..62caccf 100644
--- a/src/views/Logs/PostCodeLogs/PostCodeLogs.vue
+++ b/src/views/Logs/PostCodeLogs/PostCodeLogs.vue
@@ -8,7 +8,7 @@
@change-search="onChangeSearchInput"
@clear-search="onClearSearchInput"
/>
- <div class="ml-sm-4">
+ <div class="ms-sm-4">
<table-cell-count
:filtered-items-count="filteredRows"
:total-number-of-cells="allLogs.length"
@@ -20,7 +20,7 @@
</b-col>
</b-row>
<b-row>
- <b-col xl="12" class="text-right">
+ <b-col xl="12" class="text-end">
<b-button
variant="link"
:disabled="allLogs.length === 0"
@@ -34,7 +34,8 @@
:download="exportFileNameByDate()"
:href="href"
>
- <icon-export /> {{ $t('pagePostCodeLogs.button.exportAll') }}
+ <icon-export />
+ {{ $t('pagePostCodeLogs.button.exportAll') }}
</b-button>
</b-col>
</b-row>
@@ -42,7 +43,9 @@
<b-col>
<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>
@@ -60,10 +63,11 @@
no-select-on-click
sort-icon-left
hover
- no-sort-reset
- sort-desc
+ must-sort
+ thead-class="table-light"
+ :sort-desc="[true]"
show-empty
- sort-by="id"
+ :sort-by="['id']"
:fields="fields"
:items="filteredLogs"
:empty-text="$t('global.table.emptyMessage')"
@@ -81,9 +85,11 @@
v-model="tableHeaderCheckboxModel"
data-test-id="postCode-checkbox-selectAll"
: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">
@@ -92,7 +98,9 @@
:data-test-id="`postCode-checkbox-selectRow-${row.index}`"
@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>
<!-- Date column -->
@@ -111,9 +119,12 @@
:row-data="row.item"
:btn-icon-only="true"
:export-name="exportFileNameByDate(action.value)"
- :download-location="row.item.uri"
- :download-in-new-tab="true"
- :show-button="false"
+ :download-location="
+ action.value === 'download' ? '' : row.item.uri
+ "
+ :download-in-new-tab="false"
+ :show-button="action.value === 'download' ? true : false"
+ @click-table-action="onRowAction(action.value, row.item)"
>
<template #icon>
<icon-export v-if="action.value === 'export'" />
@@ -188,6 +199,7 @@
} from '@/components/Mixins/SearchFilterMixin';
import { useI18n } from 'vue-i18n';
import i18n from '@/i18n';
+import { useModal } from 'bootstrap-vue-next';
export default {
components: {
@@ -218,6 +230,10 @@
this.hideLoader();
next();
},
+ setup() {
+ const bvModal = useModal();
+ return { bvModal };
+ },
data() {
return {
$t: useI18n().t,
@@ -247,7 +263,7 @@
{
key: 'actions',
label: '',
- tdClass: 'text-right text-nowrap',
+ tdClass: 'text-end text-nowrap',
},
],
expandRowLabel,
@@ -317,23 +333,37 @@
});
},
methods: {
- deleteAllLogs() {
- this.$bvModal
- .msgBoxConfirm(i18n.global.t('pageEventLogs.modal.deleteAllMessage'), {
+ onRowAction(action, item) {
+ if (action === 'download') {
+ this.$store
+ .dispatch('postCodeLogs/downloadEntry', item.uri)
+ .then((blob) => {
+ const link = document.createElement('a');
+ link.href = URL.createObjectURL(blob);
+ link.download = this.exportFileNameByDate('download');
+ link.click();
+ URL.revokeObjectURL(link.href);
+ })
+ .catch(({ message }) => this.errorToast(message));
+ }
+ },
+ async deleteAllLogs() {
+ const deleteConfirmed = await this.confirmDialog(
+ i18n.global.t('pageEventLogs.modal.deleteAllMessage'),
+ {
title: i18n.global.t('pageEventLogs.modal.deleteAllTitle'),
okTitle: i18n.global.t('global.action.delete'),
okVariant: 'danger',
cancelTitle: i18n.global.t('global.action.cancel'),
autoFocusButton: 'cancel',
- })
- .then((deleteConfirmed) => {
- if (deleteConfirmed) {
- this.$store
- .dispatch('postCodeLogs/deleteAllPostCodeLogs', this.allLogs)
- .then((message) => this.successToast(message))
- .catch(({ message }) => this.errorToast(message));
- }
- });
+ },
+ );
+ if (deleteConfirmed) {
+ this.$store
+ .dispatch('postCodeLogs/deleteAllPostCodeLogs', this.allLogs)
+ .then((message) => this.successToast(message))
+ .catch(({ message }) => this.errorToast(message));
+ }
},
exportAllLogsString() {
{
@@ -372,6 +402,9 @@
}
return fileName + date;
},
+ confirmDialog(message, options = {}) {
+ return this.$confirm({ message, ...options });
+ },
},
};
</script>
diff --git a/src/views/Operations/FactoryReset/FactoryReset.vue b/src/views/Operations/FactoryReset/FactoryReset.vue
index f59b0a2..8e9ed8f 100644
--- a/src/views/Operations/FactoryReset/FactoryReset.vue
+++ b/src/views/Operations/FactoryReset/FactoryReset.vue
@@ -20,7 +20,7 @@
>
{{ $t('pageFactoryReset.form.resetBiosOptionLabel') }}
</b-form-radio>
- <b-form-text id="reset-bios" class="ml-4 mb-3">
+ <b-form-text id="reset-bios" class="ms-4 mb-3">
{{ $t('pageFactoryReset.form.resetBiosOptionHelperText') }}
</b-form-text>
@@ -32,7 +32,7 @@
>
{{ $t('pageFactoryReset.form.resetToDefaultsOptionLabel') }}
</b-form-radio>
- <b-form-text id="reset-to-defaults" class="ml-4 mb-3">
+ <b-form-text id="reset-to-defaults" class="ms-4 mb-3">
{{
$t('pageFactoryReset.form.resetToDefaultsOptionHelperText')
}}
@@ -51,7 +51,11 @@
</b-form>
<!-- Modals -->
- <modal-reset :reset-type="resetOption" @ok-confirm="onOkConfirm" />
+ <modal-reset
+ v-model="showResetModal"
+ :reset-type="resetOption"
+ @ok-confirm="onOkConfirm"
+ />
</b-container>
</template>
@@ -69,6 +73,7 @@
data() {
return {
$t: useI18n().t,
+ showResetModal: false,
resetOption: 'resetBios',
};
},
@@ -77,7 +82,7 @@
},
methods: {
onResetSubmit() {
- this.$bvModal.show('modal-reset');
+ this.showResetModal = true;
},
onOkConfirm() {
if (this.resetOption == 'resetBios') {
diff --git a/src/views/Operations/FactoryReset/FactoryResetModal.vue b/src/views/Operations/FactoryReset/FactoryResetModal.vue
index 5865bb3..97c74c4 100644
--- a/src/views/Operations/FactoryReset/FactoryResetModal.vue
+++ b/src/views/Operations/FactoryReset/FactoryResetModal.vue
@@ -2,33 +2,33 @@
<b-modal
id="modal-reset"
ref="modal"
- :title="$t(`pageFactoryReset.modal.${resetType}Title`)"
+ :title="modalTitle"
title-tag="h2"
@hidden="resetConfirm"
>
<p class="mb-2">
- <strong>{{ $t(`pageFactoryReset.modal.${resetType}Header`) }}</strong>
+ <strong>{{ modalHeader }}</strong>
</p>
- <ul v-if="resetType == 'resetBios'" class="pl-3 mb-4">
+ <ul v-if="resetType == 'resetBios'" class="ps-3 mb-4">
<li class="mt-1 mb-1">
- {{ $t('pageFactoryReset.modal.resetBiosSettingsList.item1') }}
+ {{ t('pageFactoryReset.modal.resetBiosSettingsList.item1') }}
</li>
<li class="mt-1 mb-1">
- {{ $t('pageFactoryReset.modal.resetBiosSettingsList.item2') }}
+ {{ t('pageFactoryReset.modal.resetBiosSettingsList.item2') }}
</li>
</ul>
- <ul v-else-if="resetType == 'resetToDefaults'" class="pl-3 mb-4">
+ <ul v-else-if="resetType == 'resetToDefaults'" class="ps-3 mb-4">
<li class="mt-1 mb-1">
- {{ $t('pageFactoryReset.modal.resetToDefaultsSettingsList.item1') }}
+ {{ t('pageFactoryReset.modal.resetToDefaultsSettingsList.item1') }}
</li>
<li class="mt-1 mb-1">
- {{ $t('pageFactoryReset.modal.resetToDefaultsSettingsList.item2') }}
+ {{ t('pageFactoryReset.modal.resetToDefaultsSettingsList.item2') }}
</li>
<li class="mt-1 mb-1">
- {{ $t('pageFactoryReset.modal.resetToDefaultsSettingsList.item3') }}
+ {{ t('pageFactoryReset.modal.resetToDefaultsSettingsList.item3') }}
</li>
<li class="mt-1 mb-1">
- {{ $t('pageFactoryReset.modal.resetToDefaultsSettingsList.item4') }}
+ {{ t('pageFactoryReset.modal.resetToDefaultsSettingsList.item4') }}
</li>
</ul>
@@ -36,8 +36,8 @@
<template v-if="!isServerOff">
<p class="d-flex mb-2">
<status-icon status="danger" />
- <span id="reset-to-default-warning" class="ml-1">
- {{ $t(`pageFactoryReset.modal.resetWarningMessage`) }}
+ <span id="reset-to-default-warning" class="ms-1">
+ {{ t(`pageFactoryReset.modal.resetWarningMessage`) }}
</span>
</p>
<b-form-checkbox
@@ -45,23 +45,23 @@
aria-describedby="reset-to-default-warning"
@input="v$.confirm.$touch()"
>
- {{ $t(`pageFactoryReset.modal.resetWarningCheckLabel`) }}
+ {{ t(`pageFactoryReset.modal.resetWarningCheckLabel`) }}
</b-form-checkbox>
<b-form-invalid-feedback
role="alert"
:state="getValidationState(v$.confirm)"
>
- {{ $t('global.form.fieldRequired') }}
+ {{ t('global.form.fieldRequired') }}
</b-form-invalid-feedback>
</template>
- <template #modal-footer="{ cancel }">
+ <template #footer="{ cancel }">
<b-button
variant="secondary"
data-test-id="factoryReset-button-cancel"
@click="cancel()"
>
- {{ $t('global.action.cancel') }}
+ {{ t('global.action.cancel') }}
</b-button>
<b-button
type="sumbit"
@@ -69,7 +69,7 @@
data-test-id="factoryReset-button-confirm"
@click="handleConfirm"
>
- {{ $t(`pageFactoryReset.modal.${resetType}SubmitText`) }}
+ {{ modalSubmitText }}
</b-button>
</template>
</b-modal>
@@ -97,7 +97,7 @@
},
data() {
return {
- $t: useI18n().t,
+ t: useI18n().t,
confirm: false,
};
},
@@ -108,6 +108,15 @@
isServerOff() {
return this.serverStatus === 'off' ? true : false;
},
+ modalTitle() {
+ return this.t(`pageFactoryReset.modal.${this.resetType}Title`);
+ },
+ modalHeader() {
+ return this.t(`pageFactoryReset.modal.${this.resetType}Header`);
+ },
+ modalSubmitText() {
+ return this.t(`pageFactoryReset.modal.${this.resetType}SubmitText`);
+ },
},
validations: {
confirm: {
@@ -116,6 +125,19 @@
},
},
},
+ watch: {
+ isServerOff: {
+ handler(newValue) {
+ // Touch validation when server is on to show required message immediately
+ if (!newValue) {
+ this.$nextTick(() => {
+ this.v$.confirm.$touch();
+ });
+ }
+ },
+ immediate: true,
+ },
+ },
methods: {
handleConfirm() {
this.v$.$touch();
diff --git a/src/views/Operations/Firmware/FirmwareCardsBios.vue b/src/views/Operations/Firmware/FirmwareCardsBios.vue
index a2994cc..184f0a3 100644
--- a/src/views/Operations/Firmware/FirmwareCardsBios.vue
+++ b/src/views/Operations/Firmware/FirmwareCardsBios.vue
@@ -1,38 +1,45 @@
<template>
<page-section :section-title="$t('pageFirmware.sectionTitleBiosCards')">
- <b-card-group deck>
+ <b-row class="row-cols-1 row-cols-md-2">
<!-- Running image -->
- <b-card>
- <template #header>
- <p class="font-weight-bold m-0">
- {{ $t('pageFirmware.cardTitleRunning') }}
- </p>
- </template>
- <dl class="mb-0">
- <dt>{{ $t('pageFirmware.cardBodyVersion') }}</dt>
- <dd class="mb-0">{{ runningVersion }}</dd>
- </dl>
- </b-card>
+ <b-col class="mb-3">
+ <b-card class="h-100">
+ <template #header>
+ <p class="fw-bold m-0">
+ {{ $t('pageFirmware.cardTitleRunning') }}
+ </p>
+ </template>
+ <dl class="mb-0">
+ <dt>{{ $t('pageFirmware.cardBodyVersion') }}</dt>
+ <dd class="mb-0">{{ runningVersion }}</dd>
+ </dl>
+ </b-card>
+ </b-col>
<!-- Backup image -->
- <b-card>
- <template #header>
- <p class="font-weight-bold m-0">
- {{ $t('pageFirmware.cardTitleBackup') }}
- </p>
- </template>
- <dl class="mb-0">
- <dt>{{ $t('pageFirmware.cardBodyVersion') }}</dt>
- <dd class="mb-0">
- <status-icon v-if="showBackupImageStatus" status="danger" />
- <span v-if="showBackupImageStatus" class="sr-only">
- {{ backupStatus }}
- </span>
- {{ backupVersion }}
- </dd>
- </dl>
- </b-card>
- </b-card-group>
+ <b-col class="mb-3">
+ <b-card class="h-100">
+ <template #header>
+ <p class="fw-bold m-0">
+ {{ $t('pageFirmware.cardTitleBackup') }}
+ </p>
+ </template>
+ <dl class="mb-0">
+ <dt>{{ $t('pageFirmware.cardBodyVersion') }}</dt>
+ <dd class="mb-0">
+ <status-icon v-if="showBackupImageStatus" status="danger" />
+ <span
+ v-if="showBackupImageStatus"
+ class="visually-hidden-focusable"
+ >
+ {{ backupStatus }}
+ </span>
+ {{ backupVersion }}
+ </dd>
+ </dl>
+ </b-card>
+ </b-col>
+ </b-row>
</page-section>
</template>
diff --git a/src/views/Operations/Firmware/FirmwareCardsBmc.vue b/src/views/Operations/Firmware/FirmwareCardsBmc.vue
index 2d18d5b..0e763f3 100644
--- a/src/views/Operations/Firmware/FirmwareCardsBmc.vue
+++ b/src/views/Operations/Firmware/FirmwareCardsBmc.vue
@@ -1,53 +1,64 @@
<template>
<div>
<page-section :section-title="sectionTitle">
- <b-card-group deck>
+ <b-row class="row-cols-1 row-cols-md-2">
<!-- Running image -->
- <b-card>
- <template #header>
- <p class="font-weight-bold m-0">
- {{ $t('pageFirmware.cardTitleRunning') }}
- </p>
- </template>
- <dl class="mb-0">
- <dt>{{ $t('pageFirmware.cardBodyVersion') }}</dt>
- <dd class="mb-0">{{ runningVersion }}</dd>
- </dl>
- </b-card>
+ <b-col class="mb-3">
+ <b-card class="h-100">
+ <template #header>
+ <p class="fw-bold m-0">
+ {{ $t('pageFirmware.cardTitleRunning') }}
+ </p>
+ </template>
+ <dl class="mb-0">
+ <dt>{{ $t('pageFirmware.cardBodyVersion') }}</dt>
+ <dd class="mb-0">{{ runningVersion }}</dd>
+ </dl>
+ </b-card>
+ </b-col>
<!-- Backup image -->
- <b-card>
- <template #header>
- <p class="font-weight-bold m-0">
- {{ $t('pageFirmware.cardTitleBackup') }}
- </p>
- </template>
- <dl>
- <dt>{{ $t('pageFirmware.cardBodyVersion') }}</dt>
- <dd>
- <status-icon v-if="showBackupImageStatus" status="danger" />
- <span v-if="showBackupImageStatus" class="sr-only">
- {{ backupStatus }}
- </span>
- {{ backupVersion }}
- </dd>
- </dl>
- <b-btn
- v-if="!switchToBackupImageDisabled"
- v-b-modal.modal-switch-to-running
- data-test-id="firmware-button-switchToRunning"
- variant="link"
- size="sm"
- class="py-0 px-1 mt-2"
- :disabled="isPageDisabled || !backup || !isServerOff"
- >
- <icon-switch class="d-none d-sm-inline-block" />
- {{ $t('pageFirmware.cardActionSwitchToRunning') }}
- </b-btn>
- </b-card>
- </b-card-group>
+ <b-col class="mb-3">
+ <b-card class="h-100">
+ <template #header>
+ <p class="fw-bold m-0">
+ {{ $t('pageFirmware.cardTitleBackup') }}
+ </p>
+ </template>
+ <dl>
+ <dt>{{ $t('pageFirmware.cardBodyVersion') }}</dt>
+ <dd>
+ <status-icon v-if="showBackupImageStatus" status="danger" />
+ <span
+ v-if="showBackupImageStatus"
+ class="visually-hidden-focusable"
+ >
+ {{ backupStatus }}
+ </span>
+ {{ backupVersion }}
+ </dd>
+ </dl>
+ <b-btn
+ v-if="!switchToBackupImageDisabled"
+ data-test-id="firmware-button-switchToRunning"
+ variant="link"
+ size="sm"
+ class="py-0 px-1 mt-2"
+ :disabled="isPageDisabled || !backup || !isServerOff"
+ @click="showSwitchToRunning = true"
+ >
+ <icon-switch class="d-none d-sm-inline-block" />
+ {{ $t('pageFirmware.cardActionSwitchToRunning') }}
+ </b-btn>
+ </b-card>
+ </b-col>
+ </b-row>
</page-section>
- <modal-switch-to-running :backup="backupVersion" @ok="switchToRunning" />
+ <modal-switch-to-running
+ v-model="showSwitchToRunning"
+ :backup="backupVersion"
+ @ok="switchToRunning"
+ />
</div>
</template>
@@ -82,6 +93,7 @@
loading,
switchToBackupImageDisabled:
process.env.VUE_APP_SWITCH_TO_BACKUP_IMAGE_DISABLED === 'true',
+ showSwitchToRunning: false,
};
},
computed: {
diff --git a/src/views/Operations/Firmware/FirmwareFormUpdate.vue b/src/views/Operations/Firmware/FirmwareFormUpdate.vue
index 2b9a616..a9e327d 100644
--- a/src/views/Operations/Firmware/FirmwareFormUpdate.vue
+++ b/src/views/Operations/Firmware/FirmwareFormUpdate.vue
@@ -6,6 +6,7 @@
<b-form-group
:label="$t('pageFirmware.form.updateFirmware.imageFile')"
label-for="image-file"
+ class="mb-3"
>
<form-file
id="image-file"
@@ -34,7 +35,7 @@
</div>
<!-- Modals -->
- <modal-update-firmware @ok="updateFirmware" />
+ <modal-update-firmware v-model="showUpdateModal" @ok="updateFirmware" />
</div>
</template>
@@ -74,6 +75,7 @@
return {
$t: useI18n().t,
loading,
+ showUpdateModal: false,
file: null,
isServerPowerOffRequired:
process.env.VUE_APP_SERVER_OFF_REQUIRED === 'true',
@@ -120,7 +122,7 @@
onSubmitUpload() {
this.v$.$touch();
if (this.v$.$invalid) return;
- this.$bvModal.show('modal-update-firmware');
+ this.showUpdateModal = true;
},
onFileUpload(file) {
this.file = file;
diff --git a/src/views/Operations/KeyClear/KeyClear.vue b/src/views/Operations/KeyClear/KeyClear.vue
index 7baad34..d0a6a5e 100644
--- a/src/views/Operations/KeyClear/KeyClear.vue
+++ b/src/views/Operations/KeyClear/KeyClear.vue
@@ -4,7 +4,7 @@
<b-row>
<b-col md="8" xl="6">
<alert variant="info" class="mb-4">
- <div class="font-weight-bold">
+ <div class="fw-bold">
{{ $t('pageKeyClear.alert.title') }}
</div>
<div>
@@ -26,19 +26,19 @@
<b-form-radio class="mb-1" value="NONE">
{{ $t('pageKeyClear.form.none') }}
</b-form-radio>
- <b-form-text id="key-clear-not-requested" class="ml-4 mb-3">
+ <b-form-text id="key-clear-not-requested" class="ms-4 mb-3">
{{ $t('pageKeyClear.form.keyClearNotRequested') }}
</b-form-text>
<b-form-radio class="mb-1" value="ALL">
{{ $t('pageKeyClear.form.clearAllLabel') }}
</b-form-radio>
- <b-form-text id="clear-all" class="ml-4 mb-3">
+ <b-form-text id="clear-all" class="ms-4 mb-3">
{{ $t('pageKeyClear.form.clearAllHeperText') }}
</b-form-text>
<b-form-radio class="mb-1" value="POWERVM_SYSKEY">
{{ $t('pageKeyClear.form.clearHypervisorSystemKeyLabel') }}
</b-form-radio>
- <b-form-text id="clear-hypervisor-key" class="ml-4 mb-3">
+ <b-form-text id="clear-hypervisor-key" class="ms-4 mb-3">
{{ $t('pageKeyClear.form.clearHypervisorSystemKeyHelperText') }}
</b-form-text>
<template v-if="username == 'service'">
@@ -71,11 +71,16 @@
import Alert from '@/components/Global/Alert';
import { useI18n } from 'vue-i18n';
import i18n from '@/i18n';
+import { useModal } from 'bootstrap-vue-next';
export default {
name: 'KeyClear',
components: { PageTitle, Alert },
mixins: [LoadingBarMixin, BVToastMixin],
+ setup() {
+ const bvModal = useModal();
+ return { bvModal };
+ },
data() {
return {
$t: useI18n().t,
@@ -88,22 +93,20 @@
},
methods: {
onKeyClearSubmit(valueSelected) {
- this.$bvModal
- .msgBoxConfirm(i18n.global.t('pageKeyClear.modal.clearAllMessage'), {
- title: i18n.global.t('pageKeyClear.modal.clearAllTitle'),
- okTitle: i18n.global.t('pageKeyClear.modal.clear'),
- okVariant: 'danger',
- cancelTitle: i18n.global.t('global.action.cancel'),
- autoFocusButton: 'cancel',
- })
- .then((clearConfirmed) => {
- if (clearConfirmed) {
- this.$store
- .dispatch('keyClear/clearEncryptionKeys', valueSelected)
- .then((message) => this.successToast(message))
- .catch(({ message }) => this.errorToast(message));
- }
- });
+ this.$confirm(i18n.global.t('pageKeyClear.modal.clearAllMessage'), {
+ title: i18n.global.t('pageKeyClear.modal.clearAllTitle'),
+ okTitle: i18n.global.t('pageKeyClear.modal.clear'),
+ okVariant: 'danger',
+ cancelTitle: i18n.global.t('global.action.cancel'),
+ autoFocusButton: 'cancel',
+ }).then((clearConfirmed) => {
+ if (clearConfirmed) {
+ this.$store
+ .dispatch('keyClear/clearEncryptionKeys', valueSelected)
+ .then((message) => this.successToast(message))
+ .catch(({ message }) => this.errorToast(message));
+ }
+ });
},
},
};
diff --git a/src/views/Operations/Kvm/KvmConsole.vue b/src/views/Operations/Kvm/KvmConsole.vue
index 64ee897..1ea6771 100644
--- a/src/views/Operations/Kvm/KvmConsole.vue
+++ b/src/views/Operations/Kvm/KvmConsole.vue
@@ -4,9 +4,7 @@
<b-row class="d-flex">
<b-col class="d-flex flex-column justify-content-end" cols="4">
<dl class="mb-2" sm="2" md="2">
- <dt class="d-inline font-weight-bold mr-1">
- {{ $t('pageKvm.status') }}:
- </dt>
+ <dt class="d-inline fw-bold me-1">{{ $t('pageKvm.status') }}:</dt>
<dd class="d-inline">
<status-icon :status="serverStatusIcon" />
<span class="d-none d-md-inline"> {{ serverStatus }}</span>
@@ -14,7 +12,7 @@
</dl>
</b-col>
- <b-col class="d-flex justify-content-end pr-1">
+ <b-col class="d-flex justify-content-end pe-1">
<b-button
v-if="isConnected"
variant="link"
@@ -125,7 +123,9 @@
this.resizeKvmWindow = throttle(() => {
setTimeout(that.setWidthToolbar, 0);
}, 1000);
- window.addEventListener('resize', this.resizeKvmWindow);
+ window.addEventListener('resize', this.resizeKvmWindow, {
+ passive: true,
+ });
this.rfb.addEventListener('connect', () => {
that.isConnected = true;
@@ -178,16 +178,16 @@
<style scoped lang="scss">
.button-ctrl-alt-delete {
- float: right;
+ float: inline-end;
}
.kvm-status {
- padding-top: $spacer / 2;
- padding-left: $spacer / 4;
+ padding-top: calc(#{$spacer} / 2);
+ padding-inline-start: calc(#{$spacer} / 4);
display: inline-block;
}
.margin-left-full-window {
- margin-left: 5px;
+ margin-inline-start: 5px;
}
</style>
diff --git a/src/views/Operations/RebootBmc/RebootBmc.vue b/src/views/Operations/RebootBmc/RebootBmc.vue
index d8c529c..e98b7da 100644
--- a/src/views/Operations/RebootBmc/RebootBmc.vue
+++ b/src/views/Operations/RebootBmc/RebootBmc.vue
@@ -40,6 +40,7 @@
import LoadingBarMixin from '@/components/Mixins/LoadingBarMixin';
import { useI18n } from 'vue-i18n';
import i18n from '@/i18n';
+import { useModal } from 'bootstrap-vue-next';
export default {
name: 'RebootBmc',
@@ -49,6 +50,10 @@
this.hideLoader();
next();
},
+ setup() {
+ const bvModal = useModal();
+ return { bvModal };
+ },
data() {
return {
$t: useI18n().t,
@@ -67,16 +72,14 @@
},
methods: {
onClick() {
- this.$bvModal
- .msgBoxConfirm(i18n.global.t('pageRebootBmc.modal.confirmMessage'), {
- title: i18n.global.t('pageRebootBmc.modal.confirmTitle'),
- okTitle: i18n.global.t('global.action.confirm'),
- cancelTitle: i18n.global.t('global.action.cancel'),
- autoFocusButton: 'ok',
- })
- .then((confirmed) => {
- if (confirmed) this.rebootBmc();
- });
+ this.$confirm(i18n.global.t('pageRebootBmc.modal.confirmMessage'), {
+ title: i18n.global.t('pageRebootBmc.modal.confirmTitle'),
+ okTitle: i18n.global.t('global.action.confirm'),
+ cancelTitle: i18n.global.t('global.action.cancel'),
+ autoFocusButton: 'ok',
+ }).then((confirmed) => {
+ if (confirmed) this.rebootBmc();
+ });
},
rebootBmc() {
this.$store
diff --git a/src/views/Operations/SerialOverLan/SerialOverLanConsole.vue b/src/views/Operations/SerialOverLan/SerialOverLanConsole.vue
index 8f1c4bc..ca6a46d 100644
--- a/src/views/Operations/SerialOverLan/SerialOverLanConsole.vue
+++ b/src/views/Operations/SerialOverLan/SerialOverLanConsole.vue
@@ -17,7 +17,7 @@
<b-row class="d-flex">
<b-col class="d-flex flex-column justify-content-end">
<dl class="mb-2" sm="6" md="6">
- <dt class="d-inline font-weight-bold mr-1">
+ <dt class="d-inline fw-bold me-1">
{{ $t('pageSerialOverLan.status') }}:
</dt>
<dd class="d-inline">
diff --git a/src/views/Operations/ServerPowerOperations/BootSettings.vue b/src/views/Operations/ServerPowerOperations/BootSettings.vue
index ea66baa..b9ea035 100644
--- a/src/views/Operations/ServerPowerOperations/BootSettings.vue
+++ b/src/views/Operations/ServerPowerOperations/BootSettings.vue
@@ -13,7 +13,7 @@
v-model="form.bootOption"
:disabled="bootSourceOptions.length === 0"
:options="bootSourceOptions"
- @change="onChangeSelect"
+ @update:model-value="onChangeSelect"
>
</b-form-select>
</b-form-group>
@@ -118,7 +118,9 @@
this.$store
.dispatch('serverBootSettings/getTpmPolicy')
.finally(() =>
- this.$root.$emit('server-power-operations-boot-settings-complete'),
+ require('@/eventBus').default.$emit(
+ 'server-power-operations-boot-settings-complete',
+ ),
);
},
methods: {
diff --git a/src/views/Operations/ServerPowerOperations/ServerPowerOperations.vue b/src/views/Operations/ServerPowerOperations/ServerPowerOperations.vue
index 8dca96e..95a36d3 100644
--- a/src/views/Operations/ServerPowerOperations/ServerPowerOperations.vue
+++ b/src/views/Operations/ServerPowerOperations/ServerPowerOperations.vue
@@ -9,7 +9,9 @@
<b-row>
<b-col>
<dl>
- <dt>{{ $t('pageServerPowerOperations.serverStatus') }}</dt>
+ <dt>
+ {{ $t('pageServerPowerOperations.serverStatus') }}
+ </dt>
<dd
v-if="serverStatus === 'on'"
data-test-id="powerServerOps-text-hostStatus"
@@ -180,6 +182,7 @@
import i18n from '@/i18n';
import { privilegesId } from '@/store/modules/GlobalStore';
import { mapGetters } from 'vuex';
+import { useModal } from 'bootstrap-vue-next';
export default {
name: 'ServerPowerOperations',
@@ -189,6 +192,10 @@
this.hideLoader();
next();
},
+ setup() {
+ const bvModal = useModal();
+ return { bvModal };
+ },
data() {
return {
$t: useI18n().t,
@@ -223,10 +230,9 @@
},
created() {
this.startLoader();
+ const eventBus = require('@/eventBus').default;
const bootSettingsPromise = new Promise((resolve) => {
- this.$root.$on('server-power-operations-boot-settings-complete', () =>
- resolve(),
- );
+ eventBus.$once('server-power-operations-boot-settings-complete', resolve);
});
Promise.all([
this.$store.dispatch('serverBootSettings/getBootSettings'),
@@ -252,17 +258,13 @@
};
if (this.form.rebootOption === 'orderly') {
- this.$bvModal
- .msgBoxConfirm(modalMessage, modalOptions)
- .then((confirmed) => {
- if (confirmed) this.$store.dispatch('controls/serverSoftReboot');
- });
+ this.confirmDialog(modalMessage, modalOptions).then((confirmed) => {
+ if (confirmed) this.$store.dispatch('controls/serverSoftReboot');
+ });
} else if (this.form.rebootOption === 'immediate') {
- this.$bvModal
- .msgBoxConfirm(modalMessage, modalOptions)
- .then((confirmed) => {
- if (confirmed) this.$store.dispatch('controls/serverHardReboot');
- });
+ this.confirmDialog(modalMessage, modalOptions).then((confirmed) => {
+ if (confirmed) this.$store.dispatch('controls/serverHardReboot');
+ });
}
},
shutdownServer() {
@@ -279,20 +281,19 @@
};
if (this.form.shutdownOption === 'orderly') {
- this.$bvModal
- .msgBoxConfirm(modalMessage, modalOptions)
- .then((confirmed) => {
- if (confirmed) this.$store.dispatch('controls/serverSoftPowerOff');
- });
+ this.confirmDialog(modalMessage, modalOptions).then((confirmed) => {
+ if (confirmed) this.$store.dispatch('controls/serverSoftPowerOff');
+ });
}
if (this.form.shutdownOption === 'immediate') {
- this.$bvModal
- .msgBoxConfirm(modalMessage, modalOptions)
- .then((confirmed) => {
- if (confirmed) this.$store.dispatch('controls/serverHardPowerOff');
- });
+ this.confirmDialog(modalMessage, modalOptions).then((confirmed) => {
+ if (confirmed) this.$store.dispatch('controls/serverHardPowerOff');
+ });
}
},
+ confirmDialog(message, options = {}) {
+ return this.$confirm({ message, ...options });
+ },
},
};
</script>
diff --git a/src/views/Operations/VirtualMedia/ModalConfigureConnection.vue b/src/views/Operations/VirtualMedia/ModalConfigureConnection.vue
index 682528b..b2eaaae 100644
--- a/src/views/Operations/VirtualMedia/ModalConfigureConnection.vue
+++ b/src/views/Operations/VirtualMedia/ModalConfigureConnection.vue
@@ -36,6 +36,7 @@
id="username"
v-model="form.username"
type="text"
+ autocomplete="username"
data-test-id="configureConnection-input-username"
/>
</b-form-group>
@@ -47,6 +48,7 @@
id="password"
v-model="form.password"
type="password"
+ autocomplete="current-password"
data-test-id="configureConnection-input-password"
/>
</b-form-group>
@@ -81,10 +83,6 @@
connection: {
type: Object,
default: null,
- validator: (prop) => {
- console.log(prop);
- return true;
- },
},
},
emits: ['ok'],
diff --git a/src/views/Operations/VirtualMedia/VirtualMedia.vue b/src/views/Operations/VirtualMedia/VirtualMedia.vue
index e158059..36fb909 100644
--- a/src/views/Operations/VirtualMedia/VirtualMedia.vue
+++ b/src/views/Operations/VirtualMedia/VirtualMedia.vue
@@ -69,7 +69,7 @@
<b-button
v-if="!device.isActive"
variant="primary"
- class="float-right"
+ class="float-end"
:disabled="!device.serverUri"
@click="startLegacy(device)"
>
@@ -78,7 +78,7 @@
<b-button
v-if="device.isActive"
variant="primary"
- class="float-right"
+ class="float-end"
@click="stopLegacy(device)"
>
{{ $t('pageVirtualMedia.stop') }}
@@ -90,6 +90,7 @@
</b-col>
</b-row>
<modal-configure-connection
+ v-model="showConfigureConnectionModal"
:connection="modalConfigureConnection"
@ok="saveConnection"
/>
@@ -106,15 +107,21 @@
import FormFile from '@/components/Global/FormFile';
import { useI18n } from 'vue-i18n';
import i18n from '@/i18n';
+import { useModal } from 'bootstrap-vue-next';
export default {
name: 'VirtualMedia',
components: { PageTitle, PageSection, ModalConfigureConnection, FormFile },
mixins: [BVToastMixin, LoadingBarMixin],
+ setup() {
+ const bvModal = useModal();
+ return { bvModal };
+ },
data() {
return {
$t: useI18n().t,
modalConfigureConnection: null,
+ showConfigureConnectionModal: false,
loadImageFromExternalServer:
process.env.VUE_APP_VIRTUAL_MEDIA_LIST_ENABLED === 'true'
? true
@@ -223,7 +230,7 @@
},
configureConnection(connectionData) {
this.modalConfigureConnection = connectionData;
- this.$bvModal.show('configure-connection');
+ this.showConfigureConnectionModal = true;
},
concatId(val) {
return val.split(' ').join('_').toLowerCase();
diff --git a/src/views/Overview/Overview.vue b/src/views/Overview/Overview.vue
index 2bf4205..1d64dff 100644
--- a/src/views/Overview/Overview.vue
+++ b/src/views/Overview/Overview.vue
@@ -6,21 +6,35 @@
:section-title="$t('pageOverview.systemInformation')"
class="mb-1"
>
- <b-card-group deck>
- <overview-server />
- <overview-firmware />
- </b-card-group>
- <b-card-group deck>
- <overview-network />
- <overview-power />
- </b-card-group>
+ <b-row class="row-cols-1 row-cols-md-2">
+ <b-col class="mb-3">
+ <overview-server class="h-100" />
+ </b-col>
+ <b-col class="mb-3">
+ <overview-firmware class="h-100" />
+ </b-col>
+ </b-row>
+ <b-row class="row-cols-1 row-cols-md-2">
+ <b-col class="mb-3">
+ <overview-network class="h-100" />
+ </b-col>
+ <b-col class="mb-3">
+ <overview-power class="h-100" />
+ </b-col>
+ </b-row>
</page-section>
<page-section :section-title="$t('pageOverview.statusInformation')">
- <b-card-group deck>
- <overview-events />
- <overview-inventory />
- <overview-dumps v-if="showDumps" />
- </b-card-group>
+ <b-row class="row-cols-1 row-cols-md-2">
+ <b-col class="mb-3">
+ <overview-events class="h-100" />
+ </b-col>
+ <b-col class="mb-3">
+ <overview-inventory class="h-100" />
+ </b-col>
+ <b-col v-if="showDumps" class="mb-3">
+ <overview-dumps class="h-100" />
+ </b-col>
+ </b-row>
</page-section>
</b-container>
</template>
@@ -58,33 +72,59 @@
return {
$t: useI18n().t,
showDumps: process.env.VUE_APP_ENV_NAME === 'ibm',
+ // Promise resolvers
+ dumpsResolver: null,
+ eventsResolver: null,
+ selResolver: null,
+ firmwareResolver: null,
+ inventoryResolver: null,
+ networkResolver: null,
+ powerResolver: null,
+ quicklinksResolver: null,
+ serverResolver: null,
};
},
created() {
this.startLoader();
+
const dumpsPromise = new Promise((resolve) => {
- this.$root.$on('overview-dumps-complete', () => resolve());
+ this.dumpsResolver = resolve;
+ this.$eventBus.on('overview-dumps-complete', () => resolve());
});
+
const eventsPromise = new Promise((resolve) => {
- this.$root.$on('overview-events-complete', () => resolve());
+ this.eventsResolver = resolve;
+ this.$eventBus.on('overview-events-complete', () => resolve());
});
+
const firmwarePromise = new Promise((resolve) => {
- this.$root.$on('overview-firmware-complete', () => resolve());
+ this.firmwareResolver = resolve;
+ this.$eventBus.on('overview-firmware-complete', () => resolve());
});
+
const inventoryPromise = new Promise((resolve) => {
- this.$root.$on('overview-inventory-complete', () => resolve());
+ this.inventoryResolver = resolve;
+ this.$eventBus.on('overview-inventory-complete', () => resolve());
});
+
const networkPromise = new Promise((resolve) => {
- this.$root.$on('overview-network-complete', () => resolve());
+ this.networkResolver = resolve;
+ this.$eventBus.on('overview-network-complete', () => resolve());
});
+
const powerPromise = new Promise((resolve) => {
- this.$root.$on('overview-power-complete', () => resolve());
+ this.powerResolver = resolve;
+ this.$eventBus.on('overview-power-complete', () => resolve());
});
+
const quicklinksPromise = new Promise((resolve) => {
- this.$root.$on('overview-quicklinks-complete', () => resolve());
+ this.quicklinksResolver = resolve;
+ this.$eventBus.on('overview-quicklinks-complete', () => resolve());
});
+
const serverPromise = new Promise((resolve) => {
- this.$root.$on('overview-server-complete', () => resolve());
+ this.serverResolver = resolve;
+ this.$eventBus.on('overview-server-complete', () => resolve());
});
const promises = [
@@ -97,7 +137,59 @@
serverPromise,
];
if (this.showDumps) promises.push(dumpsPromise);
- Promise.all(promises).finally(() => this.endLoader());
+ Promise.all(promises).finally(() => {
+ this.endLoader();
+ });
+ },
+ beforeUnmount() {
+ // Clean up event listeners
+ this.$eventBus.off('overview-dumps-complete', this.handleDumpsComplete);
+ this.$eventBus.off('overview-events-complete', this.handleEventsComplete);
+ this.$eventBus.off('overview-sel-complete', this.handleSelComplete);
+ this.$eventBus.off(
+ 'overview-firmware-complete',
+ this.handleFirmwareComplete,
+ );
+ this.$eventBus.off(
+ 'overview-inventory-complete',
+ this.handleInventoryComplete,
+ );
+ this.$eventBus.off('overview-network-complete', this.handleNetworkComplete);
+ this.$eventBus.off('overview-power-complete', this.handlePowerComplete);
+ this.$eventBus.off(
+ 'overview-quicklinks-complete',
+ this.handleQuicklinksComplete,
+ );
+ this.$eventBus.off('overview-server-complete', this.handleServerComplete);
+ },
+ methods: {
+ handleDumpsComplete() {
+ if (this.dumpsResolver) this.dumpsResolver();
+ },
+ handleEventsComplete() {
+ if (this.eventsResolver) this.eventsResolver();
+ },
+ handleSelComplete() {
+ if (this.selResolver) this.selResolver();
+ },
+ handleFirmwareComplete() {
+ if (this.firmwareResolver) this.firmwareResolver();
+ },
+ handleInventoryComplete() {
+ if (this.inventoryResolver) this.inventoryResolver();
+ },
+ handleNetworkComplete() {
+ if (this.networkResolver) this.networkResolver();
+ },
+ handlePowerComplete() {
+ if (this.powerResolver) this.powerResolver();
+ },
+ handleQuicklinksComplete() {
+ if (this.quicklinksResolver) this.quicklinksResolver();
+ },
+ handleServerComplete() {
+ if (this.serverResolver) this.serverResolver();
+ },
},
};
</script>
diff --git a/src/views/Overview/OverviewCard.vue b/src/views/Overview/OverviewCard.vue
index 7cfe558..d6955b6 100644
--- a/src/views/Overview/OverviewCard.vue
+++ b/src/views/Overview/OverviewCard.vue
@@ -14,7 +14,7 @@
<span v-if="downloadButton">{{ $t('global.action.download') }}</span>
<span v-if="exportButton">{{ $t('global.action.exportAll') }}</span>
</b-button>
- <span v-if="exportButton || downloadButton" class="pl-2 pr-2">|</span>
+ <span v-if="exportButton || downloadButton" class="ps-2 pe-2">|</span>
<b-link :to="to">{{ $t('pageOverview.viewMore') }}</b-link>
</div>
</div>
diff --git a/src/views/Overview/OverviewDumps.vue b/src/views/Overview/OverviewDumps.vue
index ba7d7e2..abdaf47 100644
--- a/src/views/Overview/OverviewDumps.vue
+++ b/src/views/Overview/OverviewDumps.vue
@@ -41,7 +41,7 @@
},
created() {
this.$store.dispatch('dumps/getAllDumps').finally(() => {
- this.$root.$emit('overview-dumps-complete');
+ this.$eventBus.$emit('overview-dumps-complete');
});
},
methods: {
diff --git a/src/views/Overview/OverviewEvents.vue b/src/views/Overview/OverviewEvents.vue
index 147f30c..65ac0ea 100644
--- a/src/views/Overview/OverviewEvents.vue
+++ b/src/views/Overview/OverviewEvents.vue
@@ -72,7 +72,7 @@
},
created() {
this.$store.dispatch('eventLog/getEventLogData').finally(() => {
- this.$root.$emit('overview-events-complete');
+ this.$eventBus.$emit('overview-events-complete');
});
},
methods: {
diff --git a/src/views/Overview/OverviewFirmware.vue b/src/views/Overview/OverviewFirmware.vue
index 0be920f..0b58a6c 100644
--- a/src/views/Overview/OverviewFirmware.vue
+++ b/src/views/Overview/OverviewFirmware.vue
@@ -61,7 +61,7 @@
},
created() {
this.$store.dispatch('firmware/getFirmwareInformation').finally(() => {
- this.$root.$emit('overview-firmware-complete');
+ this.$eventBus.$emit('overview-firmware-complete');
});
},
};
diff --git a/src/views/Overview/OverviewInventory.vue b/src/views/Overview/OverviewInventory.vue
index 4d923e1..0c92b8e 100644
--- a/src/views/Overview/OverviewInventory.vue
+++ b/src/views/Overview/OverviewInventory.vue
@@ -51,7 +51,7 @@
},
created() {
this.$store.dispatch('system/getSystem').finally(() => {
- this.$root.$emit('overview-inventory-complete');
+ this.$eventBus.$emit('overview-inventory-complete');
});
},
methods: {
diff --git a/src/views/Overview/OverviewNetwork.vue b/src/views/Overview/OverviewNetwork.vue
index c2a7693..63f5533 100644
--- a/src/views/Overview/OverviewNetwork.vue
+++ b/src/views/Overview/OverviewNetwork.vue
@@ -70,7 +70,7 @@
},
created() {
this.$store.dispatch('network/getEthernetData').finally(() => {
- this.$root.$emit('overview-network-complete');
+ this.$eventBus.$emit('overview-network-complete');
});
},
};
diff --git a/src/views/Overview/OverviewPower.vue b/src/views/Overview/OverviewPower.vue
index 74a737e..1d3d4a0 100644
--- a/src/views/Overview/OverviewPower.vue
+++ b/src/views/Overview/OverviewPower.vue
@@ -47,7 +47,7 @@
},
created() {
this.$store.dispatch('powerControl/getPowerControl').finally(() => {
- this.$root.$emit('overview-power-complete');
+ this.$eventBus.$emit('overview-power-complete');
});
},
};
diff --git a/src/views/Overview/OverviewQuickLinks.vue b/src/views/Overview/OverviewQuickLinks.vue
index abcf9fa..f50e7b2 100644
--- a/src/views/Overview/OverviewQuickLinks.vue
+++ b/src/views/Overview/OverviewQuickLinks.vue
@@ -49,7 +49,7 @@
},
created() {
Promise.all([this.$store.dispatch('global/getBmcTime')]).finally(() => {
- this.$root.$emit('overview-quicklinks-complete');
+ this.$eventBus.$emit('overview-quicklinks-complete');
});
},
};
diff --git a/src/views/Overview/OverviewServer.vue b/src/views/Overview/OverviewServer.vue
index fe0e41d..b9eda24 100644
--- a/src/views/Overview/OverviewServer.vue
+++ b/src/views/Overview/OverviewServer.vue
@@ -55,7 +55,7 @@
},
created() {
this.$store.dispatch('system/getSystem').finally(() => {
- this.$root.$emit('overview-server-complete');
+ this.$eventBus.$emit('overview-server-complete');
});
},
};
diff --git a/src/views/ProfileSettings/ProfileSettings.vue b/src/views/ProfileSettings/ProfileSettings.vue
index 73ee63f..2e6cec6 100644
--- a/src/views/ProfileSettings/ProfileSettings.vue
+++ b/src/views/ProfileSettings/ProfileSettings.vue
@@ -18,6 +18,16 @@
</b-row>
<b-form @submit.prevent="submitForm">
+ <!-- Hidden username field for browser autocomplete accessibility -->
+ <input
+ type="text"
+ name="username"
+ :value="username"
+ autocomplete="username"
+ class="visually-hidden"
+ aria-hidden="true"
+ tabindex="-1"
+ />
<b-row>
<b-col sm="8" md="6" xl="3">
<page-section
@@ -33,6 +43,7 @@
id="old-password"
v-model="form.currentPassword"
type="password"
+ autocomplete="current-password"
data-test-id="profileSettings-input-ocurrentPassword"
class="form-control-with-button"
/>
@@ -57,6 +68,7 @@
v-model="form.newPassword"
type="password"
aria-describedby="password-help-block"
+ autocomplete="new-password"
:state="getValidationState(v$.form.newPassword)"
data-test-id="profileSettings-input-newPassword"
class="form-control-with-button"
@@ -89,6 +101,7 @@
id="password-confirmation"
v-model="form.confirmPassword"
type="password"
+ autocomplete="new-password"
:state="getValidationState(v$.form.confirmPassword)"
data-test-id="profileSettings-input-confirmPassword"
class="form-control-with-button"
diff --git a/src/views/ResourceManagement/Power.vue b/src/views/ResourceManagement/Power.vue
index ac70091..5248efc 100644
--- a/src/views/ResourceManagement/Power.vue
+++ b/src/views/ResourceManagement/Power.vue
@@ -19,7 +19,7 @@
<b-form @submit.prevent="submitForm">
<b-form-group :disabled="loading">
- <b-row>
+ <b-row class="mb-3">
<b-col sm="8" md="6" xl="12">
<b-form-group :label="$t('pagePower.powerCapSettingLabel')">
<b-form-checkbox
@@ -33,7 +33,7 @@
</b-col>
</b-row>
- <b-row>
+ <b-row class="mb-3">
<b-col sm="8" md="6" xl="3">
<b-form-group
id="input-group-1"
@@ -75,6 +75,7 @@
variant="primary"
type="submit"
data-test-id="power-button-savePowerCapValue"
+ class="mt-3"
>
{{ $t('global.action.save') }}
</b-button>
diff --git a/src/views/SecurityAndAccess/Certificates/Certificates.vue b/src/views/SecurityAndAccess/Certificates/Certificates.vue
index a55b66c..29c1436 100644
--- a/src/views/SecurityAndAccess/Certificates/Certificates.vue
+++ b/src/views/SecurityAndAccess/Certificates/Certificates.vue
@@ -32,11 +32,11 @@
</b-col>
</b-row>
<b-row>
- <b-col xl="11" class="text-right">
+ <b-col xl="11" class="text-end">
<b-button
- v-b-modal.generate-csr
data-test-id="certificates-button-generateCsr"
variant="link"
+ @click="showCsr = true"
>
<icon-add />
{{ $t('pageCertificates.generateCsr') }}
@@ -57,6 +57,7 @@
responsive="md"
show-empty
hover
+ thead-class="table-light"
:busy="isBusy"
:fields="fields"
:items="tableItems"
@@ -94,8 +95,12 @@
</b-row>
<!-- Modals -->
- <modal-upload-certificate :certificate="modalCertificate" @ok="onModalOk" />
- <modal-generate-csr />
+ <modal-upload-certificate
+ v-model="showUpload"
+ :certificate="modalCertificate"
+ @ok="onModalOk"
+ />
+ <modal-generate-csr v-model="showCsr" />
</b-container>
</template>
@@ -115,6 +120,7 @@
import LoadingBarMixin from '@/components/Mixins/LoadingBarMixin';
import { useI18n } from 'vue-i18n';
import i18n from '@/i18n';
+import { useModal } from 'bootstrap-vue-next';
export default {
name: 'Certificates',
@@ -134,11 +140,17 @@
this.hideLoader();
next();
},
+ setup() {
+ const bvModal = useModal();
+ return { bvModal };
+ },
data() {
return {
$t: useI18n().t,
isBusy: true,
modalCertificate: null,
+ showUpload: false,
+ showCsr: false,
fileTypeCorrect: undefined,
fields: [
{
@@ -164,7 +176,7 @@
{
key: 'actions',
label: '',
- tdClass: 'text-right text-nowrap',
+ tdClass: 'text-end text-nowrap',
},
],
};
@@ -240,25 +252,23 @@
},
initModalUploadCertificate(certificate = null) {
this.modalCertificate = certificate;
- this.$bvModal.show('upload-certificate');
+ this.showUpload = true;
},
initModalDeleteCertificate(certificate) {
- this.$bvModal
- .msgBoxConfirm(
- i18n.global.t('pageCertificates.modal.deleteConfirmMessage', {
- issuedBy: certificate.issuedBy,
- certificate: certificate.certificate,
- }),
- {
- title: i18n.global.t('pageCertificates.deleteCertificate'),
- okTitle: i18n.global.t('global.action.delete'),
- cancelTitle: i18n.global.t('global.action.cancel'),
- autoFocusButton: 'ok',
- },
- )
- .then((deleteConfirmed) => {
- if (deleteConfirmed) this.deleteCertificate(certificate);
- });
+ this.confirmDialog(
+ i18n.global.t('pageCertificates.modal.deleteConfirmMessage', {
+ issuedBy: certificate.issuedBy,
+ certificate: certificate.certificate,
+ }),
+ {
+ title: i18n.global.t('pageCertificates.deleteCertificate'),
+ okTitle: i18n.global.t('global.action.delete'),
+ cancelTitle: i18n.global.t('global.action.cancel'),
+ autoFocusButton: 'ok',
+ },
+ ).then((deleteConfirmed) => {
+ if (deleteConfirmed) this.deleteCertificate(certificate);
+ });
},
onModalOk({ addNew, file, type, location }) {
if (addNew) {
@@ -342,6 +352,9 @@
const fileTypeExtension = file.name.split('.').pop();
return fileTypeExtension === 'pem';
},
+ confirmDialog(message, options = {}) {
+ return this.$confirm({ message, ...options });
+ },
},
};
</script>
diff --git a/src/views/SecurityAndAccess/Certificates/ModalGenerateCsr.vue b/src/views/SecurityAndAccess/Certificates/ModalGenerateCsr.vue
index a74a1e4..53364de 100644
--- a/src/views/SecurityAndAccess/Certificates/ModalGenerateCsr.vue
+++ b/src/views/SecurityAndAccess/Certificates/ModalGenerateCsr.vue
@@ -3,6 +3,7 @@
<b-modal
id="generate-csr"
ref="modal"
+ v-model="isVisible"
size="lg"
no-stacking
:title="$t('pageCertificates.modal.generateACertificateSigningRequest')"
@@ -157,7 +158,8 @@
<b-col lg="6">
<b-form-group label-for="contact-person">
<template #label>
- {{ $t('pageCertificates.modal.contactPerson') }} -
+ {{ $t('pageCertificates.modal.contactPerson') }}
+ -
<span class="form-text d-inline">
{{ $t('global.form.optional') }}
</span>
@@ -175,7 +177,8 @@
<b-col lg="6">
<b-form-group label-for="email-address">
<template #label>
- {{ $t('pageCertificates.modal.emailAddress') }} -
+ {{ $t('pageCertificates.modal.emailAddress') }}
+ -
<span class="form-text d-inline">
{{ $t('global.form.optional') }}
</span>
@@ -193,7 +196,8 @@
<b-col lg="12">
<b-form-group label-for="alternate-name">
<template #label>
- {{ $t('pageCertificates.modal.alternateName') }} -
+ {{ $t('pageCertificates.modal.alternateName') }}
+ -
<span class="form-text d-inline">
{{ $t('global.form.optional') }}
</span>
@@ -218,7 +222,8 @@
data-test-id="modalGenerateCsr-input-alternateName"
>
<template #add-button-text>
- <icon-add /> {{ $t('global.action.add') }}
+ <icon-add />
+ {{ $t('global.action.add') }}
</template>
</b-form-tags>
</b-form-group>
@@ -311,7 +316,7 @@
</b-row>
</b-container>
</b-form>
- <template #modal-footer="{ ok, cancel }">
+ <template #footer="{ ok, cancel }">
<b-button variant="secondary" @click="cancel()">
{{ $t('global.action.cancel') }}
</b-button>
@@ -328,13 +333,14 @@
</b-modal>
<b-modal
id="csr-string"
+ v-model="showCsrString"
no-stacking
size="lg"
:title="$t('pageCertificates.modal.certificateSigningRequest')"
@hidden="onHiddenCsrStringModal"
>
{{ csrString }}
- <template #modal-footer>
+ <template #footer>
<b-btn variant="secondary" @click="copyCsrString">
<template v-if="csrStringCopied">
<icon-checkmark />
@@ -375,6 +381,13 @@
name: 'ModalGenerateCsr',
components: { IconAdd, IconCheckmark },
mixins: [BVToastMixin, VuelidateMixin],
+ props: {
+ modelValue: {
+ type: Boolean,
+ default: false,
+ },
+ },
+ emits: ['update:modelValue'],
setup() {
return {
v$: useVuelidate(),
@@ -383,6 +396,7 @@
data() {
return {
$t: useI18n().t,
+ showCsrString: false,
form: {
certificateType: null,
country: null,
@@ -410,6 +424,14 @@
};
},
computed: {
+ isVisible: {
+ get() {
+ return this.modelValue;
+ },
+ set(value) {
+ this.$emit('update:modelValue', value);
+ },
+ },
certificateTypes() {
return this.$store.getters['certificates/certificateTypes'];
},
@@ -457,7 +479,7 @@
.dispatch('certificates/generateCsr', this.form)
.then(({ data: { CSRString } }) => {
this.csrString = CSRString;
- this.$bvModal.show('csr-string');
+ this.showCsrString = true;
this.v$.$reset();
});
},
diff --git a/src/views/SecurityAndAccess/Certificates/ModalUploadCertificate.vue b/src/views/SecurityAndAccess/Certificates/ModalUploadCertificate.vue
index 7a40bfb..927163e 100644
--- a/src/views/SecurityAndAccess/Certificates/ModalUploadCertificate.vue
+++ b/src/views/SecurityAndAccess/Certificates/ModalUploadCertificate.vue
@@ -1,18 +1,16 @@
<template>
- <b-modal id="upload-certificate" ref="modal" @ok="onOk" @hidden="resetForm">
- <template #modal-title>
- <template v-if="certificate">
- {{ $t('pageCertificates.replaceCertificate') }}
- </template>
- <template v-else>
- {{ $t('pageCertificates.addNewCertificate') }}
- </template>
- </template>
+ <b-modal
+ id="upload-certificate"
+ ref="modal"
+ :title="modalTitle"
+ @ok="onOk"
+ @hidden="resetForm"
+ >
<b-form>
<!-- Replace Certificate type -->
<template v-if="certificate !== null">
<dl class="mb-4">
- <dt>{{ $t('pageCertificates.modal.certificateType') }}</dt>
+ <dt>{{ i18n.t('pageCertificates.modal.certificateType') }}</dt>
<dd>{{ certificate.certificate }}</dd>
</dl>
</template>
@@ -20,7 +18,7 @@
<!-- Add new Certificate type -->
<template v-else>
<b-form-group
- :label="$t('pageCertificates.modal.certificateType')"
+ :label="i18n.t('pageCertificates.modal.certificateType')"
label-for="certificate-type"
>
<b-form-select
@@ -33,13 +31,13 @@
</b-form-select>
<b-form-invalid-feedback role="alert">
<template v-if="v$.form.certificateType.required.$invalid">
- {{ $t('global.form.fieldRequired') }}
+ {{ i18n.t('global.form.fieldRequired') }}
</template>
</b-form-invalid-feedback>
</b-form-group>
</template>
- <b-form-group :label="$t('pageCertificates.modal.certificateFile')">
+ <b-form-group :label="i18n.t('pageCertificates.modal.certificateFile')">
<form-file
id="certificate-file"
v-model="form.file"
@@ -48,7 +46,7 @@
>
<template #invalid>
<b-form-invalid-feedback role="alert">
- {{ $t('global.form.required') }}
+ {{ i18n.t('global.form.required') }}
</b-form-invalid-feedback>
</template>
</form-file>
@@ -56,14 +54,14 @@
</b-form>
<template #modal-ok>
<template v-if="certificate">
- {{ $t('global.action.replace') }}
+ {{ i18n.t('global.action.replace') }}
</template>
<template v-else>
- {{ $t('global.action.add') }}
+ {{ i18n.t('global.action.add') }}
</template>
</template>
<template #modal-cancel>
- {{ $t('global.action.cancel') }}
+ {{ i18n.t('global.action.cancel') }}
</template>
</b-modal>
</template>
@@ -94,13 +92,14 @@
},
emits: ['ok'],
setup() {
+ const i18n = useI18n();
return {
v$: useVuelidate(),
+ i18n,
};
},
data() {
return {
- $t: useI18n().t,
form: {
certificateType: null,
file: null,
@@ -108,6 +107,11 @@
};
},
computed: {
+ modalTitle() {
+ return this.certificate
+ ? this.i18n.t('pageCertificates.replaceCertificate')
+ : this.i18n.t('pageCertificates.addNewCertificate');
+ },
certificateTypes() {
return this.$store.getters['certificates/availableUploadTypes'];
},
diff --git a/src/views/SecurityAndAccess/Ldap/Ldap.vue b/src/views/SecurityAndAccess/Ldap/Ldap.vue
index 6de491a..a39b800 100644
--- a/src/views/SecurityAndAccess/Ldap/Ldap.vue
+++ b/src/views/SecurityAndAccess/Ldap/Ldap.vue
@@ -24,7 +24,7 @@
<b-form-group
class="m-0"
:label="$t('pageLdap.ariaLabel.ldapSettings')"
- label-class="sr-only"
+ label-class="visually-hidden-focusable"
:disabled="!form.ldapAuthenticationEnabled || loading"
>
<b-row>
@@ -50,12 +50,16 @@
</b-form-checkbox>
</b-form-group>
<dl>
- <dt>{{ $t('pageLdap.form.caCertificateValidUntil') }}</dt>
+ <dt>
+ {{ $t('pageLdap.form.caCertificateValidUntil') }}
+ </dt>
<dd v-if="caCertificateExpiration">
{{ $filters.formatDate(caCertificateExpiration) }}
</dd>
<dd v-else>--</dd>
- <dt>{{ $t('pageLdap.form.ldapCertificateValidUntil') }}</dt>
+ <dt>
+ {{ $t('pageLdap.form.ldapCertificateValidUntil') }}
+ </dt>
<dd v-if="ldapCertificateExpiration">
{{ $filters.formatDate(ldapCertificateExpiration) }}
</dd>
@@ -122,6 +126,7 @@
<b-form-input
id="bind-dn"
v-model="form.bindDn"
+ autocomplete="username"
data-test-id="ldap-input-bindDn"
:state="getValidationState(v$.form.bindDn)"
@change="v$.form.bindDn.$touch()"
@@ -143,6 +148,7 @@
id="bind-password"
v-model="form.bindPassword"
type="password"
+ autocomplete="current-password"
:state="getValidationState(v$.form.bindPassword)"
class="form-control-with-button"
@change="v$.form.bindPassword.$touch()"
@@ -173,7 +179,8 @@
<b-col sm="6" xl="4">
<b-form-group label-for="user-id-attribute">
<template #label>
- {{ $t('pageLdap.form.userIdAttribute') }} -
+ {{ $t('pageLdap.form.userIdAttribute') }}
+ -
<span class="form-text d-inline">
{{ $t('global.form.optional') }}
</span>
@@ -189,7 +196,8 @@
<b-col sm="6" xl="4">
<b-form-group label-for="group-id-attribute">
<template #label>
- {{ $t('pageLdap.form.groupIdAttribute') }} -
+ {{ $t('pageLdap.form.groupIdAttribute') }}
+ -
<span class="form-text d-inline">
{{ $t('global.form.optional') }}
</span>
diff --git a/src/views/SecurityAndAccess/Ldap/ModalAddRoleGroup.vue b/src/views/SecurityAndAccess/Ldap/ModalAddRoleGroup.vue
index f4dcb82..38ff473 100644
--- a/src/views/SecurityAndAccess/Ldap/ModalAddRoleGroup.vue
+++ b/src/views/SecurityAndAccess/Ldap/ModalAddRoleGroup.vue
@@ -1,13 +1,11 @@
<template>
- <b-modal id="modal-role-group" ref="modal" @ok="onOk" @hidden="resetForm">
- <template #modal-title>
- <template v-if="roleGroup">
- {{ $t('pageLdap.modal.editRoleGroup') }}
- </template>
- <template v-else>
- {{ $t('pageLdap.modal.addNewRoleGroup') }}
- </template>
- </template>
+ <b-modal
+ id="modal-role-group"
+ ref="modal"
+ :title="modalTitle"
+ @ok="onOk"
+ @hidden="resetForm"
+ >
<b-container>
<b-row>
<b-col sm="8">
@@ -15,7 +13,7 @@
<!-- Edit role group -->
<template v-if="roleGroup !== null">
<dl class="mb-4">
- <dt>{{ $t('pageLdap.modal.groupName') }}</dt>
+ <dt>{{ i18n.t('pageLdap.modal.groupName') }}</dt>
<dd style="word-break: break-all">{{ form.groupName }}</dd>
</dl>
</template>
@@ -23,7 +21,7 @@
<!-- Add new role group -->
<template v-else>
<b-form-group
- :label="$t('pageLdap.modal.groupName')"
+ :label="i18n.t('pageLdap.modal.groupName')"
label-for="role-group-name"
>
<b-form-input
@@ -33,13 +31,13 @@
@input="v$.form.groupName.$touch()"
/>
<b-form-invalid-feedback role="alert">
- {{ $t('global.form.fieldRequired') }}
+ {{ i18n.t('global.form.fieldRequired') }}
</b-form-invalid-feedback>
</b-form-group>
</template>
<b-form-group
- :label="$t('pageLdap.modal.groupPrivilege')"
+ :label="i18n.t('pageLdap.modal.groupPrivilege')"
label-for="privilege"
>
<b-form-select
@@ -51,28 +49,28 @@
>
<template v-if="!roleGroup" #first>
<b-form-select-option :value="null" disabled>
- {{ $t('global.form.selectAnOption') }}
+ {{ i18n.t('global.form.selectAnOption') }}
</b-form-select-option>
</template>
</b-form-select>
<b-form-invalid-feedback role="alert">
- {{ $t('global.form.fieldRequired') }}
+ {{ i18n.t('global.form.fieldRequired') }}
</b-form-invalid-feedback>
</b-form-group>
</b-form>
</b-col>
</b-row>
</b-container>
- <template #modal-footer="{ cancel }">
+ <template #footer="{ cancel }">
<b-button variant="secondary" @click="cancel()">
- {{ $t('global.action.cancel') }}
+ {{ i18n.t('global.action.cancel') }}
</b-button>
<b-button form="role-group" type="submit" variant="primary" @click="onOk">
<template v-if="roleGroup">
- {{ $t('global.action.save') }}
+ {{ i18n.t('global.action.save') }}
</template>
<template v-else>
- {{ $t('global.action.add') }}
+ {{ i18n.t('global.action.add') }}
</template>
</b-button>
</template>
@@ -102,13 +100,14 @@
},
emits: ['ok', 'hidden'],
setup() {
+ const i18n = useI18n();
return {
v$: useVuelidate(),
+ i18n,
};
},
data() {
return {
- $t: useI18n().t,
form: {
groupName: null,
groupPrivilege: null,
@@ -116,6 +115,11 @@
};
},
computed: {
+ modalTitle() {
+ return this.roleGroup
+ ? this.i18n.t('pageLdap.modal.editRoleGroup')
+ : this.i18n.t('pageLdap.modal.addNewRoleGroup');
+ },
accountRoles() {
return this.$store.getters['userManagement/accountRoles'];
},
diff --git a/src/views/SecurityAndAccess/Ldap/TableRoleGroups.vue b/src/views/SecurityAndAccess/Ldap/TableRoleGroups.vue
index f73d927..8b8b3ac 100644
--- a/src/views/SecurityAndAccess/Ldap/TableRoleGroups.vue
+++ b/src/views/SecurityAndAccess/Ldap/TableRoleGroups.vue
@@ -8,7 +8,7 @@
</b-col>
</b-row>
<b-row>
- <b-col class="text-right" md="9">
+ <b-col class="text-end" md="9">
<b-btn
variant="primary"
:disabled="!isServiceEnabled"
@@ -23,7 +23,9 @@
<b-col md="9">
<table-toolbar
ref="toolbar"
- :selected-items-count="selectedRows.length"
+ :selected-items-count="
+ Array.isArray(selectedRows) ? selectedRows.length : 0
+ "
:actions="batchActions"
@clear-selected="clearSelectedRows($refs.table)"
@batch-action="onBatchAction"
@@ -35,8 +37,9 @@
show-empty
no-select-on-click
hover
- no-sort-reset
+ must-sort
sort-icon-left
+ thead-class="table-light"
:busy="isBusy"
:items="tableItems"
:fields="fields"
@@ -49,9 +52,11 @@
v-model="tableHeaderCheckboxModel"
:indeterminate="tableHeaderCheckboxIndeterminate"
:disabled="!isServiceEnabled"
- @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">
@@ -60,7 +65,9 @@
:disabled="!isServiceEnabled"
@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>
@@ -84,6 +91,7 @@
</b-col>
</b-row>
<modal-add-role-group
+ v-model="showRoleGroupModal"
:role-group="activeRoleGroup"
@ok="saveRoleGroup"
@hidden="activeRoleGroup = null"
@@ -110,6 +118,7 @@
import LoadingBarMixin from '@/components/Mixins/LoadingBarMixin';
import { useI18n } from 'vue-i18n';
import i18n from '@/i18n';
+import { useModal } from 'bootstrap-vue-next';
export default {
components: {
@@ -122,11 +131,16 @@
TableToolbar,
},
mixins: [BVTableSelectableMixin, BVToastMixin, LoadingBarMixin],
+ setup() {
+ const bvModal = useModal();
+ return { bvModal };
+ },
data() {
return {
$t: useI18n().t,
isBusy: true,
activeRoleGroup: null,
+ showRoleGroupModal: false,
fields: [
{
key: 'checkbox',
@@ -146,7 +160,7 @@
key: 'actions',
sortable: false,
label: '',
- tdClass: 'text-right',
+ tdClass: 'text-end',
},
],
batchActions: [
@@ -190,31 +204,30 @@
},
methods: {
onBatchAction() {
- this.$bvModal
- .msgBoxConfirm(
- i18n.global.t(
- 'pageLdap.modal.deleteRoleGroupBatchConfirmMessage',
- this.selectedRows.length,
- ),
- {
- title: i18n.global.t('pageLdap.modal.deleteRoleGroup'),
- okTitle: i18n.global.t('global.action.delete'),
- cancelTitle: i18n.global.t('global.action.cancel'),
- autoFocusButton: 'ok',
- },
- )
- .then((deleteConfirmed) => {
- if (deleteConfirmed) {
- this.startLoader();
- this.$store
- .dispatch('ldap/deleteRoleGroup', {
- roleGroups: this.selectedRows,
- })
- .then((success) => this.successToast(success))
- .catch(({ message }) => this.errorToast(message))
- .finally(() => this.endLoader());
- }
- });
+ const count = this.selectedRows.length;
+ this.confirmDialog(
+ i18n.global.t(
+ 'pageLdap.modal.deleteRoleGroupBatchConfirmMessage',
+ count,
+ ),
+ {
+ title: i18n.global.t('pageLdap.modal.deleteRoleGroup'),
+ okTitle: i18n.global.t('global.action.delete'),
+ cancelTitle: i18n.global.t('global.action.cancel'),
+ autoFocusButton: 'ok',
+ },
+ ).then((deleteConfirmed) => {
+ if (deleteConfirmed) {
+ this.startLoader();
+ this.$store
+ .dispatch('ldap/deleteRoleGroup', {
+ roleGroups: this.selectedRows,
+ })
+ .then((success) => this.successToast(success))
+ .catch(({ message }) => this.errorToast(message))
+ .finally(() => this.endLoader());
+ }
+ });
},
onTableRowAction(action, row) {
switch (action) {
@@ -222,34 +235,37 @@
this.initRoleGroupModal(row);
break;
case 'delete':
- this.$bvModal
- .msgBoxConfirm(
- i18n.global.t('pageLdap.modal.deleteRoleGroupConfirmMessage', {
- groupName: row.groupName,
- }),
- {
- title: i18n.global.t('pageLdap.modal.deleteRoleGroup'),
- okTitle: i18n.global.t('global.action.delete'),
- cancelTitle: i18n.global.t('global.action.cancel'),
- autoFocusButton: 'ok',
- },
- )
- .then((deleteConfirmed) => {
- if (deleteConfirmed) {
- this.startLoader();
- this.$store
- .dispatch('ldap/deleteRoleGroup', { roleGroups: [row] })
- .then((success) => this.successToast(success))
- .catch(({ message }) => this.errorToast(message))
- .finally(() => this.endLoader());
- }
- });
+ this.confirmDialog(
+ i18n.global.t('pageLdap.modal.deleteRoleGroupConfirmMessage', {
+ groupName: row.groupName,
+ }),
+ {
+ title: i18n.global.t('pageLdap.modal.deleteRoleGroup'),
+ okTitle: i18n.global.t('global.action.delete'),
+ cancelTitle: i18n.global.t('global.action.cancel'),
+ autoFocusButton: 'ok',
+ },
+ ).then((deleteConfirmed) => {
+ if (deleteConfirmed) {
+ this.startLoader();
+ this.$store
+ .dispatch('ldap/deleteRoleGroup', {
+ roleGroups: [row],
+ })
+ .then((success) => this.successToast(success))
+ .catch(({ message }) => this.errorToast(message))
+ .finally(() => this.endLoader());
+ }
+ });
break;
}
},
+ confirmDialog(message, options = {}) {
+ return this.$confirm({ message, ...options });
+ },
initRoleGroupModal(roleGroup) {
this.activeRoleGroup = roleGroup;
- this.$bvModal.show('modal-role-group');
+ this.showRoleGroupModal = true;
},
saveRoleGroup({ addNew, groupName, groupPrivilege }) {
this.activeRoleGroup = null;
diff --git a/src/views/SecurityAndAccess/Policies/Policies.vue b/src/views/SecurityAndAccess/Policies/Policies.vue
index 58d8363..b9a4323 100644
--- a/src/views/SecurityAndAccess/Policies/Policies.vue
+++ b/src/views/SecurityAndAccess/Policies/Policies.vue
@@ -5,7 +5,7 @@
<b-col md="8">
<b-row v-if="!modifySSHPolicyDisabled" class="setting-section">
<b-col class="d-flex align-items-center justify-content-between">
- <dl class="mr-3 w-75">
+ <dl class="me-3 w-75">
<dt>{{ $t('pagePolicies.ssh') }}</dt>
<dd>
{{ $t('pagePolicies.sshDescription') }}
@@ -18,7 +18,7 @@
switch
@change="changeSshProtocolState"
>
- <span class="sr-only">
+ <span class="visually-hidden-focusable">
{{ $t('pagePolicies.ssh') }}
</span>
<span v-if="sshProtocolState">
@@ -30,7 +30,7 @@
</b-row>
<b-row class="setting-section">
<b-col class="d-flex align-items-center justify-content-between">
- <dl class="mt-3 mr-3 w-75">
+ <dl class="mt-3 me-3 w-75">
<dt>{{ $t('pagePolicies.ipmi') }}</dt>
<dd>
{{ $t('pagePolicies.ipmiDescription') }}
@@ -43,7 +43,7 @@
switch
@change="changeIpmiProtocolState"
>
- <span class="sr-only">
+ <span class="visually-hidden-focusable">
{{ $t('pagePolicies.ipmi') }}
</span>
<span v-if="ipmiProtocolState">
@@ -55,7 +55,7 @@
</b-row>
<b-row class="setting-section">
<b-col class="d-flex align-items-center justify-content-between">
- <dl class="mt-3 mr-3 w-75">
+ <dl class="mt-3 me-3 w-75">
<dt>{{ $t('pagePolicies.vtpm') }}</dt>
<dd>
{{ $t('pagePolicies.vtpmDescription') }}
@@ -68,7 +68,7 @@
switch
@change="changeVtpmState"
>
- <span class="sr-only">
+ <span class="visually-hidden-focusable">
{{ $t('pagePolicies.vtpm') }}
</span>
<span v-if="vtpmState">
@@ -80,7 +80,7 @@
</b-row>
<b-row class="setting-section">
<b-col class="d-flex align-items-center justify-content-between">
- <dl class="mt-3 mr-3 w-75">
+ <dl class="mt-3 me-3 w-75">
<dt>{{ $t('pagePolicies.rtad') }}</dt>
<dd>
{{ $t('pagePolicies.rtadDescription') }}
@@ -93,7 +93,7 @@
switch
@change="changeRtadState"
>
- <span class="sr-only">
+ <span class="visually-hidden-focusable">
{{ $t('pagePolicies.rtad') }}
</span>
<span v-if="rtadState">
@@ -105,7 +105,7 @@
</b-row>
<b-row class="setting-section">
<b-col class="d-flex align-items-center justify-content-between">
- <dl class="mt-3 mr-3 w-75">
+ <dl class="mt-3 me-3 w-75">
<dt>{{ $t('pagePolicies.webSessionTimeOut') }}</dt>
<dd>
{{ $t('pagePolicies.webSessionTimeOutDescription') }}
@@ -117,7 +117,7 @@
id="session-timeout-options"
v-model="sessionTimeoutState"
:options="sessionTimeOutOptions"
- @change="saveSessionTimeoutValue"
+ @update:model-value="saveSessionTimeoutValue"
>
<template #first>
<b-form-select-option :value="null" disabled>
@@ -154,12 +154,30 @@
modifySSHPolicyDisabled:
process.env.VUE_APP_MODIFY_SSH_POLICY_DISABLED === 'true',
sessionTimeOutOptions: [
- { value: 1800, text: i18n.global.t('pagePolicies.options.30minutes') },
- { value: 3600, text: i18n.global.t('pagePolicies.options.1hour') },
- { value: 7200, text: i18n.global.t('pagePolicies.options.2hours') },
- { value: 14400, text: i18n.global.t('pagePolicies.options.4hours') },
- { value: 28800, text: i18n.global.t('pagePolicies.options.8hours') },
- { value: 86400, text: i18n.global.t('pagePolicies.options.1day') },
+ {
+ value: 1800,
+ text: i18n.global.t('pagePolicies.options.30minutes'),
+ },
+ {
+ value: 3600,
+ text: i18n.global.t('pagePolicies.options.1hour'),
+ },
+ {
+ value: 7200,
+ text: i18n.global.t('pagePolicies.options.2hours'),
+ },
+ {
+ value: 14400,
+ text: i18n.global.t('pagePolicies.options.4hours'),
+ },
+ {
+ value: 28800,
+ text: i18n.global.t('pagePolicies.options.8hours'),
+ },
+ {
+ value: 86400,
+ text: i18n.global.t('pagePolicies.options.1day'),
+ },
],
};
},
@@ -246,9 +264,9 @@
.then((message) => this.successToast(message))
.catch(({ message }) => this.errorToast(message));
},
- saveSessionTimeoutValue(sessionTimeoutState) {
+ saveSessionTimeoutValue(value) {
this.$store
- .dispatch('policies/saveSessionTimeoutValue', sessionTimeoutState)
+ .dispatch('policies/saveSessionTimeoutValue', value)
.then((message) => this.successToast(message))
.catch(({ message }) => this.errorToast(message));
},
@@ -258,7 +276,7 @@
<style lang="scss" scoped>
.setting-section {
- border-bottom: 1px solid gray('300');
+ border-bottom: 1px solid $gray-300;
}
.session-timeout {
align-self: center;
diff --git a/src/views/SecurityAndAccess/Sessions/Sessions.vue b/src/views/SecurityAndAccess/Sessions/Sessions.vue
index 74dcf74..474ac95 100644
--- a/src/views/SecurityAndAccess/Sessions/Sessions.vue
+++ b/src/views/SecurityAndAccess/Sessions/Sessions.vue
@@ -21,7 +21,9 @@
<b-col>
<table-toolbar
ref="toolbar"
- :selected-items-count="selectedRows.length"
+ :selected-items-count="
+ Array.isArray(selectedRows) ? selectedRows.length : 0
+ "
:actions="batchActions"
@clear-selected="clearSelectedRows($refs.table)"
@batch-action="onBatchAction"
@@ -35,7 +37,8 @@
no-select-on-click
hover
show-empty
- sort-by="sessionID"
+ thead-class="table-light"
+ :sort-by="['sessionID']"
:busy="isBusy"
:fields="fields"
:items="allConnections"
@@ -44,7 +47,7 @@
:per-page="perPage"
:current-page="currentPage"
@filtered="onFiltered"
- @row-selected="onRowSelected($event, allConnections.length)"
+ @row-selected="onRowSelected"
>
<!-- Checkbox column -->
<template #head(checkbox)>
@@ -52,9 +55,11 @@
v-model="tableHeaderCheckboxModel"
data-test-id="sessions-checkbox-selectAll"
: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">
@@ -63,7 +68,9 @@
:data-test-id="`sessions-checkbox-selectRow-${row.index}`"
@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>
@@ -126,17 +133,14 @@
perPage,
itemsPerPageOptions,
} from '@/components/Mixins/BVPaginationMixin';
-import BVTableSelectableMixin, {
- selectedRows,
- tableHeaderCheckboxModel,
- tableHeaderCheckboxIndeterminate,
-} from '@/components/Mixins/BVTableSelectableMixin';
+import BVTableSelectableMixin from '@/components/Mixins/BVTableSelectableMixin';
import BVToastMixin from '@/components/Mixins/BVToastMixin';
import SearchFilterMixin, {
searchFilter,
} from '@/components/Mixins/SearchFilterMixin';
import { useI18n } from 'vue-i18n';
import i18n from '@/i18n';
+import { useModal } from 'bootstrap-vue-next';
export default {
components: {
@@ -159,6 +163,10 @@
this.hideLoader();
next();
},
+ setup() {
+ const bvModal = useModal();
+ return { bvModal };
+ },
data() {
return {
$t: useI18n().t,
@@ -203,10 +211,7 @@
currentPage: currentPage,
itemsPerPageOptions: itemsPerPageOptions,
perPage: perPage,
- selectedRows: selectedRows,
searchTotalFilteredRows: 0,
- tableHeaderCheckboxModel: tableHeaderCheckboxModel,
- tableHeaderCheckboxIndeterminate: tableHeaderCheckboxIndeterminate,
searchFilter: searchFilter,
};
},
@@ -259,47 +264,41 @@
},
onTableRowAction(action, { uri }) {
if (action === 'disconnect') {
- this.$bvModal
- .msgBoxConfirm(
- i18n.global.t('pageSessions.modal.disconnectMessage'),
- {
- title: i18n.global.t('pageSessions.modal.disconnectTitle'),
- okTitle: i18n.global.t('pageSessions.action.disconnect'),
- cancelTitle: i18n.global.t('global.action.cancel'),
- autoFocusButton: 'ok',
- },
- )
- .then((deleteConfirmed) => {
- if (deleteConfirmed) this.disconnectSessions([uri]);
- });
+ this.confirmDialog(
+ i18n.global.t('pageSessions.modal.disconnectMessage'),
+ {
+ title: i18n.global.t('pageSessions.modal.disconnectTitle'),
+ okTitle: i18n.global.t('pageSessions.action.disconnect'),
+ cancelTitle: i18n.global.t('global.action.cancel'),
+ autoFocusButton: 'ok',
+ },
+ ).then((deleteConfirmed) => {
+ if (deleteConfirmed) this.disconnectSessions([uri]);
+ });
}
},
onBatchAction(action) {
if (action === 'disconnect') {
const uris = this.selectedRows.map((row) => row.uri);
- this.$bvModal
- .msgBoxConfirm(
- i18n.global.t(
- 'pageSessions.modal.disconnectMessage',
- this.selectedRows.length,
- ),
- {
- title: i18n.global.t(
- 'pageSessions.modal.disconnectTitle',
- this.selectedRows.length,
- ),
- okTitle: i18n.global.t('pageSessions.action.disconnect'),
- cancelTitle: i18n.global.t('global.action.cancel'),
- autoFocusButton: 'ok',
- },
- )
- .then((deleteConfirmed) => {
- if (deleteConfirmed) {
- this.disconnectSessions(uris);
- }
- });
+ const count = this.selectedRows.length;
+ this.confirmDialog(
+ i18n.global.t('pageSessions.modal.disconnectMessage', count),
+ {
+ title: i18n.global.t('pageSessions.modal.disconnectTitle', count),
+ okTitle: i18n.global.t('pageSessions.action.disconnect'),
+ cancelTitle: i18n.global.t('global.action.cancel'),
+ autoFocusButton: 'ok',
+ },
+ ).then((deleteConfirmed) => {
+ if (deleteConfirmed) {
+ this.disconnectSessions(uris);
+ }
+ });
}
},
+ confirmDialog(message, options = {}) {
+ return this.$confirm({ message, ...options });
+ },
},
};
</script>
diff --git a/src/views/SecurityAndAccess/UserManagement/ModalSettings.vue b/src/views/SecurityAndAccess/UserManagement/ModalSettings.vue
index a181ba7..ed5114a 100644
--- a/src/views/SecurityAndAccess/UserManagement/ModalSettings.vue
+++ b/src/views/SecurityAndAccess/UserManagement/ModalSettings.vue
@@ -100,7 +100,7 @@
</b-row>
</b-container>
</b-form>
- <template #modal-footer="{ cancel }">
+ <template #footer="{ cancel }">
<b-button
variant="secondary"
data-test-id="userManagement-button-cancel"
diff --git a/src/views/SecurityAndAccess/UserManagement/ModalUser.vue b/src/views/SecurityAndAccess/UserManagement/ModalUser.vue
index af8903e..e86a2ec 100644
--- a/src/views/SecurityAndAccess/UserManagement/ModalUser.vue
+++ b/src/views/SecurityAndAccess/UserManagement/ModalUser.vue
@@ -1,13 +1,5 @@
<template>
- <b-modal id="modal-user" ref="modal" @hidden="resetForm">
- <template #modal-title>
- <template v-if="newUser">
- {{ $t('pageUserManagement.addUser') }}
- </template>
- <template v-else>
- {{ $t('pageUserManagement.editUser') }}
- </template>
- </template>
+ <b-modal id="modal-user" ref="modal" :title="modalTitle" @hidden="resetForm">
<b-form id="form-user" novalidate @submit.prevent="handleSubmit">
<b-container>
<!-- Manual unlock form control -->
@@ -15,10 +7,12 @@
<b-col sm="9">
<alert :show="true" variant="warning" small>
<template v-if="!v$.form.manualUnlock.$dirty">
- {{ $t('pageUserManagement.modal.accountLocked') }}
+ {{ i18n.t('pageUserManagement.modal.accountLocked') }}
</template>
<template v-else>
- {{ $t('pageUserManagement.modal.clickSaveToUnlockAccount') }}
+ {{
+ i18n.t('pageUserManagement.modal.clickSaveToUnlockAccount')
+ }}
</template>
</alert>
</b-col>
@@ -34,13 +28,15 @@
data-test-id="userManagement-button-manualUnlock"
@click="v$.form.manualUnlock.$touch()"
>
- {{ $t('pageUserManagement.modal.unlock') }}
+ {{ i18n.t('pageUserManagement.modal.unlock') }}
</b-button>
</b-col>
</b-row>
<b-row>
<b-col>
- <b-form-group :label="$t('pageUserManagement.modal.accountStatus')">
+ <b-form-group
+ :label="i18n.t('pageUserManagement.modal.accountStatus')"
+ >
<b-form-radio
v-model="form.status"
name="user-status"
@@ -48,7 +44,7 @@
data-test-id="userManagement-radioButton-statusEnabled"
@input="v$.form.status.$touch()"
>
- {{ $t('global.status.enabled') }}
+ {{ i18n.t('global.status.enabled') }}
</b-form-radio>
<b-form-radio
v-model="form.status"
@@ -58,18 +54,18 @@
:disabled="!newUser && originalUsername === disabled"
@input="v$.form.status.$touch()"
>
- {{ $t('global.status.disabled') }}
+ {{ i18n.t('global.status.disabled') }}
</b-form-radio>
</b-form-group>
<b-form-group
- :label="$t('pageUserManagement.modal.username')"
+ :label="i18n.t('pageUserManagement.modal.username')"
label-for="username"
>
<b-form-text id="username-help-block">
- {{ $t('pageUserManagement.modal.cannotStartWithANumber') }}
+ {{ i18n.t('pageUserManagement.modal.cannotStartWithANumber') }}
<br />
{{
- $t(
+ i18n.t(
'pageUserManagement.modal.noSpecialCharactersExceptUnderscore',
)
}}
@@ -86,20 +82,23 @@
/>
<b-form-invalid-feedback role="alert">
<template v-if="v$.form.username.required.$invalid">
- {{ $t('global.form.fieldRequired') }}
+ {{ i18n.t('global.form.fieldRequired') }}
</template>
<template v-else-if="v$.form.username.maxLength.$invalid">
{{
- $t('global.form.lengthMustBeBetween', { min: 1, max: 16 })
+ i18n.t('global.form.lengthMustBeBetween', {
+ min: 1,
+ max: 16,
+ })
}}
</template>
<template v-else-if="v$.form.username.pattern.$invalid">
- {{ $t('global.form.invalidFormat') }}
+ {{ i18n.t('global.form.invalidFormat') }}
</template>
</b-form-invalid-feedback>
</b-form-group>
<b-form-group
- :label="$t('pageUserManagement.modal.privilege')"
+ :label="i18n.t('pageUserManagement.modal.privilege')"
label-for="privilege"
>
<b-form-select
@@ -113,25 +112,25 @@
>
<template #first>
<b-form-select-option :value="null" disabled>
- {{ $t('global.form.selectAnOption') }}
+ {{ i18n.t('global.form.selectAnOption') }}
</b-form-select-option>
</template>
</b-form-select>
<b-form-invalid-feedback role="alert">
<template v-if="v$.form.privilege.required.$invalid">
- {{ $t('global.form.fieldRequired') }}
+ {{ i18n.t('global.form.fieldRequired') }}
</template>
</b-form-invalid-feedback>
</b-form-group>
</b-col>
<b-col>
<b-form-group
- :label="$t('pageUserManagement.modal.userPassword')"
+ :label="i18n.t('pageUserManagement.modal.userPassword')"
label-for="password"
>
<b-form-text id="password-help-block">
{{
- $t('pageUserManagement.modal.passwordMustBeBetween', {
+ i18n.t('pageUserManagement.modal.passwordMustBeBetween', {
min: passwordRequirements.minLength,
max: passwordRequirements.maxLength,
})
@@ -142,6 +141,7 @@
id="password"
v-model="form.password"
type="password"
+ autocomplete="new-password"
data-test-id="userManagement-input-password"
aria-describedby="password-help-block"
:state="getValidationState(v$.form.password)"
@@ -150,7 +150,7 @@
/>
<b-form-invalid-feedback role="alert">
<template v-if="v$.form.password.required.$invalid">
- {{ $t('global.form.fieldRequired') }}
+ {{ i18n.t('global.form.fieldRequired') }}
</template>
<template
v-if="
@@ -159,7 +159,7 @@
"
>
{{
- $t('pageUserManagement.modal.passwordMustBeBetween', {
+ i18n.t('pageUserManagement.modal.passwordMustBeBetween', {
min: passwordRequirements.minLength,
max: passwordRequirements.maxLength,
})
@@ -169,7 +169,7 @@
</input-password-toggle>
</b-form-group>
<b-form-group
- :label="$t('pageUserManagement.modal.confirmUserPassword')"
+ :label="i18n.t('pageUserManagement.modal.confirmUserPassword')"
label-for="password-confirmation"
>
<input-password-toggle>
@@ -178,6 +178,7 @@
v-model="form.passwordConfirmation"
data-test-id="userManagement-input-passwordConfirmation"
type="password"
+ autocomplete="new-password"
:state="getValidationState(v$.form.passwordConfirmation)"
class="form-control-with-button"
@input="v$.form.passwordConfirmation.$touch()"
@@ -186,14 +187,14 @@
<template
v-if="v$.form.passwordConfirmation.required.$invalid"
>
- {{ $t('global.form.fieldRequired') }}
+ {{ i18n.t('global.form.fieldRequired') }}
</template>
<template
v-else-if="
v$.form.passwordConfirmation.sameAsPassword.$invalid
"
>
- {{ $t('pageUserManagement.modal.passwordsDoNotMatch') }}
+ {{ i18n.t('pageUserManagement.modal.passwordsDoNotMatch') }}
</template>
</b-form-invalid-feedback>
</input-password-toggle>
@@ -202,13 +203,13 @@
</b-row>
</b-container>
</b-form>
- <template #modal-footer="{ cancel }">
+ <template #footer="{ cancel }">
<b-button
variant="secondary"
data-test-id="userManagement-button-cancel"
@click="cancel()"
>
- {{ $t('global.action.cancel') }}
+ {{ i18n.t('global.action.cancel') }}
</b-button>
<b-button
form="form-user"
@@ -218,10 +219,10 @@
@click="onOk"
>
<template v-if="newUser">
- {{ $t('pageUserManagement.addUser') }}
+ {{ i18n.t('pageUserManagement.addUser') }}
</template>
<template v-else>
- {{ $t('global.action.save') }}
+ {{ i18n.t('global.action.save') }}
</template>
</b-button>
</template>
@@ -258,13 +259,14 @@
},
emits: ['ok', 'hidden'],
setup() {
+ const i18n = useI18n();
return {
v$: useVuelidate(),
+ i18n,
};
},
data() {
return {
- $t: useI18n().t,
originalUsername: '',
form: {
status: true,
@@ -278,6 +280,11 @@
};
},
computed: {
+ modalTitle() {
+ return this.newUser
+ ? this.i18n.t('pageUserManagement.addUser')
+ : this.i18n.t('pageUserManagement.editUser');
+ },
newUser() {
return this.user ? false : true;
},
diff --git a/src/views/SecurityAndAccess/UserManagement/TableRoles.vue b/src/views/SecurityAndAccess/UserManagement/TableRoles.vue
index 7e9faef..03d0088 100644
--- a/src/views/SecurityAndAccess/UserManagement/TableRoles.vue
+++ b/src/views/SecurityAndAccess/UserManagement/TableRoles.vue
@@ -1,5 +1,12 @@
<template>
- <b-table stacked="sm" hover small :items="items" :fields="fields">
+ <b-table
+ stacked="sm"
+ hover
+ small
+ thead-class="table-light"
+ :items="items"
+ :fields="fields"
+ >
<template #cell(administrator)="data">
<template v-if="data.value">
<checkmark20 />
diff --git a/src/views/SecurityAndAccess/UserManagement/UserManagement.vue b/src/views/SecurityAndAccess/UserManagement/UserManagement.vue
index cb057a7..e59ffbc 100644
--- a/src/views/SecurityAndAccess/UserManagement/UserManagement.vue
+++ b/src/views/SecurityAndAccess/UserManagement/UserManagement.vue
@@ -2,7 +2,7 @@
<b-container fluid="xl">
<page-title />
<b-row>
- <b-col xl="9" class="text-right">
+ <b-col xl="9" class="text-end">
<b-button variant="link" @click="initModalSettings">
<icon-settings />
{{ $t('pageUserManagement.accountPolicySettings') }}
@@ -21,7 +21,9 @@
<b-col xl="9">
<table-toolbar
ref="toolbar"
- :selected-items-count="selectedRows.length"
+ :selected-items-count="
+ Array.isArray(selectedRows) ? selectedRows.length : 0
+ "
:actions="tableToolbarActions"
@clear-selected="clearSelectedRows($refs.table)"
@batch-action="onBatchAction"
@@ -33,6 +35,7 @@
show-empty
no-select-on-click
hover
+ thead-class="table-light"
:busy="isBusy"
:fields="fields"
:items="tableItems"
@@ -45,9 +48,11 @@
v-model="tableHeaderCheckboxModel"
data-test-id="userManagement-checkbox-tableHeaderCheckbox"
: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">
@@ -56,7 +61,9 @@
data-test-id="userManagement-checkbox-toggleSelectRow"
@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>
@@ -88,22 +95,27 @@
<b-row>
<b-col xl="8">
<b-button
- v-b-toggle.collapse-role-table
data-test-id="userManagement-button-viewPrivilegeRoleDescriptions"
variant="link"
class="mt-3"
+ @click="showRoles = !showRoles"
>
<icon-chevron />
{{ $t('pageUserManagement.viewPrivilegeRoleDescriptions') }}
</b-button>
- <b-collapse id="collapse-role-table" class="mt-3">
+ <b-collapse id="collapse-role-table" :visible="showRoles" class="mt-3">
<table-roles />
</b-collapse>
</b-col>
</b-row>
<!-- Modals -->
- <modal-settings :settings="setting" @ok="saveAccountSettings" />
+ <modal-settings
+ v-model="showSettingsModal"
+ :settings="setting"
+ @ok="saveAccountSettings"
+ />
<modal-user
+ v-model="showUserModal"
:user="activeUser"
:password-requirements="passwordRequirements"
@ok="saveUser"
@@ -135,6 +147,7 @@
import LoadingBarMixin from '@/components/Mixins/LoadingBarMixin';
import { useI18n } from 'vue-i18n';
import i18n from '@/i18n';
+import { useModal } from 'bootstrap-vue-next';
export default {
name: 'UserManagement',
@@ -156,6 +169,10 @@
this.hideLoader();
next();
},
+ setup() {
+ const bvModal = useModal();
+ return { bvModal };
+ },
data() {
return {
$t: useI18n().t,
@@ -181,7 +198,7 @@
{
key: 'actions',
label: '',
- tdClass: 'text-right text-nowrap',
+ tdClass: 'text-end text-nowrap',
},
],
tableToolbarActions: [
@@ -201,6 +218,9 @@
selectedRows: selectedRows,
tableHeaderCheckboxModel: tableHeaderCheckboxModel,
tableHeaderCheckboxIndeterminate: tableHeaderCheckboxIndeterminate,
+ showUserModal: false,
+ showSettingsModal: false,
+ showRoles: false,
};
},
computed: {
@@ -265,30 +285,28 @@
},
initModalUser(user) {
this.activeUser = user;
- this.$bvModal.show('modal-user');
+ this.showUserModal = true;
},
initModalDelete(user) {
- this.$bvModal
- .msgBoxConfirm(
- i18n.global.t('pageUserManagement.modal.deleteConfirmMessage', {
- user: user.username,
- }),
- {
- title: i18n.global.t('pageUserManagement.deleteUser'),
- okTitle: i18n.global.t('pageUserManagement.deleteUser'),
- cancelTitle: i18n.global.t('global.action.cancel'),
- autoFocusButton: 'ok',
- },
- )
- .then((deleteConfirmed) => {
- if (deleteConfirmed) {
- this.deleteUser(user);
- }
- });
+ this.confirmDialog(
+ i18n.global.t('pageUserManagement.modal.deleteConfirmMessage', {
+ user: user.username,
+ }),
+ {
+ title: i18n.global.t('pageUserManagement.deleteUser'),
+ okTitle: i18n.global.t('pageUserManagement.deleteUser'),
+ cancelTitle: i18n.global.t('global.action.cancel'),
+ autoFocusButton: 'ok',
+ },
+ ).then((deleteConfirmed) => {
+ if (deleteConfirmed) {
+ this.deleteUser(user);
+ }
+ });
},
initModalSettings() {
this.setting = this.settings;
- this.$bvModal.show('modal-settings');
+ this.showSettingsModal = true;
},
saveUser({ isNewUser, userData }) {
this.startLoader();
@@ -315,41 +333,34 @@
.finally(() => this.endLoader());
},
onBatchAction(action) {
+ const count = this.selectedRows.length;
switch (action) {
case 'delete':
- this.$bvModal
- .msgBoxConfirm(
- i18n.global.t(
- 'pageUserManagement.modal.batchDeleteConfirmMessage',
- this.selectedRows.length,
- ),
- {
- title: i18n.global.t(
- 'pageUserManagement.deleteUser',
- this.selectedRows.length,
- ),
- okTitle: i18n.global.t(
- 'pageUserManagement.deleteUser',
- this.selectedRows.length,
- ),
- cancelTitle: i18n.global.t('global.action.cancel'),
- autoFocusButton: 'ok',
- },
- )
- .then((deleteConfirmed) => {
- if (deleteConfirmed) {
- this.startLoader();
- this.$store
- .dispatch('userManagement/deleteUsers', this.selectedRows)
- .then((messages) => {
- messages.forEach(({ type, message }) => {
- if (type === 'success') this.successToast(message);
- if (type === 'error') this.errorToast(message);
- });
- })
- .finally(() => this.endLoader());
- }
- });
+ this.confirmDialog(
+ i18n.global.t(
+ 'pageUserManagement.modal.batchDeleteConfirmMessage',
+ count,
+ ),
+ {
+ title: i18n.global.t('pageUserManagement.deleteUser', count),
+ okTitle: i18n.global.t('pageUserManagement.deleteUser', count),
+ cancelTitle: i18n.global.t('global.action.cancel'),
+ autoFocusButton: 'ok',
+ },
+ ).then((deleteConfirmed) => {
+ if (deleteConfirmed) {
+ this.startLoader();
+ this.$store
+ .dispatch('userManagement/deleteUsers', this.selectedRows)
+ .then((messages) => {
+ messages.forEach(({ type, message }) => {
+ if (type === 'success') this.successToast(message);
+ if (type === 'error') this.errorToast(message);
+ });
+ })
+ .finally(() => this.endLoader());
+ }
+ });
break;
case 'enable':
this.startLoader();
@@ -364,42 +375,37 @@
.finally(() => this.endLoader());
break;
case 'disable':
- this.$bvModal
- .msgBoxConfirm(
- i18n.global.t(
- 'pageUserManagement.modal.batchDisableConfirmMessage',
- this.selectedRows.length,
- ),
- {
- title: i18n.global.t(
- 'pageUserManagement.disableUser',
- this.selectedRows.length,
- ),
- okTitle: i18n.global.t(
- 'pageUserManagement.disableUser',
- this.selectedRows.length,
- ),
- cancelTitle: i18n.global.t('global.action.cancel'),
- autoFocusButton: 'ok',
- },
- )
- .then((disableConfirmed) => {
- if (disableConfirmed) {
- this.startLoader();
- this.$store
- .dispatch('userManagement/disableUsers', this.selectedRows)
- .then((messages) => {
- messages.forEach(({ type, message }) => {
- if (type === 'success') this.successToast(message);
- if (type === 'error') this.errorToast(message);
- });
- })
- .finally(() => this.endLoader());
- }
- });
+ this.confirmDialog(
+ i18n.global.t(
+ 'pageUserManagement.modal.batchDisableConfirmMessage',
+ count,
+ ),
+ {
+ title: i18n.global.t('pageUserManagement.disableUser', count),
+ okTitle: i18n.global.t('pageUserManagement.disableUser', count),
+ cancelTitle: i18n.global.t('global.action.cancel'),
+ autoFocusButton: 'ok',
+ },
+ ).then((disableConfirmed) => {
+ if (disableConfirmed) {
+ this.startLoader();
+ this.$store
+ .dispatch('userManagement/disableUsers', this.selectedRows)
+ .then((messages) => {
+ messages.forEach(({ type, message }) => {
+ if (type === 'success') this.successToast(message);
+ if (type === 'error') this.errorToast(message);
+ });
+ })
+ .finally(() => this.endLoader());
+ }
+ });
break;
}
},
+ confirmDialog(message, options = {}) {
+ return this.$confirm({ message, ...options });
+ },
onTableRowAction(action, row) {
switch (action) {
case 'edit':
diff --git a/src/views/Settings/DateTime/DateTime.vue b/src/views/Settings/DateTime/DateTime.vue
index 0ddea67..7be9d18 100644
--- a/src/views/Settings/DateTime/DateTime.vue
+++ b/src/views/Settings/DateTime/DateTime.vue
@@ -18,14 +18,18 @@
<b-col lg="3">
<dl>
<dt>{{ $t('pageDateTime.form.date') }}</dt>
- <dd v-if="bmcTime">{{ $filters.formatDate(bmcTime) }}</dd>
+ <dd v-if="bmcTime">
+ {{ $filters.formatDate(bmcTime) }}
+ </dd>
<dd v-else>--</dd>
</dl>
</b-col>
<b-col lg="3">
<dl>
<dt>{{ $t('pageDateTime.form.time.label') }}</dt>
- <dd v-if="bmcTime">{{ $filters.formatTime(bmcTime) }}</dd>
+ <dd v-if="bmcTime">
+ {{ $filters.formatTime(bmcTime) }}
+ </dd>
<dd v-else>--</dd>
</dl>
</b-col>
@@ -36,7 +40,7 @@
<b-form-group
label="Configure date and time"
:disabled="loading"
- label-sr-only
+ label-class="visually-hidden"
>
<b-form-radio
v-model="form.configurationSelected"
@@ -45,7 +49,7 @@
>
{{ $t('pageDateTime.form.manual') }}
</b-form-radio>
- <b-row class="mt-3 ml-3">
+ <b-row class="mt-3 ms-3">
<b-col sm="6" lg="4" xl="3">
<b-form-group
:label="$t('pageDateTime.form.date')"
@@ -70,34 +74,16 @@
{{ $t('global.form.fieldRequired') }}
</div>
</b-form-invalid-feedback>
- <b-form-datepicker
- v-model="form.manual.date"
- class="btn-datepicker btn-icon-only"
- button-only
- right
- :hide-header="true"
- :locale="locale"
- :label-help="
- $t('global.calendar.useCursorKeysToNavigateCalendarDates')
- "
- :title="$t('global.calendar.selectDate')"
- :disabled="ntpOptionSelected"
- button-variant="link"
- aria-controls="input-manual-date"
- >
- <template #button-content>
- <icon-calendar />
- <span class="sr-only">
- {{ $t('global.calendar.selectDate') }}
- </span>
- </template>
- </b-form-datepicker>
</b-input-group>
</b-form-group>
</b-col>
<b-col sm="6" lg="4" xl="3">
<b-form-group
- :label="$t('pageDateTime.form.time.timezone', { timezone })"
+ :label="
+ $t('pageDateTime.form.time.timezone', {
+ timezone,
+ })
+ "
label-for="input-manual-time"
>
<b-form-text id="time-format-help">HH:MM</b-form-text>
@@ -129,7 +115,7 @@
>
NTP
</b-form-radio>
- <b-row class="mt-3 ml-3">
+ <b-row class="mt-3 ms-3">
<b-col sm="6" lg="4" xl="3">
<b-form-group
:label="$t('pageDateTime.form.ntpServers.server1')"
@@ -198,7 +184,6 @@
<script>
import Alert from '@/components/Global/Alert';
-import IconCalendar from '@carbon/icons-vue/es/calendar/20';
import PageTitle from '@/components/Global/PageTitle';
import PageSection from '@/components/Global/PageSection';
@@ -218,7 +203,7 @@
export default {
name: 'DateTime',
- components: { Alert, IconCalendar, PageTitle, PageSection },
+ components: { Alert, PageTitle, PageSection },
mixins: [
BVToastMixin,
LoadingBarMixin,
diff --git a/src/views/Settings/Network/ModalDefaultGateway.vue b/src/views/Settings/Network/ModalDefaultGateway.vue
index d1fa60f..1ac6f14 100644
--- a/src/views/Settings/Network/ModalDefaultGateway.vue
+++ b/src/views/Settings/Network/ModalDefaultGateway.vue
@@ -32,7 +32,7 @@
</b-col>
</b-row>
</b-form>
- <template #modal-footer="{ cancel }">
+ <template #footer="{ cancel }">
<b-button variant="secondary" @click="cancel()">
{{ $t('global.action.cancel') }}
</b-button>
@@ -63,12 +63,16 @@
export default {
mixins: [VuelidateMixin],
props: {
+ modelValue: {
+ type: Boolean,
+ default: false,
+ },
defaultGateway: {
type: String,
default: '',
},
},
- emits: ['ok', 'hidden'],
+ emits: ['ok', 'hidden', 'update:modelValue'],
setup() {
return {
v$: useVuelidate(),
@@ -86,6 +90,16 @@
defaultGateway() {
this.form.defaultGateway = this.defaultGateway;
},
+ modelValue: {
+ handler(newValue) {
+ if (newValue) {
+ this.$nextTick(() => {
+ this.$refs.modal?.show();
+ });
+ }
+ },
+ immediate: true,
+ },
},
validations() {
return {
@@ -112,6 +126,7 @@
resetForm() {
this.form.defaultGateway = this.defaultGateway;
this.v$.$reset();
+ this.$emit('update:modelValue', false);
this.$emit('hidden');
},
onOk(bvModalEvt) {
diff --git a/src/views/Settings/Network/ModalDns.vue b/src/views/Settings/Network/ModalDns.vue
index 39308ce..d9e9830 100644
--- a/src/views/Settings/Network/ModalDns.vue
+++ b/src/views/Settings/Network/ModalDns.vue
@@ -31,7 +31,7 @@
</b-col>
</b-row>
</b-form>
- <template #modal-footer="{ cancel }">
+ <template #footer="{ cancel }">
<b-button variant="secondary" @click="cancel()">
{{ $t('global.action.cancel') }}
</b-button>
@@ -51,7 +51,13 @@
export default {
mixins: [VuelidateMixin],
- emits: ['ok', 'hidden'],
+ props: {
+ modelValue: {
+ type: Boolean,
+ default: false,
+ },
+ },
+ emits: ['ok', 'hidden', 'update:modelValue'],
setup() {
return {
v$: useVuelidate(),
@@ -75,6 +81,18 @@
},
};
},
+ watch: {
+ modelValue: {
+ handler(newValue) {
+ if (newValue) {
+ this.$nextTick(() => {
+ this.$refs.modal?.show();
+ });
+ }
+ },
+ immediate: true,
+ },
+ },
methods: {
handleSubmit() {
this.v$.$touch();
@@ -90,6 +108,7 @@
resetForm() {
this.form.staticDns = null;
this.v$.$reset();
+ this.$emit('update:modelValue', false);
this.$emit('hidden');
},
onOk(bvModalEvt) {
diff --git a/src/views/Settings/Network/ModalHostname.vue b/src/views/Settings/Network/ModalHostname.vue
index eb20f17..19ef61c 100644
--- a/src/views/Settings/Network/ModalHostname.vue
+++ b/src/views/Settings/Network/ModalHostname.vue
@@ -31,7 +31,7 @@
</b-col>
</b-row>
</b-form>
- <template #modal-footer="{ cancel }">
+ <template #footer="{ cancel }">
<b-button variant="secondary" @click="cancel()">
{{ $t('global.action.cancel') }}
</b-button>
@@ -59,12 +59,16 @@
export default {
mixins: [VuelidateMixin],
props: {
+ modelValue: {
+ type: Boolean,
+ default: false,
+ },
hostname: {
type: String,
default: '',
},
},
- emits: ['ok', 'hidden'],
+ emits: ['ok', 'hidden', 'update:modelValue'],
setup() {
return {
v$: useVuelidate(),
@@ -82,6 +86,16 @@
hostname() {
this.form.hostname = this.hostname;
},
+ modelValue: {
+ handler(newValue) {
+ if (newValue) {
+ this.$nextTick(() => {
+ this.$refs.modal?.show();
+ });
+ }
+ },
+ immediate: true,
+ },
},
validations() {
return {
@@ -108,6 +122,7 @@
resetForm() {
this.form.hostname = this.hostname;
this.v$.$reset();
+ this.$emit('update:modelValue', false);
this.$emit('hidden');
},
onOk(bvModalEvt) {
diff --git a/src/views/Settings/Network/ModalIpv4.vue b/src/views/Settings/Network/ModalIpv4.vue
index e72179a..45bd411 100644
--- a/src/views/Settings/Network/ModalIpv4.vue
+++ b/src/views/Settings/Network/ModalIpv4.vue
@@ -77,7 +77,7 @@
</b-col>
</b-row>
</b-form>
- <template #modal-footer="{ cancel }">
+ <template #footer="{ cancel }">
<b-button variant="secondary" @click="cancel()">
{{ $t('global.action.cancel') }}
</b-button>
@@ -98,12 +98,16 @@
export default {
mixins: [VuelidateMixin],
props: {
+ modelValue: {
+ type: Boolean,
+ default: false,
+ },
defaultGateway: {
type: String,
default: '',
},
},
- emits: ['ok', 'hidden'],
+ emits: ['ok', 'hidden', 'update:modelValue'],
setup() {
return {
v$: useVuelidate(),
@@ -123,6 +127,16 @@
defaultGateway() {
this.form.gateway = this.defaultGateway;
},
+ modelValue: {
+ handler(newValue) {
+ if (newValue) {
+ this.$nextTick(() => {
+ this.$refs.modal?.show();
+ });
+ }
+ },
+ immediate: true,
+ },
},
validations() {
return {
@@ -143,6 +157,12 @@
};
},
methods: {
+ show() {
+ this.$refs.modal?.show();
+ },
+ hide() {
+ this.$refs.modal?.hide();
+ },
handleSubmit() {
this.v$.$touch();
if (this.v$.$invalid) return;
@@ -163,6 +183,7 @@
this.form.gateway = this.defaultGateway;
this.form.subnetMask = null;
this.v$.$reset();
+ this.$emit('update:modelValue', false);
this.$emit('hidden');
},
onOk(bvModalEvt) {
diff --git a/src/views/Settings/Network/ModalIpv6.vue b/src/views/Settings/Network/ModalIpv6.vue
index 6f844ce..358f34f 100644
--- a/src/views/Settings/Network/ModalIpv6.vue
+++ b/src/views/Settings/Network/ModalIpv6.vue
@@ -55,7 +55,7 @@
</b-col>
</b-row>
</b-form>
- <template #modal-footer="{ cancel }">
+ <template #footer="{ cancel }">
<b-button variant="secondary" @click="cancel()">
{{ $t('global.action.cancel') }}
</b-button>
@@ -85,7 +85,13 @@
export default {
mixins: [VuelidateMixin],
- emits: ['ok', 'hidden'],
+ props: {
+ modelValue: {
+ type: Boolean,
+ default: false,
+ },
+ },
+ emits: ['ok', 'hidden', 'update:modelValue'],
setup() {
return {
v$: useVuelidate(),
@@ -114,6 +120,18 @@
},
};
},
+ watch: {
+ modelValue: {
+ handler(newValue) {
+ if (newValue) {
+ this.$nextTick(() => {
+ this.$refs.modal?.show();
+ });
+ }
+ },
+ immediate: true,
+ },
+ },
methods: {
handleSubmit() {
this.v$.$touch();
@@ -133,6 +151,7 @@
this.form.ipAddress = null;
this.form.prefixLength = null;
this.v$.$reset();
+ this.$emit('update:modelValue', false);
this.$emit('hidden');
},
onOk(bvModalEvt) {
diff --git a/src/views/Settings/Network/ModalMacAddress.vue b/src/views/Settings/Network/ModalMacAddress.vue
index 83c1406..2d70ce6 100644
--- a/src/views/Settings/Network/ModalMacAddress.vue
+++ b/src/views/Settings/Network/ModalMacAddress.vue
@@ -32,7 +32,7 @@
</b-col>
</b-row>
</b-form>
- <template #modal-footer="{ cancel }">
+ <template #footer="{ cancel }">
<b-button variant="secondary" @click="cancel()">
{{ $t('global.action.cancel') }}
</b-button>
@@ -58,12 +58,16 @@
export default {
mixins: [VuelidateMixin],
props: {
+ modelValue: {
+ type: Boolean,
+ default: false,
+ },
macAddress: {
type: String,
default: '',
},
},
- emits: ['ok', 'hidden'],
+ emits: ['ok', 'hidden', 'update:modelValue'],
setup() {
return {
v$: useVuelidate(),
@@ -81,6 +85,16 @@
macAddress() {
this.form.macAddress = this.macAddress;
},
+ modelValue: {
+ handler(newValue) {
+ if (newValue) {
+ this.$nextTick(() => {
+ this.$refs.modal?.show();
+ });
+ }
+ },
+ immediate: true,
+ },
},
validations() {
return {
@@ -107,6 +121,7 @@
resetForm() {
this.form.macAddress = this.macAddress;
this.v$.$reset();
+ this.$emit('update:modelValue', false);
this.$emit('hidden');
},
onOk(bvModalEvt) {
diff --git a/src/views/Settings/Network/Network.vue b/src/views/Settings/Network/Network.vue
index 7a2e014..73118c8 100644
--- a/src/views/Settings/Network/Network.vue
+++ b/src/views/Settings/Network/Network.vue
@@ -4,20 +4,22 @@
<!-- Global settings for all interfaces -->
<network-global-settings />
<!-- Interface tabs -->
- <page-section v-show="ethernetData">
+ <page-section v-if="ethernetData && ethernetData.length">
<b-row>
<b-col>
<b-card no-body>
<b-tabs
- active-nav-item-class="font-weight-bold"
+ :key="tabsRenderKey"
+ v-model:index="tabIndex"
+ active-nav-item-class="fw-bold"
card
content-class="mt-3"
+ :lazy="false"
>
<b-tab
- v-for="(data, index) in ethernetData"
+ v-for="data in ethernetData"
:key="data.Id"
:title="data.Id"
- @click="getTabIndex(index)"
>
<!-- Interface settings -->
<network-interface-settings :tab-index="tabIndex" />
@@ -37,9 +39,18 @@
<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-hostname
+ v-model="showHostnameModal"
+ :hostname="currentHostname"
+ @ok="saveSettings"
+ />
+ <modal-mac-address
+ v-model="showMacAddressModal"
+ :mac-address="currentMacAddress"
+ @ok="saveSettings"
+ />
<modal-default-gateway
+ v-model="showDefaultGatewayModal"
:default-gateway="ipv6DefaultGateway"
@ok="saveSettings"
/>
@@ -97,6 +108,11 @@
ipv6DefaultGateway: '',
loading,
tabIndex: 0,
+ tabsReady: false,
+ tabsRenderKey: 0,
+ showHostnameModal: false,
+ showDefaultGatewayModal: false,
+ showMacAddressModal: false,
};
},
computed: {
@@ -106,23 +122,32 @@
ethernetData() {
this.getModalInfo();
},
+ tabIndex(newIndex) {
+ this.$store.dispatch('network/setSelectedTabIndex', newIndex);
+ this.$store.dispatch(
+ 'network/setSelectedTabId',
+ this.ethernetData?.[newIndex]?.Id,
+ );
+ this.getModalInfo();
+ },
},
created() {
this.startLoader();
+ const eventBus = require('@/eventBus').default;
const globalSettings = new Promise((resolve) => {
- this.$root.$on('network-global-settings-complete', () => resolve());
+ eventBus.$once('network-global-settings-complete', resolve);
});
const interfaceSettings = new Promise((resolve) => {
- this.$root.$on('network-interface-settings-complete', () => resolve());
+ eventBus.$once('network-interface-settings-complete', resolve);
});
const networkTableDns = new Promise((resolve) => {
- this.$root.$on('network-table-dns-complete', () => resolve());
+ eventBus.$once('network-table-dns-complete', resolve);
});
const networkTableIpv4 = new Promise((resolve) => {
- this.$root.$on('network-table-ipv4-complete', () => resolve());
+ eventBus.$once('network-table-ipv4-complete', resolve);
});
const networkTableIpv6 = new Promise((resolve) => {
- this.$root.$on('network-table-ipv6-complete', () => resolve());
+ eventBus.$once('network-table-ipv6-complete', resolve);
});
// Combine all child component Promises to indicate
// when page data load complete
@@ -133,28 +158,36 @@
networkTableDns,
networkTableIpv4,
networkTableIpv6,
- ]).finally(() => this.endLoader());
+ ])
+ .then(() => {
+ // ensure first tab is selected and expanded (index 0). Force a change
+ // cycle to trigger BTabs to render the pane content immediately.
+ const count = this.ethernetData?.length || 0;
+ if (count > 0) {
+ // set initial selection directly to index 0
+ this.tabIndex = 0;
+ this.$store.dispatch('network/setSelectedTabIndex', 0);
+ const firstId = this.ethernetData?.[0]?.Id;
+ if (firstId)
+ this.$store.dispatch('network/setSelectedTabId', firstId);
+ this.tabsRenderKey += 1;
+ }
+ })
+ .finally(() => this.endLoader());
},
methods: {
getModalInfo() {
- this.defaultGateway =
- this.$store.getters['network/globalNetworkSettings'][
- this.tabIndex
- ].defaultGateway;
+ const settingsArray =
+ this.$store.getters['network/globalNetworkSettings'];
+ const settings = Array.isArray(settingsArray)
+ ? settingsArray[this.tabIndex]
+ : undefined;
- this.currentHostname =
- this.$store.getters['network/globalNetworkSettings'][
- this.tabIndex
- ].hostname;
-
- this.currentMacAddress =
- this.$store.getters['network/globalNetworkSettings'][
- this.tabIndex
- ].macAddress;
- this.ipv6DefaultGateway =
- this.$store.getters['network/globalNetworkSettings'][
- this.tabIndex
- ].ipv6DefaultGateway;
+ if (!settings) return;
+ this.defaultGateway = settings.defaultGateway;
+ this.currentHostname = settings.hostname;
+ this.currentMacAddress = settings.macAddress;
+ this.ipv6DefaultGateway = settings.ipv6DefaultGateway;
},
getTabIndex(selectedIndex) {
this.tabIndex = selectedIndex;
diff --git a/src/views/Settings/Network/NetworkGlobalSettings.vue b/src/views/Settings/Network/NetworkGlobalSettings.vue
index 23ce6ca..4f11421 100644
--- a/src/views/Settings/Network/NetworkGlobalSettings.vue
+++ b/src/views/Settings/Network/NetworkGlobalSettings.vue
@@ -134,16 +134,22 @@
import PageSection from '@/components/Global/PageSection';
import { mapState } from 'vuex';
import { useI18n } from 'vue-i18n';
+import { useModal } from 'bootstrap-vue-next';
export default {
name: 'GlobalNetworkSettings',
components: { IconEdit, PageSection },
mixins: [BVToastMixin, DataFormatterMixin],
+ setup() {
+ const bvModal = useModal();
+ return { bvModal };
+ },
data() {
return {
$t: useI18n().t,
hostname: '',
+ showHostnameModal: false,
};
},
computed: {
@@ -209,14 +215,14 @@
created() {
this.$store.dispatch('network/getEthernetData').finally(() => {
// Emit initial data fetch complete to parent component
- this.$root.$emit('network-global-settings-complete');
+ require('@/eventBus').default.$emit('network-global-settings-complete');
});
},
methods: {
changeDomainNameState(state) {
this.$store
.dispatch('network/saveDomainNameState', {
- domainState: state,
+ domainState: !!state,
ipVersion: 'IPv4',
})
.then((success) => {
@@ -227,7 +233,7 @@
changeDnsState(state) {
this.$store
.dispatch('network/saveDnsState', {
- dnsState: state,
+ dnsState: !!state,
ipVersion: 'IPv4',
})
.then((message) => {
@@ -238,7 +244,7 @@
changeNtpState(state) {
this.$store
.dispatch('network/saveNtpState', {
- ntpState: state,
+ ntpState: !!state,
ipVersion: 'IPv4',
})
.then((message) => {
@@ -249,7 +255,7 @@
changeDomainNameStateIpv6(state) {
this.$store
.dispatch('network/saveDomainNameState', {
- domainState: state,
+ domainState: !!state,
ipVersion: 'IPv6',
})
.then((success) => {
@@ -260,7 +266,7 @@
changeDnsStateIpv6(state) {
this.$store
.dispatch('network/saveDnsState', {
- dnsState: state,
+ dnsState: !!state,
ipVersion: 'IPv6',
})
.then((message) => {
@@ -271,7 +277,7 @@
changeNtpStateIpv6(state) {
this.$store
.dispatch('network/saveNtpState', {
- ntpState: state,
+ ntpState: !!state,
ipVersion: 'IPv6',
})
.then((message) => {
@@ -280,7 +286,7 @@
.catch(({ message }) => this.errorToast(message));
},
initSettingsModal() {
- this.$bvModal.show('modal-hostname');
+ this.showHostnameModal = true;
},
},
};
diff --git a/src/views/Settings/Network/NetworkInterfaceSettings.vue b/src/views/Settings/Network/NetworkInterfaceSettings.vue
index ea83757..18f4868 100644
--- a/src/views/Settings/Network/NetworkInterfaceSettings.vue
+++ b/src/views/Settings/Network/NetworkInterfaceSettings.vue
@@ -63,6 +63,7 @@
import DataFormatterMixin from '@/components/Mixins/DataFormatterMixin';
import { mapState } from 'vuex';
import { useI18n } from 'vue-i18n';
+import { useModal } from 'bootstrap-vue-next';
export default {
name: 'Ipv4Table',
@@ -77,6 +78,10 @@
default: 0,
},
},
+ setup() {
+ const bvModal = useModal();
+ return { bvModal };
+ },
data() {
return {
$t: useI18n().t,
@@ -85,6 +90,7 @@
linkSpeed: '',
fqdn: '',
macAddress: '',
+ showMacAddressModal: false,
};
},
computed: {
@@ -100,7 +106,9 @@
this.getSettings();
this.$store.dispatch('network/getEthernetData').finally(() => {
// Emit initial data fetch complete to parent component
- this.$root.$emit('network-interface-settings-complete');
+ require('@/eventBus').default.$emit(
+ 'network-interface-settings-complete',
+ );
});
},
methods: {
@@ -112,7 +120,7 @@
this.macAddress = this.ethernetData[this.selectedInterface].MACAddress;
},
initMacAddressModal() {
- this.$bvModal.show('modal-mac-address');
+ this.showMacAddressModal = true;
},
},
};
diff --git a/src/views/Settings/Network/TableDns.vue b/src/views/Settings/Network/TableDns.vue
index b0e5d80..e78234b 100644
--- a/src/views/Settings/Network/TableDns.vue
+++ b/src/views/Settings/Network/TableDns.vue
@@ -2,7 +2,7 @@
<page-section :section-title="$t('pageNetwork.staticDns')">
<b-row>
<b-col lg="6">
- <div class="text-right">
+ <div class="text-end">
<b-button variant="primary" @click="initDnsModal()">
<icon-add />
{{ $t('pageNetwork.table.addDnsAddress') }}
@@ -11,6 +11,7 @@
<b-table
responsive="md"
hover
+ thead-class="table-light"
:fields="dnsTableFields"
:items="form.dnsStaticTableItems"
:empty-text="$t('global.table.emptyMessage')"
@@ -36,6 +37,7 @@
</b-col>
</b-row>
</page-section>
+ <modal-dns v-model="showDnsModal" />
</template>
<script>
@@ -45,9 +47,11 @@
import IconTrashcan from '@carbon/icons-vue/es/trash-can/20';
import PageSection from '@/components/Global/PageSection';
import TableRowAction from '@/components/Global/TableRowAction';
+import ModalDns from './ModalDns.vue';
import { mapState } from 'vuex';
import { useI18n } from 'vue-i18n';
import i18n from '@/i18n';
+import { useModal } from 'bootstrap-vue-next';
export default {
name: 'DNSTable',
@@ -57,6 +61,7 @@
IconTrashcan,
PageSection,
TableRowAction,
+ ModalDns,
},
mixins: [BVToastMixin],
props: {
@@ -65,12 +70,17 @@
default: 0,
},
},
+ setup() {
+ const bvModal = useModal();
+ return { bvModal };
+ },
data() {
return {
$t: useI18n().t,
form: {
dnsStaticTableItems: [],
},
+ showDnsModal: false,
actions: [
{
value: 'edit',
@@ -86,7 +96,7 @@
key: 'address',
label: i18n.global.t('pageNetwork.table.ipAddress'),
},
- { key: 'actions', label: '', tdClass: 'text-right' },
+ { key: 'actions', label: '', tdClass: 'text-end' },
],
};
},
@@ -106,7 +116,7 @@
this.getStaticDnsItems();
this.$store.dispatch('network/getEthernetData').finally(() => {
// Emit initial data fetch complete to parent component
- this.$root.$emit('network-table-dns-complete');
+ require('@/eventBus').default.$emit('network-table-dns-complete');
});
},
methods: {
@@ -141,7 +151,7 @@
.catch(({ message }) => this.errorToast(message));
},
initDnsModal() {
- this.$bvModal.show('modal-dns');
+ this.showDnsModal = true;
},
},
};
diff --git a/src/views/Settings/Network/TableIpv4.vue b/src/views/Settings/Network/TableIpv4.vue
index b95e7d3..994990c 100644
--- a/src/views/Settings/Network/TableIpv4.vue
+++ b/src/views/Settings/Network/TableIpv4.vue
@@ -27,7 +27,7 @@
{{ $t('pageNetwork.ipv4Addresses') }}
</h3>
</b-col>
- <b-col class="text-right">
+ <b-col class="text-end">
<b-button variant="primary" @click="initAddIpv4Address()">
<icon-add />
{{ $t('pageNetwork.table.addIpv4Address') }}
@@ -37,6 +37,7 @@
<b-table
responsive="md"
hover
+ thead-class="table-light"
:fields="ipv4TableFields"
:items="form.ipv4TableItems"
:empty-text="$t('global.table.emptyMessage')"
@@ -59,6 +60,7 @@
</table-row-action>
</template>
</b-table>
+ <modal-ipv4 v-model="showAddIpv4" />
</page-section>
</template>
@@ -70,9 +72,11 @@
import LoadingBarMixin from '@/components/Mixins/LoadingBarMixin';
import PageSection from '@/components/Global/PageSection';
import TableRowAction from '@/components/Global/TableRowAction';
+import ModalIpv4 from './ModalIpv4.vue';
import { mapState } from 'vuex';
import { useI18n } from 'vue-i18n';
import i18n from '@/i18n';
+import { useModal } from 'bootstrap-vue-next';
export default {
name: 'Ipv4Table',
@@ -82,6 +86,7 @@
IconTrashcan,
PageSection,
TableRowAction,
+ ModalIpv4,
},
mixins: [BVToastMixin, LoadingBarMixin],
props: {
@@ -90,9 +95,14 @@
default: 0,
},
},
+ setup() {
+ const bvModal = useModal();
+ return { bvModal };
+ },
data() {
return {
$t: useI18n().t,
+ showAddIpv4: false,
form: {
ipv4TableItems: [],
},
@@ -123,7 +133,7 @@
key: 'AddressOrigin',
label: i18n.global.t('pageNetwork.table.addressOrigin'),
},
- { key: 'actions', label: '', tdClass: 'text-right' },
+ { key: 'actions', label: '', tdClass: 'text-end' },
],
};
},
@@ -165,7 +175,7 @@
this.getIpv4TableItems();
this.$store.dispatch('network/getEthernetData').finally(() => {
// Emit initial data fetch complete to parent component
- this.$root.$emit('network-table-ipv4-complete');
+ require('@/eventBus').default.$emit('network-table-ipv4-complete');
});
},
methods: {
@@ -208,39 +218,39 @@
.catch(({ message }) => this.errorToast(message));
},
initAddIpv4Address() {
- this.$bvModal.show('modal-add-ipv4');
+ this.showAddIpv4 = true;
},
changeDhcpEnabledState(state) {
- this.$bvModal
- .msgBoxConfirm(
- state
- ? i18n.global.t('pageNetwork.modal.confirmEnableDhcp')
- : i18n.global.t('pageNetwork.modal.confirmDisableDhcp'),
- {
- title: i18n.global.t('pageNetwork.modal.dhcpConfirmTitle', {
- dhcpState: state
- ? i18n.global.t('global.action.enable')
- : i18n.global.t('global.action.disable'),
- }),
- okTitle: state
- ? i18n.global.t('global.action.enable')
- : i18n.global.t('global.action.disable'),
- okVariant: 'danger',
- cancelTitle: i18n.global.t('global.action.cancel'),
- autoFocusButton: 'cancel',
- },
- )
- .then((dhcpEnableConfirmed) => {
- if (dhcpEnableConfirmed) {
- this.$store
- .dispatch('network/saveDhcpEnabledState', state)
- .then((message) => this.successToast(message))
- .catch(({ message }) => this.errorToast(message));
- } else {
- let onDhcpCancel = document.getElementById('dhcpSwitch');
- onDhcpCancel.checked = !state;
- }
- });
+ const dhcpState = state
+ ? i18n.global.t('global.action.enable')
+ : i18n.global.t('global.action.disable');
+ this.confirmDialog(
+ state
+ ? i18n.global.t('pageNetwork.modal.confirmEnableDhcp')
+ : i18n.global.t('pageNetwork.modal.confirmDisableDhcp'),
+ {
+ title: i18n.global.t('pageNetwork.modal.dhcpConfirmTitle', {
+ dhcpState,
+ }),
+ okTitle: dhcpState,
+ okVariant: 'danger',
+ cancelTitle: i18n.global.t('global.action.cancel'),
+ autoFocusButton: 'cancel',
+ },
+ ).then((dhcpEnableConfirmed) => {
+ if (dhcpEnableConfirmed) {
+ this.$store
+ .dispatch('network/saveDhcpEnabledState', state)
+ .then((message) => this.successToast(message))
+ .catch(({ message }) => this.errorToast(message));
+ } else {
+ let onDhcpCancel = document.getElementById('dhcpSwitch');
+ onDhcpCancel.checked = !state;
+ }
+ });
+ },
+ confirmDialog(message, options = {}) {
+ return this.$confirm({ message, ...options });
},
},
};
diff --git a/src/views/Settings/Network/TableIpv6.vue b/src/views/Settings/Network/TableIpv6.vue
index bdebc27..bf2e6e1 100644
--- a/src/views/Settings/Network/TableIpv6.vue
+++ b/src/views/Settings/Network/TableIpv6.vue
@@ -47,7 +47,7 @@
{{ $t('pageNetwork.ipv6Addresses') }}
</h3>
</b-col>
- <b-col class="text-right">
+ <b-col class="text-end">
<b-button variant="primary" @click="initAddIpv6Address()">
<icon-add />
{{ $t('pageNetwork.table.addIpv6Address') }}
@@ -57,6 +57,7 @@
<b-table
responsive="md"
hover
+ thead-class="table-light"
:fields="ipv6TableFields"
:items="form.ipv6TableItems"
:empty-text="$t('global.table.emptyMessage')"
@@ -79,6 +80,7 @@
</table-row-action>
</template>
</b-table>
+ <modal-ipv6 v-model="showAddIpv6" />
</page-section>
</template>
@@ -91,9 +93,11 @@
import PageSection from '@/components/Global/PageSection';
import TableRowAction from '@/components/Global/TableRowAction';
import DataFormatterMixin from '@/components/Mixins/DataFormatterMixin';
+import ModalIpv6 from './ModalIpv6.vue';
import { mapState } from 'vuex';
import i18n from '@/i18n';
import { useI18n } from 'vue-i18n';
+import { useModal } from 'bootstrap-vue-next';
export default {
name: 'Ipv6Table',
@@ -103,6 +107,7 @@
IconTrashcan,
PageSection,
TableRowAction,
+ ModalIpv6,
},
mixins: [BVToastMixin, LoadingBarMixin, DataFormatterMixin],
props: {
@@ -111,9 +116,15 @@
default: 0,
},
},
+ setup() {
+ const bvModal = useModal();
+ return { bvModal };
+ },
data() {
return {
$t: useI18n().t,
+ showAddIpv6: false,
+ showDefaultGatewayModal: false,
form: {
ipv6TableItems: [],
},
@@ -140,7 +151,7 @@
key: 'AddressOrigin',
label: i18n.global.t('pageNetwork.table.addressOrigin'),
},
- { key: 'actions', label: '', tdClass: 'text-right' },
+ { key: 'actions', label: '', tdClass: 'text-end' },
],
defaultGateway: '',
defaultGatewayEditable:
@@ -190,7 +201,7 @@
this.getDefaultGateway();
this.$store.dispatch('network/getEthernetData').finally(() => {
// Emit initial data fetch complete to parent component
- this.$root.$emit('network-table-ipv6-complete');
+ require('@/eventBus').default.$emit('network-table-ipv6-complete');
});
},
methods: {
@@ -251,41 +262,42 @@
.catch(({ message }) => this.errorToast(message));
},
initAddIpv6Address() {
- this.$bvModal.show('modal-add-ipv6');
+ this.showAddIpv6 = true;
},
changeDhcp6EnabledState(state) {
- this.$bvModal
- .msgBoxConfirm(
- state
- ? i18n.global.t('pageNetwork.modal.confirmEnableDhcp')
- : i18n.global.t('pageNetwork.modal.confirmDisableDhcp'),
- {
- title: i18n.global.t('pageNetwork.modal.dhcpConfirmTitle', {
- dhcpState: state
- ? i18n.global.t('global.action.enable')
- : i18n.global.t('global.action.disable'),
- }),
- okTitle: state
- ? i18n.global.t('global.action.enable')
- : i18n.global.t('global.action.disable'),
- okVariant: 'danger',
- cancelTitle: i18n.global.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;
- }
- });
+ const dhcpState = state
+ ? i18n.global.t('global.action.enable')
+ : i18n.global.t('global.action.disable');
+ this.confirmDialog(
+ state
+ ? i18n.global.t('pageNetwork.modal.confirmEnableDhcp')
+ : i18n.global.t('pageNetwork.modal.confirmDisableDhcp'),
+ {
+ title: i18n.global.t('pageNetwork.modal.dhcpConfirmTitle', {
+ dhcpState,
+ }),
+ okTitle: dhcpState,
+ okVariant: 'danger',
+ cancelTitle: i18n.global.t('global.action.cancel'),
+ autoFocusButton: '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;
+ }
+ });
+ },
+ confirmDialog(message, options = {}) {
+ return this.$confirm({ message, ...options });
},
initDefaultGatewayModal() {
- this.$bvModal.show('modal-default-gateway');
+ this.showDefaultGatewayModal = true;
},
},
};
diff --git a/src/views/Settings/SnmpAlerts/ModalAddDestination.vue b/src/views/Settings/SnmpAlerts/ModalAddDestination.vue
index 5eef381..38b1ced 100644
--- a/src/views/Settings/SnmpAlerts/ModalAddDestination.vue
+++ b/src/views/Settings/SnmpAlerts/ModalAddDestination.vue
@@ -1,8 +1,11 @@
<template>
- <b-modal id="add-destination" ref="modal" @ok="onOk" @hidden="resetForm">
- <template #modal-title>
- {{ $t('pageSnmpAlerts.modal.addSnmpDestinationTitle') }}
- </template>
+ <b-modal
+ id="add-destination"
+ ref="modal"
+ :title="$t('pageSnmpAlerts.modal.addSnmpDestinationTitle')"
+ @ok="onOk"
+ @hidden="resetForm"
+ >
<b-form id="form-destination">
<b-container>
<b-row>
@@ -64,7 +67,7 @@
</b-row>
</b-container>
</b-form>
- <template #modal-footer="{ cancel }">
+ <template #footer="{ cancel }">
<b-button variant="secondary" @click="cancel()">
{{ $t('global.action.cancel') }}
</b-button>
diff --git a/src/views/Settings/SnmpAlerts/SnmpAlerts.vue b/src/views/Settings/SnmpAlerts/SnmpAlerts.vue
index d18ea75..7dba367 100644
--- a/src/views/Settings/SnmpAlerts/SnmpAlerts.vue
+++ b/src/views/Settings/SnmpAlerts/SnmpAlerts.vue
@@ -2,7 +2,7 @@
<b-container fluid="xl">
<page-title :description="$t('pageSnmpAlerts.pageDescription')" />
<b-row>
- <b-col xl="9" class="text-right">
+ <b-col xl="9" class="text-end">
<b-button variant="primary" @click="initModalAddDestination">
<icon-add />
{{ $t('pageSnmpAlerts.addDestination') }}
@@ -13,7 +13,9 @@
<b-col xl="9">
<table-toolbar
ref="toolbar"
- :selected-items-count="selectedRows.length"
+ :selected-items-count="
+ Array.isArray(selectedRows) ? selectedRows.length : 0
+ "
:actions="tableToolbarActions"
@clear-selected="clearSelectedRows($refs.table)"
@batch-action="onBatchAction"
@@ -25,6 +27,7 @@
show-empty
no-select-on-click
hover
+ thead-class="table-light"
:fields="fields"
:items="tableItems"
:empty-text="$t('global.table.emptyMessage')"
@@ -36,9 +39,11 @@
v-model="tableHeaderCheckboxModel"
data-test-id="snmpAlerts-checkbox-selectAll"
: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">
@@ -47,7 +52,9 @@
:data-test-id="`snmpAlerts-checkbox-selectRow-${row.index}`"
@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>
@@ -71,7 +78,7 @@
</b-col>
</b-row>
<!-- Modals -->
- <modal-add-destination @ok="onModalOk" />
+ <modal-add-destination v-model="showAddDestination" @ok="onModalOk" />
</b-container>
</template>
@@ -84,6 +91,7 @@
import TableRowAction from '@/components/Global/TableRowAction';
import LoadingBarMixin from '@/components/Mixins/LoadingBarMixin';
import BVToastMixin from '@/components/Mixins/BVToastMixin';
+import { useModal } from 'bootstrap-vue-next';
import BVTableSelectableMixin, {
selectedRows,
@@ -108,9 +116,14 @@
this.hideLoader();
next();
},
+ setup() {
+ const bvModal = useModal();
+ return { bvModal };
+ },
data() {
return {
$t: useI18n().t,
+ showAddDestination: false,
fields: [
{
key: 'checkbox',
@@ -126,7 +139,7 @@
{
key: 'actions',
label: '',
- tdClass: 'text-right text-nowrap',
+ tdClass: 'text-end text-nowrap',
},
],
tableToolbarActions: [
@@ -201,28 +214,26 @@
.finally(() => this.endLoader());
},
initModalAddDestination() {
- this.$bvModal.show('add-destination');
+ this.showAddDestination = true;
},
initModalDeleteDestination(destination) {
- this.$bvModal
- .msgBoxConfirm(
- i18n.global.t('pageSnmpAlerts.modal.deleteConfirmMessage', {
- destination: destination.id,
- }),
- {
- title: i18n.global.t(
- 'pageSnmpAlerts.modal.deleteSnmpDestinationTitle',
- ),
- okTitle: i18n.global.t('pageSnmpAlerts.deleteDestination'),
- cancelTitle: i18n.global.t('global.action.cancel'),
- autoFocusButton: 'ok',
- },
- )
- .then((deleteConfirmed) => {
- if (deleteConfirmed) {
- this.deleteDestination(destination);
- }
- });
+ this.confirmDialog(
+ i18n.global.t('pageSnmpAlerts.modal.deleteConfirmMessage', {
+ destination: destination.id,
+ }),
+ {
+ title: i18n.global.t(
+ 'pageSnmpAlerts.modal.deleteSnmpDestinationTitle',
+ ),
+ okTitle: i18n.global.t('pageSnmpAlerts.deleteDestination'),
+ cancelTitle: i18n.global.t('global.action.cancel'),
+ autoFocusButton: 'ok',
+ },
+ ).then((deleteConfirmed) => {
+ if (deleteConfirmed) {
+ this.deleteDestination(destination);
+ }
+ });
},
deleteDestination({ id }) {
this.startLoader();
@@ -234,44 +245,43 @@
},
onBatchAction(action) {
if (action === 'delete') {
- this.$bvModal
- .msgBoxConfirm(
- i18n.global.t(
- 'pageSnmpAlerts.modal.batchDeleteConfirmMessage',
- this.selectedRows.length,
+ const count = this.selectedRows.length;
+ this.confirmDialog(
+ i18n.global.t(
+ 'pageSnmpAlerts.modal.batchDeleteConfirmMessage',
+ count,
+ ),
+ {
+ title: i18n.global.t(
+ 'pageSnmpAlerts.modal.deleteSnmpDestinationTitle',
+ count,
),
- {
- title: i18n.global.t(
- 'pageSnmpAlerts.modal.deleteSnmpDestinationTitle',
- this.selectedRows.length,
- ),
- okTitle: i18n.global.t(
- 'pageSnmpAlerts.deleteDestination',
- this.selectedRows.length,
- ),
- cancelTitle: i18n.global.t('global.action.cancel'),
- autoFocusButton: 'ok',
- },
- )
- .then((deleteConfirmed) => {
- if (deleteConfirmed) {
- this.startLoader();
- this.$store
- .dispatch(
- 'snmpAlerts/deleteMultipleDestinations',
- this.selectedRows,
- )
- .then((messages) => {
- messages.forEach(({ type, message }) => {
- if (type === 'success') this.successToast(message);
- if (type === 'error') this.errorToast(message);
- });
- })
- .finally(() => this.endLoader());
- }
- });
+ okTitle: i18n.global.t('pageSnmpAlerts.deleteDestination', count),
+ cancelTitle: i18n.global.t('global.action.cancel'),
+ autoFocusButton: 'ok',
+ },
+ ).then((deleteConfirmed) => {
+ if (deleteConfirmed) {
+ this.startLoader();
+ this.$store
+ .dispatch(
+ 'snmpAlerts/deleteMultipleDestinations',
+ this.selectedRows,
+ )
+ .then((messages) => {
+ messages.forEach(({ type, message }) => {
+ if (type === 'success') this.successToast(message);
+ if (type === 'error') this.errorToast(message);
+ });
+ })
+ .finally(() => this.endLoader());
+ }
+ });
}
},
+ confirmDialog(message, options = {}) {
+ return this.$confirm({ message, ...options });
+ },
onTableRowAction(action, row) {
if (action === 'delete') {
this.initModalDeleteDestination(row);