xref: /openbmc/webui-vue/src/views/SecurityAndAccess/Sessions/Sessions.vue (revision d36ac8a8be8636ddd0e64ce005d507b21bcdeb00)
1<template>
2  <b-container fluid="xl">
3    <page-title />
4    <b-row class="align-items-end">
5      <b-col sm="6" md="5" xl="4">
6        <search
7          :placeholder="$t('pageSessions.table.searchSessions')"
8          data-test-id="sessions-input-searchSessions"
9          @change-search="onChangeSearchInput"
10          @clear-search="onClearSearchInput"
11        />
12      </b-col>
13      <b-col sm="3" md="3" xl="2">
14        <table-cell-count
15          :filtered-items-count="filteredRows"
16          :total-number-of-cells="allConnections.length"
17        ></table-cell-count>
18      </b-col>
19    </b-row>
20    <b-row>
21      <b-col>
22        <table-toolbar
23          ref="toolbar"
24          :selected-items-count="
25            Array.isArray(selectedRows) ? selectedRows.length : 0
26          "
27          :actions="batchActions"
28          @clear-selected="clearSelectedRows($refs.table)"
29          @batch-action="onBatchAction"
30        >
31        </table-toolbar>
32        <b-table
33          id="table-session-logs"
34          ref="table"
35          responsive="md"
36          selectable
37          no-select-on-click
38          hover
39          show-empty
40          thead-class="table-light"
41          :sort-by="['sessionID']"
42          :busy="isBusy"
43          :fields="fields"
44          :items="allConnections"
45          :filter="searchFilter"
46          :empty-text="$t('global.table.emptyMessage')"
47          :per-page="perPage"
48          :current-page="currentPage"
49          @filtered="onFiltered"
50          @row-selected="onRowSelected"
51        >
52          <!-- Checkbox column -->
53          <template #head(checkbox)>
54            <b-form-checkbox
55              v-model="tableHeaderCheckboxModel"
56              data-test-id="sessions-checkbox-selectAll"
57              :indeterminate="tableHeaderCheckboxIndeterminate"
58              @change="onChangeHeaderCheckbox($refs.table, $event)"
59            >
60              <span class="visually-hidden-focusable">
61                {{ $t('global.table.selectAll') }}
62              </span>
63            </b-form-checkbox>
64          </template>
65          <template #cell(checkbox)="row">
66            <b-form-checkbox
67              v-model="row.rowSelected"
68              :data-test-id="`sessions-checkbox-selectRow-${row.index}`"
69              @change="toggleSelectRow($refs.table, row.index)"
70            >
71              <span class="visually-hidden-focusable">
72                {{ $t('global.table.selectItem') }}
73              </span>
74            </b-form-checkbox>
75          </template>
76
77          <!-- Actions column -->
78          <template #cell(actions)="row">
79            <table-row-action
80              v-for="(action, index) in row.item.actions"
81              :key="index"
82              :value="action.value"
83              :title="action.title"
84              :row-data="row.item"
85              :btn-icon-only="false"
86              :data-test-id="`sessions-button-disconnect-${row.index}`"
87              @click-table-action="onTableRowAction($event, row.item)"
88            ></table-row-action>
89          </template>
90        </b-table>
91      </b-col>
92    </b-row>
93
94    <!-- Table pagination -->
95    <b-row>
96      <b-col sm="6">
97        <b-form-group
98          class="table-pagination-select"
99          :label="$t('global.table.itemsPerPage')"
100          label-for="pagination-items-per-page"
101        >
102          <b-form-select
103            id="pagination-items-per-page"
104            v-model="perPage"
105            :options="itemsPerPageOptions"
106          />
107        </b-form-group>
108      </b-col>
109      <b-col sm="6">
110        <b-pagination
111          v-model="currentPage"
112          first-number
113          last-number
114          :per-page="perPage"
115          :total-rows="getTotalRowCount(filteredRows)"
116          aria-controls="table-session-logs"
117        />
118      </b-col>
119    </b-row>
120  </b-container>
121</template>
122
123<script>
124import PageTitle from '@/components/Global/PageTitle';
125import Search from '@/components/Global/Search';
126import TableCellCount from '@/components/Global/TableCellCount';
127import TableRowAction from '@/components/Global/TableRowAction';
128import TableToolbar from '@/components/Global/TableToolbar';
129
130import LoadingBarMixin from '@/components/Mixins/LoadingBarMixin';
131import BVPaginationMixin, {
132  currentPage,
133  perPage,
134  itemsPerPageOptions,
135} from '@/components/Mixins/BVPaginationMixin';
136import BVTableSelectableMixin from '@/components/Mixins/BVTableSelectableMixin';
137import BVToastMixin from '@/components/Mixins/BVToastMixin';
138import SearchFilterMixin, {
139  searchFilter,
140} from '@/components/Mixins/SearchFilterMixin';
141import { useI18n } from 'vue-i18n';
142import i18n from '@/i18n';
143import { useModal } from 'bootstrap-vue-next';
144
145export default {
146  components: {
147    PageTitle,
148    Search,
149    TableCellCount,
150    TableRowAction,
151    TableToolbar,
152  },
153  mixins: [
154    BVPaginationMixin,
155    BVTableSelectableMixin,
156    BVToastMixin,
157    LoadingBarMixin,
158    SearchFilterMixin,
159  ],
160  beforeRouteLeave(to, from, next) {
161    // Hide loader if the user navigates to another page
162    // before request is fulfilled.
163    this.hideLoader();
164    next();
165  },
166  setup() {
167    const bvModal = useModal();
168    return { bvModal };
169  },
170  data() {
171    return {
172      $t: useI18n().t,
173      isBusy: true,
174      fields: [
175        {
176          key: 'checkbox',
177          class: 'text-center',
178        },
179        {
180          key: 'sessionID',
181          label: i18n.global.t('pageSessions.table.sessionID'),
182          class: 'text-center',
183        },
184        {
185          key: 'context',
186          label: i18n.global.t('pageSessions.table.context'),
187          class: 'text-center',
188        },
189        {
190          key: 'username',
191          label: i18n.global.t('pageSessions.table.username'),
192          class: 'text-center',
193        },
194        {
195          key: 'ipAddress',
196          label: i18n.global.t('pageSessions.table.ipAddress'),
197          class: 'text-center',
198        },
199        {
200          key: 'actions',
201          label: '',
202          class: 'text-center',
203        },
204      ],
205      batchActions: [
206        {
207          value: 'disconnect',
208          label: i18n.global.t('pageSessions.action.disconnect'),
209        },
210      ],
211      currentPage: currentPage,
212      itemsPerPageOptions: itemsPerPageOptions,
213      perPage: perPage,
214      searchTotalFilteredRows: 0,
215      searchFilter: searchFilter,
216    };
217  },
218  computed: {
219    filteredRows() {
220      return this.searchFilter
221        ? this.searchTotalFilteredRows
222        : this.allConnections.length;
223    },
224    allConnections() {
225      return this.$store.getters['sessions/allConnections'].map((session) => {
226        return {
227          ...session,
228          actions: [
229            {
230              value: 'disconnect',
231              title: i18n.global.t('pageSessions.action.disconnect'),
232            },
233          ],
234        };
235      });
236    },
237  },
238  created() {
239    this.startLoader();
240    this.$store.dispatch('sessions/getSessionsData').finally(() => {
241      this.endLoader();
242      this.isBusy = false;
243    });
244  },
245  methods: {
246    onFiltered(filteredItems) {
247      this.searchTotalFilteredRows = filteredItems.length;
248    },
249    onChangeSearchInput(event) {
250      this.searchFilter = event;
251    },
252    disconnectSessions(uris) {
253      this.$store
254        .dispatch('sessions/disconnectSessions', uris)
255        .then((messages) => {
256          messages.forEach(({ type, message }) => {
257            if (type === 'success') {
258              this.successToast(message);
259            } else if (type === 'error') {
260              this.errorToast(message);
261            }
262          });
263        });
264    },
265    onTableRowAction(action, { uri }) {
266      if (action === 'disconnect') {
267        this.confirmDialog(
268          i18n.global.t('pageSessions.modal.disconnectMessage'),
269          {
270            title: i18n.global.t('pageSessions.modal.disconnectTitle'),
271            okTitle: i18n.global.t('pageSessions.action.disconnect'),
272            cancelTitle: i18n.global.t('global.action.cancel'),
273            autoFocusButton: 'ok',
274          },
275        ).then((deleteConfirmed) => {
276          if (deleteConfirmed) this.disconnectSessions([uri]);
277        });
278      }
279    },
280    onBatchAction(action) {
281      if (action === 'disconnect') {
282        const uris = this.selectedRows.map((row) => row.uri);
283        const count = this.selectedRows.length;
284        this.confirmDialog(
285          i18n.global.t('pageSessions.modal.disconnectMessage', count),
286          {
287            title: i18n.global.t('pageSessions.modal.disconnectTitle', count),
288            okTitle: i18n.global.t('pageSessions.action.disconnect'),
289            cancelTitle: i18n.global.t('global.action.cancel'),
290            autoFocusButton: 'ok',
291          },
292        ).then((deleteConfirmed) => {
293          if (deleteConfirmed) {
294            this.disconnectSessions(uris);
295          }
296        });
297      }
298    },
299    confirmDialog(message, options = {}) {
300      return this.$confirm({ message, ...options });
301    },
302  },
303};
304</script>
305<style lang="scss">
306#table-session-logs {
307  td .btn-link {
308    width: auto !important;
309  }
310}
311</style>
312