1<template> 2 <page-section :section-title="$t('pageInventory.fans')"> 3 <b-row class="align-items-end"> 4 <b-col sm="6" md="5" xl="4"> 5 <search 6 @change-search="onChangeSearchInput" 7 @clear-search="onClearSearchInput" 8 /> 9 </b-col> 10 <b-col sm="6" md="3" xl="2"> 11 <table-cell-count 12 :filtered-items-count="filteredRows" 13 :total-number-of-cells="fans.length" 14 ></table-cell-count> 15 </b-col> 16 </b-row> 17 <b-table 18 sort-icon-left 19 no-sort-reset 20 hover 21 responsive="md" 22 sort-by="health" 23 show-empty 24 :items="fans" 25 :fields="fields" 26 :sort-desc="true" 27 :sort-compare="sortCompare" 28 :filter="searchFilter" 29 :empty-text="$t('global.table.emptyMessage')" 30 :empty-filtered-text="$t('global.table.emptySearchMessage')" 31 :busy="isBusy" 32 @filtered="onFiltered" 33 > 34 <!-- Expand chevron icon --> 35 <template #cell(expandRow)="row"> 36 <b-button 37 variant="link" 38 data-test-id="hardwareStatus-button-expandFans" 39 :title="expandRowLabel" 40 class="btn-icon-only" 41 @click="toggleRowDetails(row)" 42 > 43 <icon-chevron /> 44 <span class="sr-only">{{ expandRowLabel }}</span> 45 </b-button> 46 </template> 47 48 <!-- Health --> 49 <template #cell(health)="{ value }"> 50 <status-icon :status="statusIcon(value)" /> 51 {{ value }} 52 </template> 53 54 <!-- StatusState --> 55 <template #cell(statusState)="{ value }"> 56 <status-icon :status="statusStateIcon(value)" /> 57 {{ value }} 58 </template> 59 60 <template #row-details="{ item }"> 61 <b-container fluid> 62 <b-row> 63 <b-col sm="6" xl="4"> 64 <dl> 65 <!-- ID --> 66 <dt>{{ $t('pageInventory.table.id') }}:</dt> 67 <dd>{{ dataFormatter(item.id) }}</dd> 68 </dl> 69 <dl> 70 <!-- Serial number --> 71 <dt>{{ $t('pageInventory.table.serialNumber') }}:</dt> 72 <dd>{{ dataFormatter(item.serialNumber) }}</dd> 73 </dl> 74 <dl> 75 <!-- Part number --> 76 <dt>{{ $t('pageInventory.table.partNumber') }}:</dt> 77 <dd>{{ dataFormatter(item.partNumber) }}</dd> 78 </dl> 79 <dl> 80 <!-- Fan speed --> 81 <dt>{{ $t('pageInventory.table.fanSpeed') }}:</dt> 82 <dd> 83 {{ dataFormatter(item.speed) }} 84 {{ $t('unit.RPM') }} 85 </dd> 86 </dl> 87 </b-col> 88 <b-col sm="6" xl="4"> 89 <dl> 90 <!-- Status state --> 91 <dt>{{ $t('pageInventory.table.statusState') }}:</dt> 92 <dd>{{ dataFormatter(item.statusState) }}</dd> 93 </dl> 94 <dl> 95 <!-- Health Rollup state --> 96 <dt>{{ $t('pageInventory.table.statusHealthRollup') }}:</dt> 97 <dd>{{ dataFormatter(item.healthRollup) }}</dd> 98 </dl> 99 </b-col> 100 </b-row> 101 </b-container> 102 </template> 103 </b-table> 104 </page-section> 105</template> 106 107<script> 108import PageSection from '@/components/Global/PageSection'; 109import IconChevron from '@carbon/icons-vue/es/chevron--down/20'; 110import TableCellCount from '@/components/Global/TableCellCount'; 111 112import StatusIcon from '@/components/Global/StatusIcon'; 113import DataFormatterMixin from '@/components/Mixins/DataFormatterMixin'; 114import TableSortMixin from '@/components/Mixins/TableSortMixin'; 115import Search from '@/components/Global/Search'; 116import SearchFilterMixin, { 117 searchFilter, 118} from '@/components/Mixins/SearchFilterMixin'; 119import TableRowExpandMixin, { 120 expandRowLabel, 121} from '@/components/Mixins/TableRowExpandMixin'; 122import { useI18n } from 'vue-i18n'; 123import i18n from '@/i18n'; 124 125export default { 126 components: { IconChevron, PageSection, StatusIcon, Search, TableCellCount }, 127 mixins: [ 128 TableRowExpandMixin, 129 DataFormatterMixin, 130 TableSortMixin, 131 SearchFilterMixin, 132 ], 133 data() { 134 return { 135 $t: useI18n().t, 136 isBusy: true, 137 fields: [ 138 { 139 key: 'expandRow', 140 label: '', 141 tdClass: 'table-row-expand', 142 sortable: false, 143 }, 144 { 145 key: 'name', 146 label: i18n.global.t('pageInventory.table.name'), 147 formatter: this.dataFormatter, 148 sortable: true, 149 }, 150 { 151 key: 'health', 152 label: i18n.global.t('pageInventory.table.health'), 153 formatter: this.dataFormatter, 154 sortable: true, 155 tdClass: 'text-nowrap', 156 }, 157 { 158 key: 'statusState', 159 label: i18n.global.t('pageInventory.table.state'), 160 formatter: this.dataFormatter, 161 tdClass: 'text-nowrap', 162 }, 163 { 164 key: 'partNumber', 165 label: i18n.global.t('pageInventory.table.partNumber'), 166 formatter: this.dataFormatter, 167 sortable: true, 168 }, 169 { 170 key: 'serialNumber', 171 label: i18n.global.t('pageInventory.table.serialNumber'), 172 formatter: this.dataFormatter, 173 }, 174 ], 175 searchFilter: searchFilter, 176 searchTotalFilteredRows: 0, 177 expandRowLabel: expandRowLabel, 178 }; 179 }, 180 computed: { 181 filteredRows() { 182 return this.searchFilter 183 ? this.searchTotalFilteredRows 184 : this.fans.length; 185 }, 186 fans() { 187 return this.$store.getters['fan/fans']; 188 }, 189 }, 190 created() { 191 this.$store.dispatch('fan/getFanInfo').finally(() => { 192 // Emit initial data fetch complete to parent component 193 this.$root.$emit('hardware-status-fans-complete'); 194 this.isBusy = false; 195 }); 196 }, 197 methods: { 198 sortCompare(a, b, key) { 199 if (key === 'health') { 200 return this.sortStatus(a, b, key); 201 } else if (key === 'statusState') { 202 return this.sortStatusState(a, b, key); 203 } 204 }, 205 onFiltered(filteredItems) { 206 this.searchTotalFilteredRows = filteredItems.length; 207 }, 208 /** 209 * Returns the appropriate icon based on the given status. 210 * 211 * @param {string} status - The status to determine the icon for. 212 * @return {string} The icon corresponding to the given status. 213 */ 214 statusStateIcon(status) { 215 switch (status) { 216 case 'Enabled': 217 return 'success'; 218 case 'Absent': 219 return 'warning'; 220 default: 221 return ''; 222 } 223 }, 224 /** 225 * Sorts the status state of two objects based on the provided key. 226 * 227 * @param {Object} a - The first object to compare. 228 * @param {Object} b - The second object to compare. 229 * @param {string} key - The key to use for comparison. 230 */ 231 sortStatusState(a, b, key) { 232 const statusState = ['Enabled', 'Absent']; 233 return statusState.indexOf(a[key]) - statusState.indexOf(b[key]); 234 }, 235 }, 236}; 237</script> 238