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