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