@flightctl/ui-components 0.0.5 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/dist/src/components/Device/DeviceDetails/DeviceDetailsPage.d.ts +3 -1
  2. package/dist/src/components/Device/DeviceDetails/DeviceDetailsPage.d.ts.map +1 -1
  3. package/dist/src/components/Device/DeviceDetails/DeviceDetailsPage.js +2 -2
  4. package/dist/src/components/Device/DeviceDetails/DeviceDetailsPage.js.map +1 -1
  5. package/dist/src/components/Device/DevicesPage/DeviceTableToolbar.d.ts +2 -2
  6. package/dist/src/components/Device/DevicesPage/DeviceTableToolbar.d.ts.map +1 -1
  7. package/dist/src/components/Device/DevicesPage/DeviceTableToolbar.js +8 -8
  8. package/dist/src/components/Device/DevicesPage/DeviceTableToolbar.js.map +1 -1
  9. package/dist/src/components/Device/DevicesPage/DeviceToolbarFilters.d.ts +3 -3
  10. package/dist/src/components/Device/DevicesPage/DeviceToolbarFilters.d.ts.map +1 -1
  11. package/dist/src/components/Device/DevicesPage/DeviceToolbarFilters.js +23 -3
  12. package/dist/src/components/Device/DevicesPage/DeviceToolbarFilters.js.map +1 -1
  13. package/dist/src/components/Device/DevicesPage/DevicesPage.d.ts +7 -1
  14. package/dist/src/components/Device/DevicesPage/DevicesPage.d.ts.map +1 -1
  15. package/dist/src/components/Device/DevicesPage/DevicesPage.js +23 -23
  16. package/dist/src/components/Device/DevicesPage/DevicesPage.js.map +1 -1
  17. package/dist/src/components/Device/DevicesPage/EnrollmentRequestList.d.ts +0 -7
  18. package/dist/src/components/Device/DevicesPage/EnrollmentRequestList.d.ts.map +1 -1
  19. package/dist/src/components/Device/DevicesPage/EnrollmentRequestList.js +42 -51
  20. package/dist/src/components/Device/DevicesPage/EnrollmentRequestList.js.map +1 -1
  21. package/dist/src/components/Device/DevicesPage/useDeviceBackendFilters.d.ts +2 -0
  22. package/dist/src/components/Device/DevicesPage/useDeviceBackendFilters.d.ts.map +1 -1
  23. package/dist/src/components/Device/DevicesPage/useDeviceBackendFilters.js +10 -3
  24. package/dist/src/components/Device/DevicesPage/useDeviceBackendFilters.js.map +1 -1
  25. package/dist/src/components/Device/DevicesPage/useDevices.d.ts +10 -4
  26. package/dist/src/components/Device/DevicesPage/useDevices.d.ts.map +1 -1
  27. package/dist/src/components/Device/DevicesPage/useDevices.js +25 -26
  28. package/dist/src/components/Device/DevicesPage/useDevices.js.map +1 -1
  29. package/dist/src/components/EnrollmentRequest/PendingEnrollmentRequestsBadge.css +10 -0
  30. package/dist/src/components/EnrollmentRequest/PendingEnrollmentRequestsBadge.d.ts +5 -0
  31. package/dist/src/components/EnrollmentRequest/PendingEnrollmentRequestsBadge.d.ts.map +1 -0
  32. package/dist/src/components/EnrollmentRequest/PendingEnrollmentRequestsBadge.js +22 -0
  33. package/dist/src/components/EnrollmentRequest/PendingEnrollmentRequestsBadge.js.map +1 -0
  34. package/dist/src/components/Fleet/FleetDetails/FleetDevices.d.ts.map +1 -1
  35. package/dist/src/components/Fleet/FleetDetails/FleetDevices.js +7 -7
  36. package/dist/src/components/Fleet/FleetDetails/FleetDevices.js.map +1 -1
  37. package/dist/src/components/Fleet/FleetsPage.d.ts.map +1 -1
  38. package/dist/src/components/Fleet/FleetsPage.js +17 -23
  39. package/dist/src/components/Fleet/FleetsPage.js.map +1 -1
  40. package/dist/src/components/Fleet/useFleets.d.ts +18 -0
  41. package/dist/src/components/Fleet/useFleets.d.ts.map +1 -0
  42. package/dist/src/components/Fleet/useFleets.js +61 -0
  43. package/dist/src/components/Fleet/useFleets.js.map +1 -0
  44. package/dist/src/components/OverviewPage/Cards/Status/StatusCard.d.ts.map +1 -1
  45. package/dist/src/components/OverviewPage/Cards/Status/StatusCard.js +4 -3
  46. package/dist/src/components/OverviewPage/Cards/Status/StatusCard.js.map +1 -1
  47. package/dist/src/components/OverviewPage/Cards/ToDo/ToDoCard.d.ts.map +1 -1
  48. package/dist/src/components/OverviewPage/Cards/ToDo/ToDoCard.js +5 -9
  49. package/dist/src/components/OverviewPage/Cards/ToDo/ToDoCard.js.map +1 -1
  50. package/dist/src/components/OverviewPage/Overview.js +1 -1
  51. package/dist/src/components/OverviewPage/Overview.js.map +1 -1
  52. package/dist/src/components/Repository/CreateRepository/CreateRepository.js +2 -2
  53. package/dist/src/components/Repository/CreateRepository/CreateRepository.js.map +1 -1
  54. package/dist/src/components/Repository/RepositoryDetails/DeleteRepositoryModal.js +1 -1
  55. package/dist/src/components/Repository/RepositoryDetails/DeleteRepositoryModal.js.map +1 -1
  56. package/dist/src/components/Repository/RepositoryList.d.ts.map +1 -1
  57. package/dist/src/components/Repository/RepositoryList.js +1 -1
  58. package/dist/src/components/Repository/RepositoryList.js.map +1 -1
  59. package/dist/src/components/ResourceSync/RepositoryResourceSyncList.d.ts.map +1 -1
  60. package/dist/src/components/ResourceSync/RepositoryResourceSyncList.js +2 -2
  61. package/dist/src/components/ResourceSync/RepositoryResourceSyncList.js.map +1 -1
  62. package/dist/src/components/Table/Table.d.ts +12 -2
  63. package/dist/src/components/Table/Table.d.ts.map +1 -1
  64. package/dist/src/components/Table/Table.js +3 -3
  65. package/dist/src/components/Table/Table.js.map +1 -1
  66. package/dist/src/components/charts/DonutChart.css +5 -0
  67. package/dist/src/components/charts/DonutChart.js +1 -1
  68. package/dist/src/components/charts/DonutChart.js.map +1 -1
  69. package/dist/src/components/common/LeaveFormConfirmation.js +1 -1
  70. package/dist/src/components/common/LeaveFormConfirmation.js.map +1 -1
  71. package/dist/src/components/modals/massModals/MassDeleteRepositoryModal/MassDeleteRepositoryModal.d.ts.map +1 -1
  72. package/dist/src/components/modals/massModals/MassDeleteRepositoryModal/MassDeleteRepositoryModal.js +2 -2
  73. package/dist/src/components/modals/massModals/MassDeleteRepositoryModal/MassDeleteRepositoryModal.js.map +1 -1
  74. package/dist/src/hooks/useApiTableSort.d.ts +8 -0
  75. package/dist/src/hooks/useApiTableSort.d.ts.map +1 -0
  76. package/dist/src/hooks/useApiTableSort.js +44 -0
  77. package/dist/src/hooks/useApiTableSort.js.map +1 -0
  78. package/dist/src/hooks/useAppContext.d.ts +4 -4
  79. package/dist/src/hooks/useAppContext.d.ts.map +1 -1
  80. package/dist/src/hooks/useNavigate.d.ts +3 -3
  81. package/dist/src/hooks/useNavigate.d.ts.map +1 -1
  82. package/dist/src/hooks/useNavigate.js +3 -3
  83. package/dist/src/hooks/useNavigate.js.map +1 -1
  84. package/dist/src/hooks/usePendingEnrollmentRequestsCount.d.ts +2 -0
  85. package/dist/src/hooks/usePendingEnrollmentRequestsCount.d.ts.map +1 -0
  86. package/dist/src/hooks/usePendingEnrollmentRequestsCount.js +13 -0
  87. package/dist/src/hooks/usePendingEnrollmentRequestsCount.js.map +1 -0
  88. package/dist/src/utils/query.d.ts +6 -0
  89. package/dist/src/utils/query.d.ts.map +1 -0
  90. package/dist/src/utils/query.js +32 -0
  91. package/dist/src/utils/query.js.map +1 -0
  92. package/dist/src/utils/sort/generic.d.ts +1 -4
  93. package/dist/src/utils/sort/generic.d.ts.map +1 -1
  94. package/dist/src/utils/sort/generic.js +1 -28
  95. package/dist/src/utils/sort/generic.js.map +1 -1
  96. package/dist/src/utils/status/devices.d.ts +2 -1
  97. package/dist/src/utils/status/devices.d.ts.map +1 -1
  98. package/dist/src/utils/status/devices.js +1 -0
  99. package/dist/src/utils/status/devices.js.map +1 -1
  100. package/package.json +6 -6
  101. package/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx +2 -2
  102. package/src/components/Device/DevicesPage/DeviceTableToolbar.tsx +13 -13
  103. package/src/components/Device/DevicesPage/DeviceToolbarFilters.tsx +35 -8
  104. package/src/components/Device/DevicesPage/DevicesPage.tsx +41 -27
  105. package/src/components/Device/DevicesPage/EnrollmentRequestList.tsx +91 -116
  106. package/src/components/Device/DevicesPage/useDeviceBackendFilters.ts +14 -3
  107. package/src/components/Device/DevicesPage/useDevices.ts +43 -32
  108. package/src/components/EnrollmentRequest/PendingEnrollmentRequestsBadge.css +10 -0
  109. package/src/components/EnrollmentRequest/PendingEnrollmentRequestsBadge.tsx +27 -0
  110. package/src/components/Fleet/FleetDetails/FleetDevices.tsx +12 -18
  111. package/src/components/Fleet/FleetsPage.tsx +39 -28
  112. package/src/components/Fleet/useFleets.ts +86 -0
  113. package/src/components/OverviewPage/Cards/Status/StatusCard.tsx +4 -3
  114. package/src/components/OverviewPage/Cards/ToDo/ToDoCard.tsx +6 -10
  115. package/src/components/OverviewPage/Overview.tsx +1 -1
  116. package/src/components/Repository/CreateRepository/CreateRepository.tsx +2 -2
  117. package/src/components/Repository/RepositoryDetails/DeleteRepositoryModal.tsx +1 -1
  118. package/src/components/Repository/RepositoryList.tsx +1 -0
  119. package/src/components/ResourceSync/RepositoryResourceSyncList.tsx +2 -1
  120. package/src/components/Table/Table.tsx +19 -5
  121. package/src/components/charts/DonutChart.css +5 -0
  122. package/src/components/charts/DonutChart.tsx +1 -1
  123. package/src/components/modals/massModals/MassDeleteRepositoryModal/MassDeleteRepositoryModal.tsx +4 -2
  124. package/src/hooks/useApiTableSort.ts +49 -0
  125. package/src/hooks/useNavigate.tsx +3 -3
  126. package/src/hooks/usePendingEnrollmentRequestsCount.ts +12 -0
  127. package/src/utils/query.ts +29 -0
  128. package/src/utils/sort/generic.ts +1 -30
  129. package/src/utils/status/devices.ts +1 -0
  130. package/dist/src/components/Device/DevicesPage/useDeviceFilters.d.ts +0 -8
  131. package/dist/src/components/Device/DevicesPage/useDeviceFilters.d.ts.map +0 -1
  132. package/dist/src/components/Device/DevicesPage/useDeviceFilters.js +0 -16
  133. package/dist/src/components/Device/DevicesPage/useDeviceFilters.js.map +0 -1
  134. package/dist/src/utils/sort/device.d.ts +0 -4
  135. package/dist/src/utils/sort/device.d.ts.map +0 -1
  136. package/dist/src/utils/sort/device.js +0 -49
  137. package/dist/src/utils/sort/device.js.map +0 -1
  138. package/dist/src/utils/sort/fleet.d.ts +0 -4
  139. package/dist/src/utils/sort/fleet.d.ts.map +0 -1
  140. package/dist/src/utils/sort/fleet.js +0 -18
  141. package/dist/src/utils/sort/fleet.js.map +0 -1
  142. package/src/components/Device/DevicesPage/useDeviceFilters.ts +0 -15
  143. package/src/utils/sort/device.ts +0 -60
  144. package/src/utils/sort/fleet.ts +0 -16
@@ -8,7 +8,7 @@ import {
8
8
  PageSectionVariants,
9
9
  ToolbarItem,
10
10
  } from '@patternfly/react-core';
11
- import { Tbody } from '@patternfly/react-table';
11
+ import { Tbody, ThProps } from '@patternfly/react-table';
12
12
  import { MicrochipIcon } from '@patternfly/react-icons/dist/js/icons/microchip-icon';
13
13
  import { Trans } from 'react-i18next';
14
14
  import { TFunction } from 'i18next';
@@ -20,16 +20,12 @@ import ListPage from '../../ListPage/ListPage';
20
20
  import ListPageBody from '../../ListPage/ListPageBody';
21
21
  import { useDeleteListAction } from '../../ListPage/ListPageActions';
22
22
  import AddDeviceModal from '../AddDeviceModal/AddDeviceModal';
23
- import { sortByAlias, sortByLastSeenDate, sortByName } from '../../../utils/sort/generic';
24
- import { sortDeviceStatus, sortDevicesByFleet } from '../../../utils/sort/device';
25
- import Table, { TableColumn } from '../../Table/Table';
23
+ import Table, { ApiSortTableColumn } from '../../Table/Table';
26
24
  import DeviceTableToolbar from './DeviceTableToolbar';
27
- import { useDeviceFilters } from './useDeviceFilters';
28
25
  import DeviceTableRow from './DeviceTableRow';
29
26
  import { FlightCtlLabel } from '../../../types/extraTypes';
30
27
  import MassDeleteDeviceModal from '../../modals/massModals/MassDeleteDeviceModal/MassDeleteDeviceModal';
31
28
  import ResourceListEmptyState from '../../common/ResourceListEmptyState';
32
- import { useTableSort } from '../../../hooks/useTableSort';
33
29
  import { useTableSelect } from '../../../hooks/useTableSelect';
34
30
  import { useTranslation } from '../../../hooks/useTranslation';
35
31
  import { Link, ROUTE } from '../../../hooks/useNavigate';
@@ -40,9 +36,10 @@ import {
40
36
  getDeviceStatusHelperText,
41
37
  getUpdateStatusHelperText,
42
38
  } from '../../Status/utils';
39
+ import EnrollmentRequestList from './EnrollmentRequestList';
43
40
  import { FilterStatusMap } from './types';
44
41
  import { useFetchPeriodically } from '../../../hooks/useFetchPeriodically';
45
- import EnrollmentRequestList from './EnrollmentRequestList';
42
+ import { useApiTableSort } from '../../../hooks/useApiTableSort';
46
43
 
47
44
  type DeviceEmptyStateProps = {
48
45
  onAddDevice: VoidFunction;
@@ -67,38 +64,39 @@ const DeviceEmptyState: React.FC<DeviceEmptyStateProps> = ({ onAddDevice }) => {
67
64
  );
68
65
  };
69
66
 
70
- const getDeviceColumns = (t: TFunction): TableColumn<Device>[] => [
67
+ const getDeviceColumns = (t: TFunction): ApiSortTableColumn[] => [
71
68
  {
72
69
  name: t('Alias'),
73
- onSort: sortByAlias,
70
+ // Sorting works on this field even though "alias" is actually a label
71
+ sortableField: 'metadata.alias',
74
72
  },
75
73
  {
76
74
  name: t('Name'),
77
- onSort: sortByName,
75
+ sortableField: 'metadata.name',
78
76
  },
79
77
  {
80
78
  name: t('Fleet'),
81
- onSort: sortDevicesByFleet,
79
+ sortableField: 'metadata.owner',
82
80
  },
83
81
  {
84
82
  name: t('Application status'),
85
83
  helperText: getApplicationStatusHelperText(t),
86
- onSort: (devices: Array<Device>) => sortDeviceStatus(devices, 'ApplicationStatus'),
84
+ sortableField: 'status.applicationsSummary.status',
87
85
  },
88
86
  {
89
87
  name: t('Device status'),
90
88
  helperText: getDeviceStatusHelperText(t),
91
- onSort: (devices: Array<Device>) => sortDeviceStatus(devices, 'DeviceStatus'),
89
+ sortableField: 'status.summary.status',
92
90
  defaultSort: true,
93
91
  },
94
92
  {
95
93
  name: t('Update status'),
96
94
  helperText: getUpdateStatusHelperText(t),
97
- onSort: (devices: Array<Device>) => sortDeviceStatus(devices, 'SystemUpdateStatus'),
95
+ sortableField: 'status.updated.status',
98
96
  },
99
97
  {
100
98
  name: t('Last seen'),
101
- onSort: sortByLastSeenDate,
99
+ sortableField: 'status.lastSeen',
102
100
  },
103
101
  ];
104
102
 
@@ -108,6 +106,8 @@ interface DeviceTableProps {
108
106
  ownerFleets: string[];
109
107
  activeStatuses: FilterStatusMap;
110
108
  hasFiltersEnabled: boolean;
109
+ nameOrAlias: string | undefined;
110
+ setNameOrAlias: (text: string) => void;
111
111
  setOwnerFleets: (ownerFleets: string[]) => void;
112
112
  setActiveStatuses: (activeStatuses: FilterStatusMap) => void;
113
113
  allLabels: FlightCtlLabel[];
@@ -115,11 +115,15 @@ interface DeviceTableProps {
115
115
  setSelectedLabels: (labels: FlightCtlLabel[]) => void;
116
116
  fleets: Fleet[];
117
117
  isFilterUpdating: boolean;
118
+ deviceColumns: ApiSortTableColumn[];
119
+ getSortParams: (columnIndex: number) => ThProps['sort'];
118
120
  }
119
121
 
120
122
  export const DeviceTable = ({
121
123
  devices,
122
124
  refetch,
125
+ nameOrAlias,
126
+ setNameOrAlias,
123
127
  ownerFleets,
124
128
  setOwnerFleets,
125
129
  activeStatuses,
@@ -130,17 +134,14 @@ export const DeviceTable = ({
130
134
  hasFiltersEnabled,
131
135
  fleets,
132
136
  isFilterUpdating,
137
+ deviceColumns,
138
+ getSortParams,
133
139
  }: DeviceTableProps) => {
134
140
  const { t } = useTranslation();
135
141
  const [addDeviceModal, setAddDeviceModal] = React.useState(false);
136
142
  const [isMassDeleteModalOpen, setIsMassDeleteModalOpen] = React.useState(false);
137
143
  const { remove } = useFetch();
138
144
 
139
- const deviceColumns = React.useMemo(() => getDeviceColumns(t), [t]);
140
-
141
- const { filteredData, hasFiltersEnabled: hasUIFiltersEnabled, ...rest } = useDeviceFilters(devices);
142
- const { getSortParams, sortedData } = useTableSort(filteredData, deviceColumns);
143
-
144
145
  const { onRowSelect, hasSelectedRows, isAllSelected, isRowSelected, setAllSelected } = useTableSelect();
145
146
 
146
147
  const { deleteAction: deleteDeviceAction, deleteModal: deleteDeviceModal } = useDeleteListAction({
@@ -154,7 +155,8 @@ export const DeviceTable = ({
154
155
  return (
155
156
  <>
156
157
  <DeviceTableToolbar
157
- {...rest}
158
+ nameOrAlias={nameOrAlias}
159
+ setNameOrAlias={setNameOrAlias}
158
160
  ownerFleets={ownerFleets}
159
161
  setOwnerFleets={setOwnerFleets}
160
162
  activeStatuses={activeStatuses}
@@ -176,15 +178,16 @@ export const DeviceTable = ({
176
178
  </DeviceTableToolbar>
177
179
  <Table
178
180
  aria-label={t('Devices table')}
181
+ loading={isFilterUpdating}
179
182
  columns={deviceColumns}
180
- emptyFilters={filteredData.length === 0 && (hasFiltersEnabled || hasUIFiltersEnabled)}
183
+ emptyFilters={!hasFiltersEnabled}
181
184
  emptyData={devices.length === 0}
182
185
  getSortParams={getSortParams}
183
186
  isAllSelected={isAllSelected}
184
187
  onSelectAll={setAllSelected}
185
188
  >
186
189
  <Tbody>
187
- {sortedData.map((device, index) => (
190
+ {devices.map((device, index) => (
188
191
  <DeviceTableRow
189
192
  key={device.metadata.name || ''}
190
193
  device={device}
@@ -196,15 +199,13 @@ export const DeviceTable = ({
196
199
  ))}
197
200
  </Tbody>
198
201
  </Table>
199
- {!hasFiltersEnabled && !hasUIFiltersEnabled && devices.length === 0 && (
200
- <DeviceEmptyState onAddDevice={() => setAddDeviceModal(true)} />
201
- )}
202
+ {!hasFiltersEnabled && devices.length === 0 && <DeviceEmptyState onAddDevice={() => setAddDeviceModal(true)} />}
202
203
  {deleteDeviceModal}
203
204
  {addDeviceModal && <AddDeviceModal onClose={() => setAddDeviceModal(false)} />}
204
205
  {isMassDeleteModalOpen && (
205
206
  <MassDeleteDeviceModal
206
207
  onClose={() => setIsMassDeleteModalOpen(false)}
207
- resources={sortedData.filter(isRowSelected)}
208
+ resources={devices.filter(isRowSelected)}
208
209
  onDeleteSuccess={() => {
209
210
  setIsMassDeleteModalOpen(false);
210
211
  refetch();
@@ -217,7 +218,11 @@ export const DeviceTable = ({
217
218
 
218
219
  const DevicesPage = () => {
219
220
  const { t } = useTranslation();
221
+ const deviceColumns = React.useMemo(() => getDeviceColumns(t), [t]);
222
+
220
223
  const {
224
+ nameOrAlias,
225
+ setNameOrAlias,
221
226
  ownerFleets,
222
227
  activeStatuses,
223
228
  hasFiltersEnabled,
@@ -226,10 +231,15 @@ const DevicesPage = () => {
226
231
  selectedLabels,
227
232
  setSelectedLabels,
228
233
  } = useDeviceBackendFilters();
234
+ const { getSortParams, sortField, direction } = useApiTableSort(deviceColumns);
235
+
229
236
  const [data, loading, error, updating, refetch, allLabels] = useDevices({
237
+ nameOrAlias,
230
238
  ownerFleets,
231
239
  activeStatuses,
232
240
  labels: selectedLabels,
241
+ sortField,
242
+ direction,
233
243
  });
234
244
 
235
245
  const [fleetsList, flLoading, flError] = useFetchPeriodically<FleetList>({
@@ -246,6 +256,8 @@ const DevicesPage = () => {
246
256
  devices={data}
247
257
  allLabels={allLabels}
248
258
  refetch={refetch}
259
+ nameOrAlias={nameOrAlias}
260
+ setNameOrAlias={setNameOrAlias}
249
261
  hasFiltersEnabled={hasFiltersEnabled || updating}
250
262
  ownerFleets={ownerFleets}
251
263
  activeStatuses={activeStatuses}
@@ -255,6 +267,8 @@ const DevicesPage = () => {
255
267
  setSelectedLabels={setSelectedLabels}
256
268
  fleets={fleetsList?.items || []}
257
269
  isFilterUpdating={updating}
270
+ deviceColumns={deviceColumns}
271
+ getSortParams={getSortParams}
258
272
  />
259
273
  </ListPageBody>
260
274
  </ListPage>
@@ -5,7 +5,7 @@ import { SelectList, SelectOption, Spinner, ToolbarItem } from '@patternfly/reac
5
5
 
6
6
  import { EnrollmentRequest, EnrollmentRequestList as EnrollmentRequestListType } from '@flightctl/types';
7
7
 
8
- import Table, { TableColumn } from '../../Table/Table';
8
+ import Table, { ApiSortTableColumn } from '../../Table/Table';
9
9
  import TableActions from '../../Table/TableActions';
10
10
  import ListPage from '../../ListPage/ListPage';
11
11
  import ListPageBody from '../../ListPage/ListPageBody';
@@ -14,9 +14,8 @@ import { useFetch } from '../../../hooks/useFetch';
14
14
  import { useTranslation } from '../../../hooks/useTranslation';
15
15
  import { useFetchPeriodically } from '../../../hooks/useFetchPeriodically';
16
16
  import { useTableSelect } from '../../../hooks/useTableSelect';
17
- import { useTableSort } from '../../../hooks/useTableSort';
17
+ import { useApiTableSort } from '../../../hooks/useApiTableSort';
18
18
  import { useTableTextSearch } from '../../../hooks/useTableTextSearch';
19
- import { sortByCreationDate, sortByName } from '../../../utils/sort/generic';
20
19
 
21
20
  import ApproveDeviceModal from '../../modals/ApproveDeviceModal/ApproveDeviceModal';
22
21
  import MassDeleteDeviceModal from '../../modals/massModals/MassDeleteDeviceModal/MassDeleteDeviceModal';
@@ -24,41 +23,41 @@ import MassApproveDeviceModal from '../../modals/massModals/MassApproveDeviceMod
24
23
  import EnrollmentRequestTableRow from '../../EnrollmentRequest/EnrollmentRequestTableRow';
25
24
  import EnrollmentRequestTableToolbar from './EnrollmentRequestTableToolbar';
26
25
 
27
- const getEnrollmentColumns = (t: TFunction): TableColumn<EnrollmentRequest>[] => [
26
+ const getEnrollmentColumns = (t: TFunction): ApiSortTableColumn[] => [
28
27
  {
29
28
  name: t('Name'),
30
- onSort: sortByName,
29
+ sortableField: 'metadata.name',
30
+ defaultSort: true,
31
31
  },
32
32
  {
33
33
  name: t('Created'),
34
- onSort: sortByCreationDate,
34
+ sortableField: 'metadata.creationTimestamp',
35
35
  },
36
36
  ];
37
37
 
38
- interface EnrollmentRequestTableProps {
39
- pendingEnrollments: Array<EnrollmentRequest>;
40
- approveRefetch: VoidFunction;
41
- deleteRefetch: VoidFunction;
42
- }
43
-
44
38
  const getSearchText = (er: EnrollmentRequest) => [er.metadata.name];
45
39
 
46
- export const EnrollmentRequestTable = ({
47
- pendingEnrollments,
48
- approveRefetch,
49
- deleteRefetch,
50
- }: EnrollmentRequestTableProps) => {
40
+ const EnrollmentRequestList = ({ refetchDevices }: { refetchDevices: VoidFunction }) => {
51
41
  const { t } = useTranslation();
52
42
  const { remove } = useFetch();
43
+ const enrollmentColumns = React.useMemo(() => getEnrollmentColumns(t), [t]);
44
+ const { getSortParams, sortField, direction } = useApiTableSort(enrollmentColumns);
45
+
46
+ const [erList, isLoading, error, refetch] = useFetchPeriodically<EnrollmentRequestListType>({
47
+ endpoint: `enrollmentrequests?fieldSelector=!status.approval.approved${sortField ? `&sortBy=${sortField}&sortOrder=${direction}` : ''}`,
48
+ });
49
+ const pendingEnrollments = erList?.items || [];
50
+
51
+ const refetchWithDevices = () => {
52
+ refetch();
53
+ refetchDevices();
54
+ };
53
55
 
54
56
  const [approvingErId, setApprovingErId] = React.useState<string>();
55
57
  const [isMassDeleteModalOpen, setIsMassDeleteModalOpen] = React.useState(false);
56
58
  const [isMassApproveModalOpen, setIsMassApproveModalOpen] = React.useState(false);
57
59
 
58
- const enrollmentColumns = React.useMemo(() => getEnrollmentColumns(t), [t]);
59
-
60
60
  const { search, setSearch, filteredData } = useTableTextSearch(pendingEnrollments, getSearchText);
61
- const { getSortParams, sortedData } = useTableSort(filteredData, enrollmentColumns);
62
61
 
63
62
  const { onRowSelect, hasSelectedRows, isAllSelected, isRowSelected, setAllSelected } = useTableSelect();
64
63
 
@@ -66,115 +65,91 @@ export const EnrollmentRequestTable = ({
66
65
  resourceType: 'EnrollmentRequest',
67
66
  onDelete: async (enrollmentId: string) => {
68
67
  await remove(`enrollmentrequests/${enrollmentId}`);
69
- deleteRefetch();
68
+ refetch();
70
69
  },
71
70
  });
72
71
 
73
- const currentEnrollmentRequest = pendingEnrollments.find((er) => er.metadata.name === approvingErId);
74
-
75
- return (
76
- <>
77
- <EnrollmentRequestTableToolbar search={search} setSearch={setSearch} enrollments={pendingEnrollments}>
78
- <ToolbarItem>
79
- <TableActions isDisabled={!hasSelectedRows}>
80
- <SelectList>
81
- <SelectOption onClick={() => setIsMassApproveModalOpen(true)}>{t('Approve')}</SelectOption>
82
- <SelectOption onClick={() => setIsMassDeleteModalOpen(true)}>{t('Delete')}</SelectOption>
83
- </SelectList>
84
- </TableActions>
85
- </ToolbarItem>
86
- </EnrollmentRequestTableToolbar>
87
- <Table
88
- aria-label={t('Table for devices pending approval')}
89
- columns={enrollmentColumns}
90
- emptyFilters={filteredData.length === 0}
91
- emptyData={false}
92
- getSortParams={getSortParams}
93
- isAllSelected={isAllSelected}
94
- onSelectAll={setAllSelected}
95
- >
96
- <Tbody>
97
- {sortedData.map((er, index) => (
98
- <EnrollmentRequestTableRow
99
- key={er.metadata.name || ''}
100
- er={er}
101
- deleteAction={deleteAction}
102
- onRowSelect={onRowSelect}
103
- isRowSelected={isRowSelected}
104
- rowIndex={index}
105
- onApprove={() => {
106
- setApprovingErId(er.metadata.name as string);
107
- }}
108
- />
109
- ))}
110
- </Tbody>
111
- </Table>
112
-
113
- {deleteModal}
114
- {currentEnrollmentRequest && (
115
- <ApproveDeviceModal
116
- enrollmentRequest={currentEnrollmentRequest}
117
- onClose={(updateList) => {
118
- setApprovingErId(undefined);
119
- updateList && approveRefetch();
120
- }}
121
- />
122
- )}
123
- {isMassDeleteModalOpen && (
124
- <MassDeleteDeviceModal
125
- onClose={() => setIsMassDeleteModalOpen(false)}
126
- resources={filteredData.filter(isRowSelected)}
127
- onDeleteSuccess={() => {
128
- setIsMassDeleteModalOpen(false);
129
- deleteRefetch();
130
- }}
131
- />
132
- )}
133
- {isMassApproveModalOpen && (
134
- <MassApproveDeviceModal
135
- onClose={() => setIsMassApproveModalOpen(false)}
136
- pendingEnrollments={filteredData.filter(isRowSelected)}
137
- onApproveSuccess={() => {
138
- setAllSelected(false);
139
- setIsMassApproveModalOpen(false);
140
- approveRefetch();
141
- }}
142
- />
143
- )}
144
- </>
145
- );
146
- };
147
-
148
- const EnrollmentRequestList = ({ refetchDevices }: { refetchDevices: VoidFunction }) => {
149
- const { t } = useTranslation();
150
- const [erList, isLoading, error, refetch] = useFetchPeriodically<EnrollmentRequestListType>({
151
- endpoint: 'enrollmentrequests',
152
- });
153
-
154
72
  if (isLoading) {
155
73
  return <Spinner size="md" />;
156
74
  }
157
75
 
158
- // The content only appears if there are pending enrollment requests
159
- // TODO move the filter as part of the query once it's available via the API
160
- const pendingEnrollments = (erList?.items || []).filter((er) => er.status?.approval?.approved !== true);
161
-
162
76
  if (pendingEnrollments.length === 0) {
163
77
  return null;
164
78
  }
165
79
 
166
- const approveRefetch = () => {
167
- refetch();
168
- refetchDevices();
169
- };
80
+ const currentEnrollmentRequest = pendingEnrollments.find((er) => er.metadata.name === approvingErId);
81
+
170
82
  return (
171
83
  <ListPage title={t('Devices pending approval')} headingLevel="h2">
172
- <ListPageBody error={error} loading={false}>
173
- <EnrollmentRequestTable
174
- pendingEnrollments={pendingEnrollments}
175
- approveRefetch={approveRefetch}
176
- deleteRefetch={refetch}
177
- />
84
+ <ListPageBody error={error} loading={isLoading}>
85
+ <EnrollmentRequestTableToolbar search={search} setSearch={setSearch} enrollments={pendingEnrollments}>
86
+ <ToolbarItem>
87
+ <TableActions isDisabled={!hasSelectedRows}>
88
+ <SelectList>
89
+ <SelectOption onClick={() => setIsMassApproveModalOpen(true)}>{t('Approve')}</SelectOption>
90
+ <SelectOption onClick={() => setIsMassDeleteModalOpen(true)}>{t('Delete')}</SelectOption>
91
+ </SelectList>
92
+ </TableActions>
93
+ </ToolbarItem>
94
+ </EnrollmentRequestTableToolbar>
95
+ <Table
96
+ aria-label={t('Table for devices pending approval')}
97
+ loading={isLoading}
98
+ columns={enrollmentColumns}
99
+ emptyFilters={filteredData.length === 0}
100
+ emptyData={false}
101
+ getSortParams={getSortParams}
102
+ isAllSelected={isAllSelected}
103
+ onSelectAll={setAllSelected}
104
+ >
105
+ <Tbody>
106
+ {pendingEnrollments.map((er, index) => (
107
+ <EnrollmentRequestTableRow
108
+ key={er.metadata.name || ''}
109
+ er={er}
110
+ deleteAction={deleteAction}
111
+ onRowSelect={onRowSelect}
112
+ isRowSelected={isRowSelected}
113
+ rowIndex={index}
114
+ onApprove={() => {
115
+ setApprovingErId(er.metadata.name as string);
116
+ }}
117
+ />
118
+ ))}
119
+ </Tbody>
120
+ </Table>
121
+
122
+ {deleteModal}
123
+ {currentEnrollmentRequest && (
124
+ <ApproveDeviceModal
125
+ enrollmentRequest={currentEnrollmentRequest}
126
+ onClose={(updateList) => {
127
+ setApprovingErId(undefined);
128
+ updateList && refetchWithDevices();
129
+ }}
130
+ />
131
+ )}
132
+ {isMassDeleteModalOpen && (
133
+ <MassDeleteDeviceModal
134
+ onClose={() => setIsMassDeleteModalOpen(false)}
135
+ resources={filteredData.filter(isRowSelected)}
136
+ onDeleteSuccess={() => {
137
+ setIsMassDeleteModalOpen(false);
138
+ refetch();
139
+ }}
140
+ />
141
+ )}
142
+ {isMassApproveModalOpen && (
143
+ <MassApproveDeviceModal
144
+ onClose={() => setIsMassApproveModalOpen(false)}
145
+ pendingEnrollments={filteredData.filter(isRowSelected)}
146
+ onApproveSuccess={() => {
147
+ setAllSelected(false);
148
+ setIsMassApproveModalOpen(false);
149
+ refetchWithDevices();
150
+ }}
151
+ />
152
+ )}
178
153
  </ListPageBody>
179
154
  </ListPage>
180
155
  );
@@ -3,7 +3,6 @@ import { ApplicationsSummaryStatusType, DeviceSummaryStatusType, DeviceUpdatedSt
3
3
 
4
4
  import { FilterSearchParams } from '../../../utils/status/devices';
5
5
  import { useAppContext } from '../../../hooks/useAppContext';
6
- import { EnrollmentRequestStatus } from '../../../utils/status/enrollmentRequest';
7
6
  import { FilterStatusMap } from './types';
8
7
  import { FlightCtlLabel } from '../../../types/extraTypes';
9
8
  import { labelToString } from '../../../utils/labels';
@@ -11,7 +10,6 @@ import { labelToString } from '../../../utils/labels';
11
10
  const validAppStatuses = Object.values(ApplicationsSummaryStatusType) as string[];
12
11
  const validUpdatedStatuses = Object.values(DeviceUpdatedStatusType) as string[];
13
12
  const validDeviceStatuses = Object.values(DeviceSummaryStatusType) as string[];
14
- validDeviceStatuses.push(EnrollmentRequestStatus.Pending);
15
13
 
16
14
  const getNewParams = (currentParams: URLSearchParams, newValues: { [key: string]: string[] }) => {
17
15
  let newParams = [...currentParams.entries()];
@@ -30,6 +28,7 @@ export const useDeviceBackendFilters = () => {
30
28
  const [searchParams, setSearchParams] = useSearchParams();
31
29
  const paramsRef = React.useRef(searchParams);
32
30
  const ownerFleets = searchParams.getAll(FilterSearchParams.Fleet) || undefined;
31
+ const nameOrAlias = searchParams.get(FilterSearchParams.NameOrAlias) || undefined;
33
32
 
34
33
  const updateSearchParams = React.useCallback(
35
34
  (params: [string, string][]) => {
@@ -104,10 +103,22 @@ export const useDeviceBackendFilters = () => {
104
103
  [updateSearchParams],
105
104
  );
106
105
 
106
+ const setNameOrAlias = React.useCallback(
107
+ (nameOrAlias: string) => {
108
+ updateSearchParams(getNewParams(paramsRef.current, { [FilterSearchParams.NameOrAlias]: [nameOrAlias] }));
109
+ },
110
+ [updateSearchParams],
111
+ );
112
+
107
113
  const hasFiltersEnabled =
108
- !!selectedLabels.length || !!ownerFleets.length || Object.values(activeStatuses).some((s) => !!s.length);
114
+ !!nameOrAlias ||
115
+ !!selectedLabels.length ||
116
+ !!ownerFleets.length ||
117
+ Object.values(activeStatuses).some((s) => !!s.length);
109
118
 
110
119
  return {
120
+ nameOrAlias,
121
+ setNameOrAlias,
111
122
  activeStatuses,
112
123
  setActiveStatuses,
113
124
  ownerFleets,
@@ -1,53 +1,65 @@
1
- import React from 'react';
1
+ import * as React from 'react';
2
2
  import { useDebounce } from 'use-debounce';
3
3
 
4
- import { Device, DeviceList, DevicesSummary } from '@flightctl/types';
4
+ import { Device, DeviceList, DevicesSummary, SortOrder } from '@flightctl/types';
5
5
  import { FilterSearchParams } from '../../../utils/status/devices';
6
+ import * as queryUtils from '../../../utils/query';
7
+ import { fromAPILabel } from '../../../utils/labels';
6
8
  import { useFetchPeriodically } from '../../../hooks/useFetchPeriodically';
7
9
  import { FlightCtlLabel } from '../../../types/extraTypes';
8
10
  import { FilterStatusMap } from './types';
9
11
 
10
- import { fromAPILabel } from '../../../utils/labels';
11
-
12
- const setLabelParams = (params: URLSearchParams, labels?: FlightCtlLabel[]) => {
13
- if (labels?.length) {
14
- const labelSelector = labels.reduce((acc, curr) => {
15
- if (!acc) {
16
- acc = `${curr.key}=${curr.value || ''}`;
17
- } else {
18
- acc += `,${curr.key}=${curr.value || ''}`;
19
- }
20
- return acc;
21
- }, '');
22
- params.append('labelSelector', labelSelector);
23
- }
24
- };
25
-
26
12
  type DevicesEndpointArgs = {
13
+ nameOrAlias?: string;
27
14
  ownerFleets?: string[];
28
15
  activeStatuses?: FilterStatusMap;
29
16
  labels?: FlightCtlLabel[];
17
+ sortField?: string;
18
+ direction?: string;
30
19
  summaryOnly?: boolean;
31
20
  };
32
21
 
33
- const getDevicesEndpoint = ({ ownerFleets, activeStatuses, labels, summaryOnly }: DevicesEndpointArgs) => {
22
+ const getDevicesEndpoint = ({
23
+ nameOrAlias,
24
+ ownerFleets,
25
+ activeStatuses,
26
+ labels,
27
+ sortField,
28
+ direction,
29
+ summaryOnly,
30
+ }: DevicesEndpointArgs) => {
34
31
  const filterByAppStatus = activeStatuses?.[FilterSearchParams.AppStatus];
35
32
  const filterByDevStatus = activeStatuses?.[FilterSearchParams.DeviceStatus];
36
33
  const filterByUpdateStatus = activeStatuses?.[FilterSearchParams.UpdatedStatus];
37
34
 
38
- const params = new URLSearchParams();
35
+ const fieldSelectors: string[] = [];
36
+ queryUtils.addQueryConditions(fieldSelectors, 'status.applicationsSummary.status', filterByAppStatus);
37
+ queryUtils.addQueryConditions(fieldSelectors, 'status.summary.status', filterByDevStatus);
38
+ queryUtils.addQueryConditions(fieldSelectors, 'status.updated.status', filterByUpdateStatus);
39
+
40
+ if (nameOrAlias) {
41
+ queryUtils.addTextContainsCondition(fieldSelectors, 'metadata.nameoralias', nameOrAlias);
42
+ }
39
43
  if (ownerFleets?.length) {
40
- params.set('owner', ownerFleets.map((fleet) => `Fleet/${fleet}`).join(','));
44
+ queryUtils.addQueryConditions(
45
+ fieldSelectors,
46
+ 'metadata.owner',
47
+ ownerFleets.map((fleet) => `Fleet/${fleet}`),
48
+ );
41
49
  }
42
- filterByAppStatus?.forEach((appSt) => params.append('statusFilter', `applications.summary.status=${appSt}`));
43
- filterByDevStatus?.forEach((devSt) => params.append('statusFilter', `summary.status=${devSt}`));
44
- filterByUpdateStatus?.forEach((updSt) => params.append('statusFilter', `updated.status=${updSt}`));
45
-
46
- setLabelParams(params, labels);
47
50
 
51
+ const params = new URLSearchParams();
52
+ if (fieldSelectors.length > 0) {
53
+ params.set('fieldSelector', fieldSelectors.join(','));
54
+ }
55
+ queryUtils.setLabelParams(params, labels);
48
56
  if (summaryOnly) {
49
57
  params.set('summaryOnly', 'true');
50
58
  }
59
+ if (sortField) {
60
+ params.set('sortBy', sortField);
61
+ params.set('sortOrder', direction || SortOrder.ASC);
62
+ }
51
63
  return params.size ? `devices?${params.toString()}` : 'devices';
52
64
  };
53
65
 
@@ -72,19 +84,18 @@ export const useDevicesSummary = ({
72
84
  return [deviceList?.summary, listLoading];
73
85
  };
74
86
 
75
- export const useDevices = ({
76
- ownerFleets,
77
- activeStatuses,
78
- labels,
79
- }: {
87
+ export const useDevices = (args: {
88
+ nameOrAlias?: string;
80
89
  ownerFleets?: string[];
81
90
  activeStatuses?: FilterStatusMap;
82
91
  labels?: FlightCtlLabel[];
92
+ sortField?: string;
93
+ direction?: string;
83
94
  }): [Device[], boolean, unknown, boolean, VoidFunction, FlightCtlLabel[]] => {
84
95
  const [deviceLabelList] = useFetchPeriodically<DeviceList>({
85
96
  endpoint: 'devices',
86
97
  });
87
- const [devicesEndpoint, devicesDebouncing] = useDevicesEndpoint({ ownerFleets, activeStatuses, labels });
98
+ const [devicesEndpoint, devicesDebouncing] = useDevicesEndpoint(args);
88
99
  const [devicesList, devicesLoading, devicesError, devicesRefetch, updating] = useFetchPeriodically<DeviceList>({
89
100
  endpoint: devicesEndpoint,
90
101
  });
@@ -0,0 +1,10 @@
1
+
2
+ /* The default color is too light */
3
+ .fctl-pendingdevices-badge {
4
+ --pf-v5-c-badge--m-unread--BackgroundColor: var(--pf-v5-global--active-color--100)
5
+ }
6
+
7
+ /* For the dark theme, we need to put it back to the correct value */
8
+ .pf-v5-theme-dark .fctl-pendingdevices-badge {
9
+ --pf-v5-c-badge--m-unread--BackgroundColor: var(--pf-v5-global--active-color--300)
10
+ }