xref: /openbmc/webui-vue/src/views/HardwareStatus/Inventory/InventoryTableFans.vue (revision d36ac8a8be8636ddd0e64ce005d507b21bcdeb00)
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