@flightctl/ui-components 0.9.2 → 0.10.0-rc1

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 (242) hide show
  1. package/dist/src/components/DetailsPage/DetailsPage.d.ts +2 -1
  2. package/dist/src/components/DetailsPage/DetailsPage.d.ts.map +1 -1
  3. package/dist/src/components/DetailsPage/DetailsPage.js +2 -1
  4. package/dist/src/components/DetailsPage/DetailsPage.js.map +1 -1
  5. package/dist/src/components/DetailsPage/DetailsPageActions.d.ts +10 -0
  6. package/dist/src/components/DetailsPage/DetailsPageActions.d.ts.map +1 -1
  7. package/dist/src/components/DetailsPage/DetailsPageActions.js +23 -1
  8. package/dist/src/components/DetailsPage/DetailsPageActions.js.map +1 -1
  9. package/dist/src/components/Device/DeviceDetails/DeviceDetailsPage.d.ts.map +1 -1
  10. package/dist/src/components/Device/DeviceDetails/DeviceDetailsPage.js +29 -3
  11. package/dist/src/components/Device/DeviceDetails/DeviceDetailsPage.js.map +1 -1
  12. package/dist/src/components/Device/DeviceDetails/DeviceDetailsTab.d.ts.map +1 -1
  13. package/dist/src/components/Device/DeviceDetails/DeviceDetailsTab.js +0 -4
  14. package/dist/src/components/Device/DeviceDetails/DeviceDetailsTab.js.map +1 -1
  15. package/dist/src/components/Device/DeviceDetails/DeviceDetailsTabContent/StatusContent.d.ts.map +1 -1
  16. package/dist/src/components/Device/DeviceDetails/DeviceDetailsTabContent/StatusContent.js +8 -2
  17. package/dist/src/components/Device/DeviceDetails/DeviceDetailsTabContent/StatusContent.js.map +1 -1
  18. package/dist/src/components/Device/DevicesPage/DecommissionedDeviceTableRow.d.ts.map +1 -1
  19. package/dist/src/components/Device/DevicesPage/DecommissionedDeviceTableRow.js +1 -3
  20. package/dist/src/components/Device/DevicesPage/DecommissionedDeviceTableRow.js.map +1 -1
  21. package/dist/src/components/Device/DevicesPage/DecommissionedDevicesTable.d.ts.map +1 -1
  22. package/dist/src/components/Device/DevicesPage/DecommissionedDevicesTable.js +0 -3
  23. package/dist/src/components/Device/DevicesPage/DecommissionedDevicesTable.js.map +1 -1
  24. package/dist/src/components/Device/DevicesPage/DeviceToolbarFilters.d.ts.map +1 -1
  25. package/dist/src/components/Device/DevicesPage/DeviceToolbarFilters.js +3 -1
  26. package/dist/src/components/Device/DevicesPage/DeviceToolbarFilters.js.map +1 -1
  27. package/dist/src/components/Device/DevicesPage/DevicesPage.d.ts.map +1 -1
  28. package/dist/src/components/Device/DevicesPage/DevicesPage.js +1 -1
  29. package/dist/src/components/Device/DevicesPage/DevicesPage.js.map +1 -1
  30. package/dist/src/components/Device/DevicesPage/EnrolledDeviceTableRow.d.ts +3 -1
  31. package/dist/src/components/Device/DevicesPage/EnrolledDeviceTableRow.d.ts.map +1 -1
  32. package/dist/src/components/Device/DevicesPage/EnrolledDeviceTableRow.js +12 -4
  33. package/dist/src/components/Device/DevicesPage/EnrolledDeviceTableRow.js.map +1 -1
  34. package/dist/src/components/Device/DevicesPage/EnrolledDevicesTable.d.ts +2 -1
  35. package/dist/src/components/Device/DevicesPage/EnrolledDevicesTable.d.ts.map +1 -1
  36. package/dist/src/components/Device/DevicesPage/EnrolledDevicesTable.js +7 -6
  37. package/dist/src/components/Device/DevicesPage/EnrolledDevicesTable.js.map +1 -1
  38. package/dist/src/components/Device/SystemdUnitsModal/TrackSystemdUnitsForm.js +1 -1
  39. package/dist/src/components/Device/SystemdUnitsModal/TrackSystemdUnitsForm.js.map +1 -1
  40. package/dist/src/components/Events/useEvents.d.ts.map +1 -1
  41. package/dist/src/components/Events/useEvents.js +12 -0
  42. package/dist/src/components/Events/useEvents.js.map +1 -1
  43. package/dist/src/components/Fleet/CreateFleet/steps/UpdatePolicyStep.js +1 -1
  44. package/dist/src/components/Fleet/CreateFleet/steps/UpdatePolicyStep.js.map +1 -1
  45. package/dist/src/components/Fleet/FleetDetails/FleetDetailsPage.d.ts.map +1 -1
  46. package/dist/src/components/Fleet/FleetDetails/FleetDetailsPage.js +4 -3
  47. package/dist/src/components/Fleet/FleetDetails/FleetDetailsPage.js.map +1 -1
  48. package/dist/src/components/Fleet/FleetDetails/FleetDevicesCharts.d.ts.map +1 -1
  49. package/dist/src/components/Fleet/FleetDetails/FleetDevicesCharts.js +7 -1
  50. package/dist/src/components/Fleet/FleetDetails/FleetDevicesCharts.js.map +1 -1
  51. package/dist/src/components/Fleet/FleetDetails/FleetRestoreBanner.d.ts +8 -0
  52. package/dist/src/components/Fleet/FleetDetails/FleetRestoreBanner.d.ts.map +1 -0
  53. package/dist/src/components/Fleet/FleetDetails/FleetRestoreBanner.js +36 -0
  54. package/dist/src/components/Fleet/FleetDetails/FleetRestoreBanner.js.map +1 -0
  55. package/dist/src/components/Fleet/FleetsPage.d.ts.map +1 -1
  56. package/dist/src/components/Fleet/FleetsPage.js +2 -0
  57. package/dist/src/components/Fleet/FleetsPage.js.map +1 -1
  58. package/dist/src/components/ListPage/ListPageActions.d.ts +3 -2
  59. package/dist/src/components/ListPage/ListPageActions.d.ts.map +1 -1
  60. package/dist/src/components/ListPage/ListPageActions.js +27 -1
  61. package/dist/src/components/ListPage/ListPageActions.js.map +1 -1
  62. package/dist/src/components/Masthead/CommandLineToolsPage.d.ts.map +1 -1
  63. package/dist/src/components/Masthead/CommandLineToolsPage.js +18 -14
  64. package/dist/src/components/Masthead/CommandLineToolsPage.js.map +1 -1
  65. package/dist/src/components/OverviewPage/Cards/Alerts/AlertsCard.d.ts.map +1 -1
  66. package/dist/src/components/OverviewPage/Cards/Alerts/AlertsCard.js +15 -5
  67. package/dist/src/components/OverviewPage/Cards/Alerts/AlertsCard.js.map +1 -1
  68. package/dist/src/components/OverviewPage/Cards/Status/DeviceStatusChart.d.ts.map +1 -1
  69. package/dist/src/components/OverviewPage/Cards/Status/DeviceStatusChart.js +7 -1
  70. package/dist/src/components/OverviewPage/Cards/Status/DeviceStatusChart.js.map +1 -1
  71. package/dist/src/components/OverviewPage/Overview.d.ts.map +1 -1
  72. package/dist/src/components/OverviewPage/Overview.js +4 -2
  73. package/dist/src/components/OverviewPage/Overview.js.map +1 -1
  74. package/dist/src/components/Status/StatusDisplay.d.ts +3 -1
  75. package/dist/src/components/Status/StatusDisplay.d.ts.map +1 -1
  76. package/dist/src/components/Status/StatusDisplay.js +8 -8
  77. package/dist/src/components/Status/StatusDisplay.js.map +1 -1
  78. package/dist/src/components/SystemRestore/PendingSyncDevicesAlert.d.ts +7 -0
  79. package/dist/src/components/SystemRestore/PendingSyncDevicesAlert.d.ts.map +1 -0
  80. package/dist/src/components/SystemRestore/PendingSyncDevicesAlert.js +22 -0
  81. package/dist/src/components/SystemRestore/PendingSyncDevicesAlert.js.map +1 -0
  82. package/dist/src/components/SystemRestore/SuspendedDevicesAlert.d.ts +16 -0
  83. package/dist/src/components/SystemRestore/SuspendedDevicesAlert.d.ts.map +1 -0
  84. package/dist/src/components/SystemRestore/SuspendedDevicesAlert.js +75 -0
  85. package/dist/src/components/SystemRestore/SuspendedDevicesAlert.js.map +1 -0
  86. package/dist/src/components/SystemRestore/SystemRestoreBanners.css +6 -0
  87. package/dist/src/components/SystemRestore/SystemRestoreBanners.d.ts +28 -0
  88. package/dist/src/components/SystemRestore/SystemRestoreBanners.d.ts.map +1 -0
  89. package/dist/src/components/SystemRestore/SystemRestoreBanners.js +38 -0
  90. package/dist/src/components/SystemRestore/SystemRestoreBanners.js.map +1 -0
  91. package/dist/src/components/charts/utils.js +1 -1
  92. package/dist/src/components/charts/utils.js.map +1 -1
  93. package/dist/src/components/common/OrganizationGuard.d.ts +13 -0
  94. package/dist/src/components/common/OrganizationGuard.d.ts.map +1 -0
  95. package/dist/src/components/common/OrganizationGuard.js +106 -0
  96. package/dist/src/components/common/OrganizationGuard.js.map +1 -0
  97. package/dist/src/components/common/OrganizationSelector.d.ts +8 -0
  98. package/dist/src/components/common/OrganizationSelector.d.ts.map +1 -0
  99. package/dist/src/components/common/OrganizationSelector.js +92 -0
  100. package/dist/src/components/common/OrganizationSelector.js.map +1 -0
  101. package/dist/src/components/common/PageNavigation.d.ts +4 -0
  102. package/dist/src/components/common/PageNavigation.d.ts.map +1 -0
  103. package/dist/src/components/common/PageNavigation.js +46 -0
  104. package/dist/src/components/common/PageNavigation.js.map +1 -0
  105. package/dist/src/components/form/FilterSelect.css +3 -4
  106. package/dist/src/components/form/validations.d.ts +9 -0
  107. package/dist/src/components/form/validations.d.ts.map +1 -1
  108. package/dist/src/components/form/validations.js +12 -1
  109. package/dist/src/components/form/validations.js.map +1 -1
  110. package/dist/src/components/modals/ResumeDevicesModal/ResumeDevicesModal.d.ts +15 -0
  111. package/dist/src/components/modals/ResumeDevicesModal/ResumeDevicesModal.d.ts.map +1 -0
  112. package/dist/src/components/modals/ResumeDevicesModal/ResumeDevicesModal.js +56 -0
  113. package/dist/src/components/modals/ResumeDevicesModal/ResumeDevicesModal.js.map +1 -0
  114. package/dist/src/components/modals/massModals/ResumeDevicesModal/MassResumeDevicesModal.d.ts +7 -0
  115. package/dist/src/components/modals/massModals/ResumeDevicesModal/MassResumeDevicesModal.d.ts.map +1 -0
  116. package/dist/src/components/modals/massModals/ResumeDevicesModal/MassResumeDevicesModal.js +265 -0
  117. package/dist/src/components/modals/massModals/ResumeDevicesModal/MassResumeDevicesModal.js.map +1 -0
  118. package/dist/src/components/modals/massModals/ResumeDevicesModal/ResumeAllDevicesConfirmationDialog.d.ts +9 -0
  119. package/dist/src/components/modals/massModals/ResumeDevicesModal/ResumeAllDevicesConfirmationDialog.d.ts.map +1 -0
  120. package/dist/src/components/modals/massModals/ResumeDevicesModal/ResumeAllDevicesConfirmationDialog.js +27 -0
  121. package/dist/src/components/modals/massModals/ResumeDevicesModal/ResumeAllDevicesConfirmationDialog.js.map +1 -0
  122. package/dist/src/hooks/useAccessReview.d.ts.map +1 -1
  123. package/dist/src/hooks/useAccessReview.js +17 -4
  124. package/dist/src/hooks/useAccessReview.js.map +1 -1
  125. package/dist/src/hooks/useAlertsEnabled.d.ts +2 -0
  126. package/dist/src/hooks/useAlertsEnabled.d.ts.map +1 -0
  127. package/dist/src/hooks/useAlertsEnabled.js +50 -0
  128. package/dist/src/hooks/useAlertsEnabled.js.map +1 -0
  129. package/dist/src/hooks/useAppContext.d.ts +3 -6
  130. package/dist/src/hooks/useAppContext.d.ts.map +1 -1
  131. package/dist/src/hooks/useAppContext.js +1 -0
  132. package/dist/src/hooks/useAppContext.js.map +1 -1
  133. package/dist/src/hooks/useFetch.d.ts +6 -7
  134. package/dist/src/hooks/useFetch.d.ts.map +1 -1
  135. package/dist/src/hooks/useFetch.js +2 -3
  136. package/dist/src/hooks/useFetch.js.map +1 -1
  137. package/dist/src/hooks/useFetchPeriodically.d.ts.map +1 -1
  138. package/dist/src/hooks/useFetchPeriodically.js +4 -9
  139. package/dist/src/hooks/useFetchPeriodically.js.map +1 -1
  140. package/dist/src/hooks/useSystemRestoreContext.d.ts +16 -0
  141. package/dist/src/hooks/useSystemRestoreContext.d.ts.map +1 -0
  142. package/dist/src/hooks/useSystemRestoreContext.js +45 -0
  143. package/dist/src/hooks/useSystemRestoreContext.js.map +1 -0
  144. package/dist/src/types/extraTypes.d.ts +17 -18
  145. package/dist/src/types/extraTypes.d.ts.map +1 -1
  146. package/dist/src/types/extraTypes.js +1 -6
  147. package/dist/src/types/extraTypes.js.map +1 -1
  148. package/dist/src/types/rbac.d.ts +1 -0
  149. package/dist/src/types/rbac.d.ts.map +1 -1
  150. package/dist/src/types/rbac.js +1 -0
  151. package/dist/src/types/rbac.js.map +1 -1
  152. package/dist/src/utils/api.d.ts +2 -15
  153. package/dist/src/utils/api.d.ts.map +1 -1
  154. package/dist/src/utils/api.js +1 -40
  155. package/dist/src/utils/api.js.map +1 -1
  156. package/dist/src/utils/devices.d.ts +2 -0
  157. package/dist/src/utils/devices.d.ts.map +1 -1
  158. package/dist/src/utils/devices.js +11 -1
  159. package/dist/src/utils/devices.js.map +1 -1
  160. package/dist/src/utils/organizationStorage.d.ts +4 -0
  161. package/dist/src/utils/organizationStorage.d.ts.map +1 -0
  162. package/dist/src/utils/organizationStorage.js +18 -0
  163. package/dist/src/utils/organizationStorage.js.map +1 -0
  164. package/dist/src/utils/query.d.ts +2 -0
  165. package/dist/src/utils/query.d.ts.map +1 -1
  166. package/dist/src/utils/query.js +16 -0
  167. package/dist/src/utils/query.js.map +1 -1
  168. package/dist/src/utils/status/common.d.ts +1 -0
  169. package/dist/src/utils/status/common.d.ts.map +1 -1
  170. package/dist/src/utils/status/common.js.map +1 -1
  171. package/dist/src/utils/status/devices.d.ts +5 -0
  172. package/dist/src/utils/status/devices.d.ts.map +1 -1
  173. package/dist/src/utils/status/devices.js +44 -5
  174. package/dist/src/utils/status/devices.js.map +1 -1
  175. package/dist/src/utils/status/fleet.js +1 -1
  176. package/dist/src/utils/status/fleet.js.map +1 -1
  177. package/dist/src/utils/status/repository.js +1 -1
  178. package/dist/src/utils/status/repository.js.map +1 -1
  179. package/package.json +1 -1
  180. package/src/components/DetailsPage/DetailsPage.tsx +3 -0
  181. package/src/components/DetailsPage/DetailsPageActions.tsx +45 -0
  182. package/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx +57 -5
  183. package/src/components/Device/DeviceDetails/DeviceDetailsTab.tsx +0 -5
  184. package/src/components/Device/DeviceDetails/DeviceDetailsTabContent/StatusContent.tsx +11 -3
  185. package/src/components/Device/DevicesPage/DecommissionedDeviceTableRow.tsx +0 -2
  186. package/src/components/Device/DevicesPage/DecommissionedDevicesTable.tsx +0 -3
  187. package/src/components/Device/DevicesPage/DeviceToolbarFilters.tsx +5 -1
  188. package/src/components/Device/DevicesPage/DevicesPage.tsx +1 -0
  189. package/src/components/Device/DevicesPage/EnrolledDeviceTableRow.tsx +15 -3
  190. package/src/components/Device/DevicesPage/EnrolledDevicesTable.tsx +11 -5
  191. package/src/components/Device/SystemdUnitsModal/TrackSystemdUnitsForm.tsx +1 -1
  192. package/src/components/Events/useEvents.ts +12 -0
  193. package/src/components/Fleet/CreateFleet/steps/UpdatePolicyStep.tsx +1 -1
  194. package/src/components/Fleet/FleetDetails/FleetDetailsPage.tsx +4 -5
  195. package/src/components/Fleet/FleetDetails/FleetDevicesCharts.tsx +9 -3
  196. package/src/components/Fleet/FleetDetails/FleetRestoreBanner.tsx +46 -0
  197. package/src/components/Fleet/FleetsPage.tsx +2 -0
  198. package/src/components/ListPage/ListPageActions.tsx +46 -3
  199. package/src/components/Masthead/CommandLineToolsPage.tsx +17 -14
  200. package/src/components/OverviewPage/Cards/Alerts/AlertsCard.tsx +19 -5
  201. package/src/components/OverviewPage/Cards/Status/DeviceStatusChart.tsx +8 -2
  202. package/src/components/OverviewPage/Overview.tsx +5 -2
  203. package/src/components/Status/StatusDisplay.tsx +32 -23
  204. package/src/components/SystemRestore/PendingSyncDevicesAlert.tsx +36 -0
  205. package/src/components/SystemRestore/SuspendedDevicesAlert.tsx +144 -0
  206. package/src/components/SystemRestore/SystemRestoreBanners.css +6 -0
  207. package/src/components/SystemRestore/SystemRestoreBanners.tsx +82 -0
  208. package/src/components/charts/utils.ts +1 -1
  209. package/src/components/common/OrganizationGuard.tsx +124 -0
  210. package/src/components/common/OrganizationSelector.tsx +192 -0
  211. package/src/components/common/PageNavigation.tsx +103 -0
  212. package/src/components/form/FilterSelect.css +3 -4
  213. package/src/components/form/validations.ts +14 -0
  214. package/src/components/modals/ResumeDevicesModal/ResumeDevicesModal.tsx +114 -0
  215. package/src/components/modals/massModals/ResumeDevicesModal/MassResumeDevicesModal.tsx +465 -0
  216. package/src/components/modals/massModals/ResumeDevicesModal/ResumeAllDevicesConfirmationDialog.tsx +55 -0
  217. package/src/hooks/useAccessReview.ts +20 -4
  218. package/src/hooks/useAlertsEnabled.ts +50 -0
  219. package/src/hooks/useAppContext.tsx +9 -7
  220. package/src/hooks/useFetch.ts +1 -3
  221. package/src/hooks/useFetchPeriodically.ts +4 -11
  222. package/src/hooks/useSystemRestoreContext.tsx +54 -0
  223. package/src/types/extraTypes.ts +17 -23
  224. package/src/types/rbac.ts +1 -0
  225. package/src/utils/api.ts +2 -51
  226. package/src/utils/devices.ts +11 -1
  227. package/src/utils/organizationStorage.ts +13 -0
  228. package/src/utils/query.ts +22 -0
  229. package/src/utils/status/common.ts +1 -0
  230. package/src/utils/status/devices.ts +49 -2
  231. package/src/utils/status/fleet.ts +1 -1
  232. package/src/utils/status/repository.ts +1 -1
  233. package/dist/src/hooks/useAlerts.d.ts +0 -26
  234. package/dist/src/hooks/useAlerts.d.ts.map +0 -1
  235. package/dist/src/hooks/useAlerts.js +0 -114
  236. package/dist/src/hooks/useAlerts.js.map +0 -1
  237. package/dist/src/utils/metrics.d.ts +0 -9
  238. package/dist/src/utils/metrics.d.ts.map +0 -1
  239. package/dist/src/utils/metrics.js +0 -48
  240. package/dist/src/utils/metrics.js.map +0 -1
  241. package/src/hooks/useAlerts.ts +0 -147
  242. package/src/utils/metrics.ts +0 -49
@@ -13,8 +13,9 @@ import NavItem from '../../NavItem/NavItem';
13
13
  import DetailsPage from '../../DetailsPage/DetailsPage';
14
14
  import DetailsPageActions from '../../DetailsPage/DetailsPageActions';
15
15
  import DeleteFleetModal from '../DeleteFleetModal/DeleteFleetModal';
16
- import FleetDetailsContent from './FleetDetailsContent';
17
16
  import YamlEditor from '../../common/CodeEditor/YamlEditor';
17
+ import FleetDetailsContent from './FleetDetailsContent';
18
+ import FleetRestoreBanner from './FleetRestoreBanner';
18
19
 
19
20
  const FleetDetailPage = () => {
20
21
  const { t } = useTranslation();
@@ -45,6 +46,7 @@ const FleetDetailPage = () => {
45
46
  resourceLink={ROUTE.FLEETS}
46
47
  resourceType="Fleets"
47
48
  resourceTypeLabel={t('Fleets')}
49
+ banner={<FleetRestoreBanner fleet={fleet} refetch={refetch} />}
48
50
  nav={
49
51
  <Nav variant="tertiary">
50
52
  <NavList>
@@ -98,10 +100,7 @@ const FleetDetailPage = () => {
98
100
  <Routes>
99
101
  <Route index element={<Navigate to="details" replace />} />
100
102
  <Route path="details" element={<FleetDetailsContent fleet={fleet} />} />
101
- <Route
102
- path="yaml"
103
- element={<YamlEditor filename={fleet.metadata.name || 'fleet'} apiObj={fleet} refetch={refetch} />}
104
- />
103
+ <Route path="yaml" element={<YamlEditor filename={fleetId} apiObj={fleet} refetch={refetch} />} />
105
104
  </Routes>
106
105
  {isDeleteModalOpen && (
107
106
  <DeleteFleetModal
@@ -1,9 +1,9 @@
1
1
  import * as React from 'react';
2
2
  import { Flex, FlexItem } from '@patternfly/react-core';
3
3
 
4
- import { DevicesSummary } from '@flightctl/types';
4
+ import { DeviceSummaryStatusType, DevicesSummary } from '@flightctl/types';
5
5
  import { useTranslation } from '../../../hooks/useTranslation';
6
- import { FilterSearchParams, getDeviceStatusItems } from '../../../utils/status/devices';
6
+ import { FilterSearchParams, getOverviewDeviceStatusItems } from '../../../utils/status/devices';
7
7
  import { getSystemUpdateStatusItems } from '../../../utils/status/system';
8
8
  import {
9
9
  getApplicationStatusHelperText,
@@ -19,6 +19,11 @@ interface FleetDevicesChartsProps {
19
19
  devicesSummary: DevicesSummary;
20
20
  }
21
21
 
22
+ const systemRestoreStatuses = [
23
+ DeviceSummaryStatusType.DeviceSummaryStatusAwaitingReconnect,
24
+ DeviceSummaryStatusType.DeviceSummaryStatusConflictPaused,
25
+ ];
26
+
22
27
  const getBaseFleetQuery = (fleetId: string) => {
23
28
  const baseQuery = new URLSearchParams();
24
29
  baseQuery.set(FilterSearchParams.Fleet, fleetId);
@@ -78,7 +83,8 @@ const DevicesByDeviceStatusChart = ({
78
83
  }) => {
79
84
  const { t } = useTranslation();
80
85
 
81
- const statusItems = getDeviceStatusItems(t);
86
+ const excludeStatuses = systemRestoreStatuses.filter((status) => !deviceStatus[status]);
87
+ const statusItems = getOverviewDeviceStatusItems(t, excludeStatuses);
82
88
 
83
89
  const deviceStatusData = toChartData(
84
90
  deviceStatus,
@@ -0,0 +1,46 @@
1
+ import * as React from 'react';
2
+ import { Trans } from 'react-i18next';
3
+ import { DeviceSummaryStatusType, Fleet } from '@flightctl/types';
4
+
5
+ import { SystemRestoreBanners } from '../../SystemRestore/SystemRestoreBanners';
6
+ import { fromAPILabel, labelToExactApiMatchString } from '../../../utils/labels';
7
+ import { useTranslation } from '../../../hooks/useTranslation';
8
+
9
+ const FleetRestoreBanner = ({ fleet, refetch }: { fleet?: Fleet; refetch: VoidFunction }) => {
10
+ const { t } = useTranslation();
11
+ if (!fleet) {
12
+ return null;
13
+ }
14
+
15
+ const fleetId = fleet.metadata.name as string;
16
+
17
+ const fleetDeviceStatuses = fleet?.status?.devicesSummary?.summaryStatus;
18
+ const suspendedDevicesCountNum =
19
+ fleetDeviceStatuses?.[DeviceSummaryStatusType.DeviceSummaryStatusConflictPaused] || 0;
20
+ const suspendedDevicesCount = suspendedDevicesCountNum.toString();
21
+
22
+ return (
23
+ <SystemRestoreBanners
24
+ mode="fleet"
25
+ summaryStatus={fleetDeviceStatuses}
26
+ resumeAction={{
27
+ actionText: t('Resume all'),
28
+ title: (
29
+ <Trans t={t} count={suspendedDevicesCountNum}>
30
+ You are about to resume all<strong>{suspendedDevicesCount}</strong> suspended devices in{' '}
31
+ <strong>{fleetId}</strong>
32
+ </Trans>
33
+ ),
34
+ requestSelector: {
35
+ labelSelector: fromAPILabel(fleet.spec.selector?.matchLabels || {})
36
+ .map(labelToExactApiMatchString)
37
+ .join(','),
38
+ },
39
+ }}
40
+ onResumeComplete={refetch}
41
+ className="pf-v5-u-pt-0 pf-v5-u-px-lg"
42
+ />
43
+ );
44
+ };
45
+
46
+ export default FleetRestoreBanner;
@@ -37,6 +37,7 @@ import ButtonWithPermissions from '../common/ButtonWithPermissions';
37
37
  import { RESOURCE, VERB } from '../../types/rbac';
38
38
  import PageWithPermissions from '../common/PageWithPermissions';
39
39
  import { useFleetImportAccessReview } from '../../hooks/useFleetImportAccessReview';
40
+ import { GlobalSystemRestoreBanners } from '../SystemRestore/SystemRestoreBanners';
40
41
 
41
42
  const FleetPageActions = ({ createText }: { createText?: string }) => {
42
43
  const { t } = useTranslation();
@@ -122,6 +123,7 @@ const FleetTable = () => {
122
123
 
123
124
  return (
124
125
  <ListPageBody error={error} loading={isLoading}>
126
+ <GlobalSystemRestoreBanners onResumeComplete={refetch} />
125
127
  <Toolbar inset={{ default: 'insetNone' }}>
126
128
  <ToolbarContent>
127
129
  <ToolbarGroup>
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { TFunction } from 'react-i18next';
2
+ import { TFunction, Trans } from 'react-i18next';
3
3
 
4
4
  import { DeviceDecommissionTargetType } from '@flightctl/types';
5
5
  import { ListAction, ListActionProps, ListActionResult } from './types';
@@ -8,9 +8,10 @@ import { useTranslation } from '../../hooks/useTranslation';
8
8
  import { getDisabledTooltipProps } from '../../utils/tooltip';
9
9
  import DeleteModal from '../modals/DeleteModal/DeleteModal';
10
10
  import DecommissionModal from '../modals/DecommissionModal/DecommissionModal';
11
+ import ResumeDevicesModal from '../modals/ResumeDevicesModal/ResumeDevicesModal';
11
12
 
12
13
  type DeleteResourceType = 'EnrollmentRequest' | 'ResourceSync' | 'Device';
13
- type DecommissionResourceType = 'Device';
14
+ type DeviceOnlyResourceType = 'Device';
14
15
 
15
16
  type ResourceType = 'Device' | 'EnrollmentRequest' | 'ResourceSync';
16
17
 
@@ -70,7 +71,7 @@ export const useDeleteListAction = ({
70
71
 
71
72
  export const useDecommissionListAction = ({
72
73
  onConfirm,
73
- }: ListActionProps<DecommissionResourceType, { target: DeviceDecommissionTargetType }>): ListActionResult => {
74
+ }: ListActionProps<DeviceOnlyResourceType, { target: DeviceDecommissionTargetType }>): ListActionResult => {
74
75
  const { t } = useTranslation();
75
76
  const [decommissionDeviceId, setDecommissionDeviceId] = React.useState<string>();
76
77
 
@@ -100,3 +101,45 @@ export const useDecommissionListAction = ({
100
101
 
101
102
  return { action: decommissionAction, modal: decommissionModal };
102
103
  };
104
+
105
+ export const useResumeListAction = (onResumeComplete?: VoidFunction): ListActionResult => {
106
+ const { t } = useTranslation();
107
+ const [deviceId, setDeviceId] = React.useState<string>();
108
+ const [deviceName, setDeviceName] = React.useState<string>();
109
+
110
+ const resumeAction: ListAction = ({ resourceId, resourceName, disabledReason }) => {
111
+ const popperProps = getDisabledTooltipProps(disabledReason);
112
+ return {
113
+ title: t('Resume device'),
114
+ ...popperProps,
115
+ onClick: () => {
116
+ setDeviceId(resourceId);
117
+ setDeviceName(resourceName || resourceId);
118
+ },
119
+ };
120
+ };
121
+
122
+ const onClose = (hasResumed?: boolean) => {
123
+ setDeviceId(undefined);
124
+ setDeviceName(undefined);
125
+ if (hasResumed) {
126
+ onResumeComplete?.();
127
+ }
128
+ };
129
+
130
+ const resumeModal = deviceId && (
131
+ <ResumeDevicesModal
132
+ mode="device"
133
+ title={
134
+ <Trans t={t}>
135
+ You are about to resume device <strong>{deviceName}</strong>
136
+ </Trans>
137
+ }
138
+ selector={{ fieldSelector: `metadata.name=${deviceId}` }}
139
+ expectedCount={1}
140
+ onClose={onClose}
141
+ />
142
+ );
143
+
144
+ return { action: resumeAction, modal: resumeModal };
145
+ };
@@ -31,9 +31,6 @@ type CommandLineToolsContentProps = {
31
31
 
32
32
  type CommandLineArtifact = CliArtifactsResponse['artifacts'][0];
33
33
 
34
- // "Not implemented" response from the UI Proxy when artifact functionality is disabled
35
- const cliArtifactsDisabledError = 'Error 501';
36
-
37
34
  const getArtifactUrl = (baseUrl: string, artifact: CommandLineArtifact) => {
38
35
  const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
39
36
  return `${normalizedBaseUrl}/${artifact.arch}/${artifact.os}/${artifact.filename}`;
@@ -103,7 +100,8 @@ const CommandLineToolsContent = ({
103
100
 
104
101
  const CommandLineToolsPage = () => {
105
102
  const { t } = useTranslation();
106
- const { getCliArtifacts, settings } = useAppContext();
103
+ const { fetch, settings } = useAppContext();
104
+ const proxyFetch = fetch.proxyFetch;
107
105
 
108
106
  const [loading, setLoading] = React.useState<boolean>(true);
109
107
  const [loadError, setLoadError] = React.useState<string>();
@@ -113,23 +111,28 @@ const CommandLineToolsPage = () => {
113
111
  React.useEffect(() => {
114
112
  const getLinks = async () => {
115
113
  try {
116
- if (getCliArtifacts) {
117
- const apiResponse = await getCliArtifacts();
118
- setCliArtifactsResponse(apiResponse);
114
+ const response = await proxyFetch('cli-artifacts', {
115
+ method: 'GET',
116
+ });
117
+ if (!response.ok) {
118
+ if (response.status === 501) {
119
+ // Response that indicatest that the feature is disabled
120
+ setArtifactsEnabled(false);
121
+ } else {
122
+ setLoadError(getErrorMessage(response.statusText));
123
+ }
124
+ return;
119
125
  }
126
+ const apiResponse = (await response.json()) as CliArtifactsResponse;
127
+ setCliArtifactsResponse(apiResponse);
120
128
  } catch (e) {
121
- const msg = getErrorMessage(e);
122
- if (msg.includes(cliArtifactsDisabledError)) {
123
- setArtifactsEnabled(false);
124
- } else {
125
- setLoadError(msg);
126
- }
129
+ setArtifactsEnabled(false);
127
130
  } finally {
128
131
  setLoading(false);
129
132
  }
130
133
  };
131
134
  void getLinks();
132
- }, [getCliArtifacts]);
135
+ }, [proxyFetch]);
133
136
 
134
137
  const productName = settings.isRHEM ? t('Red Hat Edge Manager') : t('Flight Control');
135
138
 
@@ -15,7 +15,8 @@ import { TFunction } from 'react-i18next';
15
15
  import { ExclamationCircleIcon } from '@patternfly/react-icons/dist/js/icons/exclamation-circle-icon';
16
16
 
17
17
  import { Event, ResourceKind } from '@flightctl/types';
18
- import { useAlerts } from '../../../../hooks/useAlerts';
18
+ import { AlertManagerAlert } from '../../../../types/extraTypes';
19
+ import { useFetchPeriodically } from '../../../../hooks/useFetchPeriodically';
19
20
  import { useTranslation } from '../../../../hooks/useTranslation';
20
21
  import { getDateDisplay } from '../../../../utils/dates';
21
22
  import { getErrorMessage } from '../../../../utils/error';
@@ -23,6 +24,8 @@ import ResourceLink from '../../../common/ResourceLink';
23
24
 
24
25
  import AlertsEmptyState from './AlertsEmptyState';
25
26
 
27
+ const ALERTS_TIMEOUT = 20000; // 20 seconds
28
+
26
29
  // Define only the Event.reason values that correspond to alerts
27
30
  type AlertEventReason =
28
31
  | Event.reason.DEVICE_APPLICATION_DEGRADED
@@ -113,10 +116,21 @@ const resourceKindLabel = (t: TFunction, resourceKind: ResourceKind | undefined)
113
116
  return t('Template version');
114
117
  }
115
118
  };
119
+ const getAlertTitle = (alert: AlertManagerAlert, defaultTitle: string) => {
120
+ if (alert.annotations.summary) {
121
+ return alert.annotations.summary;
122
+ }
123
+ return defaultTitle;
124
+ };
116
125
 
117
126
  const AlertsCard = () => {
118
127
  const { t } = useTranslation();
119
- const [alerts, isLoading, error] = useAlerts();
128
+
129
+ const [alerts, isLoading, error] = useFetchPeriodically<AlertManagerAlert[]>({
130
+ endpoint: 'alerts',
131
+ timeout: ALERTS_TIMEOUT,
132
+ });
133
+
120
134
  const alertTypes = React.useMemo(() => getAlertTitles(t), [t]);
121
135
 
122
136
  let alertsBody: React.ReactNode;
@@ -130,12 +144,12 @@ const AlertsCard = () => {
130
144
  </Alert>
131
145
  </CardBody>
132
146
  );
133
- } else if (alerts.length === 0) {
147
+ } else if (alerts?.length === 0) {
134
148
  alertsBody = <AlertsEmptyState />;
135
149
  } else {
136
150
  alertsBody = (
137
151
  <List isPlain>
138
- {alerts.map((alert) => {
152
+ {alerts?.map((alert) => {
139
153
  const alertName = alert.labels.alertname as AlertEventReason;
140
154
  const resourceKind = alertResourceKind[alertName];
141
155
  const kindLabel = resourceKindLabel(t, resourceKind);
@@ -146,7 +160,7 @@ const AlertsCard = () => {
146
160
  <Icon status="danger" size="md">
147
161
  <ExclamationCircleIcon />
148
162
  </Icon>{' '}
149
- <strong>{alertTypes[alertName] || alertName}</strong>
163
+ <strong>{getAlertTitle(alert, alertTypes[alertName] || alertName)}</strong>
150
164
  </StackItem>
151
165
  <StackItem>
152
166
  <TextContent>
@@ -5,11 +5,16 @@ import { FlightCtlLabel } from '../../../../types/extraTypes';
5
5
 
6
6
  import { useTranslation } from '../../../../hooks/useTranslation';
7
7
  import { getDeviceStatusHelperText } from '../../../Status/utils';
8
- import { getDeviceStatusItems } from '../../../../utils/status/devices';
8
+ import { getOverviewDeviceStatusItems } from '../../../../utils/status/devices';
9
9
  import { FilterSearchParams } from '../../../../utils/status/devices';
10
10
  import { toOverviewChartData } from './utils';
11
11
  import DonutChart from '../../../charts/DonutChart';
12
12
 
13
+ const systemRestoreStatuses = [
14
+ DeviceSummaryStatusType.DeviceSummaryStatusAwaitingReconnect,
15
+ DeviceSummaryStatusType.DeviceSummaryStatusConflictPaused,
16
+ ];
17
+
13
18
  const DeviceStatusChart = ({
14
19
  deviceStatus,
15
20
  labels,
@@ -21,7 +26,8 @@ const DeviceStatusChart = ({
21
26
  }) => {
22
27
  const { t } = useTranslation();
23
28
 
24
- const statusItems = getDeviceStatusItems(t);
29
+ const excludeStatuses = systemRestoreStatuses.filter((status) => !deviceStatus[status]);
30
+ const statusItems = getOverviewDeviceStatusItems(t, excludeStatuses);
25
31
 
26
32
  const devStatusData = toOverviewChartData<DeviceSummaryStatusType>(
27
33
  deviceStatus,
@@ -2,21 +2,24 @@ import * as React from 'react';
2
2
  import { Grid, GridItem } from '@patternfly/react-core';
3
3
 
4
4
  import { useAccessReview } from '../../hooks/useAccessReview';
5
+ import { useAlertsEnabled } from '../../hooks/useAlertsEnabled';
5
6
  import { RESOURCE, VERB } from '../../types/rbac';
6
7
  import PageWithPermissions from '../common/PageWithPermissions';
7
- import { useAlertsEnabled } from '../../hooks/useAlerts';
8
+ import { GlobalSystemRestoreBanners } from '../SystemRestore/SystemRestoreBanners';
8
9
 
9
10
  import AlertsCard from './Cards/Alerts/AlertsCard';
10
11
  import StatusCard from './Cards/Status/StatusCard';
11
12
  import TasksCard from './Cards/Tasks/TasksCard';
12
13
 
13
14
  const Overview = () => {
15
+ const alertsEnabled = useAlertsEnabled();
14
16
  const [canListDevices, devicesLoading] = useAccessReview(RESOURCE.DEVICE, VERB.LIST);
15
17
  const [canListErs, erLoading] = useAccessReview(RESOURCE.ENROLLMENT_REQUEST, VERB.LIST);
16
- const alertsEnabled = useAlertsEnabled();
17
18
 
18
19
  return (
19
20
  <PageWithPermissions allowed={canListDevices || canListErs} loading={devicesLoading || erLoading}>
21
+ <GlobalSystemRestoreBanners className="pf-v5-u-py-0" />
22
+
20
23
  <Grid hasGutter>
21
24
  <GridItem md={alertsEnabled ? 9 : 12}>
22
25
  <Grid hasGutter>
@@ -14,11 +14,29 @@ type StatusLabelProps = {
14
14
  messageTitle?: string;
15
15
  level: StatusLevel;
16
16
  customIcon?: React.ComponentClass<SVGIconProps>;
17
+ customColor?: string;
17
18
  };
18
19
 
19
- export const StatusDisplayContent = ({ label, messageTitle, message, level, customIcon }: StatusLabelProps) => {
20
- const iconStatus = level === 'unknown' ? undefined : level;
20
+ export const StatusDisplayContent = ({
21
+ label,
22
+ messageTitle,
23
+ message,
24
+ level,
25
+ customIcon,
26
+ customColor,
27
+ }: StatusLabelProps) => {
28
+ const overrideStatus = level === 'unknown' || (level === 'custom' && customColor);
21
29
  const IconComponent = customIcon || getDefaultStatusIcon(level);
30
+ const iconColor = customColor || getDefaultStatusColor(level);
31
+
32
+ const icon = (
33
+ <Icon
34
+ status={overrideStatus ? undefined : level}
35
+ style={{ '--pf-v5-c-icon__content--Color': iconColor } as React.CSSProperties}
36
+ >
37
+ <IconComponent />
38
+ </Icon>
39
+ );
22
40
 
23
41
  if (message) {
24
42
  return (
@@ -29,18 +47,7 @@ export const StatusDisplayContent = ({ label, messageTitle, message, level, cust
29
47
  className="fctl-status-display-content__popover"
30
48
  hasAutoWidth={false}
31
49
  >
32
- <Button
33
- variant="link"
34
- isInline
35
- icon={
36
- <Icon
37
- status={iconStatus}
38
- style={{ '--pf-v5-c-icon__content--Color': getDefaultStatusColor(level) } as React.CSSProperties}
39
- >
40
- <IconComponent />
41
- </Icon>
42
- }
43
- >
50
+ <Button variant="link" isInline icon={icon}>
44
51
  {label}
45
52
  </Button>
46
53
  </Popover>
@@ -49,14 +56,7 @@ export const StatusDisplayContent = ({ label, messageTitle, message, level, cust
49
56
 
50
57
  return (
51
58
  <Flex className="ftcl_status-label">
52
- <FlexItem>
53
- <Icon
54
- status={iconStatus}
55
- style={{ '--pf-v5-c-icon__content--Color': getDefaultStatusColor(level) } as React.CSSProperties}
56
- >
57
- <IconComponent />
58
- </Icon>
59
- </FlexItem>
59
+ <FlexItem>{icon}</FlexItem>
60
60
  <FlexItem>{label}</FlexItem>
61
61
  </Flex>
62
62
  );
@@ -67,6 +67,7 @@ type StatusDisplayProps = {
67
67
  label: string;
68
68
  level: StatusLevel;
69
69
  customIcon?: React.ComponentClass<SVGIconProps>;
70
+ customColor?: string;
70
71
  };
71
72
  message?: React.ReactNode;
72
73
  };
@@ -77,7 +78,15 @@ const StatusDisplay = ({ item, message }: StatusDisplayProps) => {
77
78
  return <StatusDisplayContent level="unknown" label={t('Unknown')} />;
78
79
  }
79
80
 
80
- return <StatusDisplayContent level={item.level} customIcon={item.customIcon} label={item.label} message={message} />;
81
+ return (
82
+ <StatusDisplayContent
83
+ level={item.level}
84
+ customIcon={item.customIcon}
85
+ customColor={item.customColor}
86
+ label={item.label}
87
+ message={message}
88
+ />
89
+ );
81
90
  };
82
91
 
83
92
  export default StatusDisplay;
@@ -0,0 +1,36 @@
1
+ import * as React from 'react';
2
+ import { Alert } from '@patternfly/react-core';
3
+
4
+ import { useAppContext } from '../../hooks/useAppContext';
5
+ import { useTranslation } from '../../hooks/useTranslation';
6
+
7
+ interface PendingSyncDevicesAlertProps {
8
+ forSingleDevice?: boolean;
9
+ }
10
+
11
+ const getBrand = (isRHEM: boolean | undefined) => (isRHEM ? 'RHEM' : 'Flight Control');
12
+
13
+ export const PendingSyncDevicesAlert = ({ forSingleDevice }: PendingSyncDevicesAlertProps) => {
14
+ const { t } = useTranslation();
15
+ const { settings } = useAppContext();
16
+
17
+ const getMessage = () => {
18
+ if (forSingleDevice) {
19
+ return t(
20
+ '{{brand}} is waiting for the device to connect and report its status. It will report a ʼPending syncʼ status until it is able to reconnect. If it has configuration conflicts, it will report a ʼSuspendedʼ status and require manual action to resume.',
21
+ { brand: getBrand(settings.isRHEM) },
22
+ );
23
+ }
24
+
25
+ return t(
26
+ '{{brand}} is waiting for devices to connect and report their status. Devices will report a ʼPending syncʼ status until they are able to connect. Devices with configuration conflicts will report a ʼSuspendedʼ status and require manual action to resume.',
27
+ { brand: getBrand(settings.isRHEM) },
28
+ );
29
+ };
30
+
31
+ return (
32
+ <Alert variant="warning" isInline title={t('System recovery complete')}>
33
+ {getMessage()}
34
+ </Alert>
35
+ );
36
+ };
@@ -0,0 +1,144 @@
1
+ import * as React from 'react';
2
+ import { Trans } from 'react-i18next';
3
+ import { Alert, AlertActionLink, Stack, StackItem } from '@patternfly/react-core';
4
+
5
+ import { useTranslation } from '../../hooks/useTranslation';
6
+ import MassResumeDevicesModal from '../modals/massModals/ResumeDevicesModal/MassResumeDevicesModal';
7
+ import ResumeDevicesModal from '../modals/ResumeDevicesModal/ResumeDevicesModal';
8
+ import { DeviceResumeRequest } from '@flightctl/types';
9
+
10
+ export type ResumeMode = 'global' | 'device' | 'fleet';
11
+
12
+ type ModalType = 'mass' | 'confirm';
13
+
14
+ interface SuspendedDevicesAlertProps {
15
+ mode: ResumeMode;
16
+ suspendedCount?: number;
17
+ extraAction?: {
18
+ actionText: string;
19
+ title: React.ReactNode;
20
+ requestSelector: DeviceResumeRequest;
21
+ };
22
+ onResumeComplete?: VoidFunction;
23
+ }
24
+
25
+ const SuspendedDevicesAlert = ({
26
+ mode,
27
+ suspendedCount = 0,
28
+ extraAction,
29
+ onResumeComplete,
30
+ }: SuspendedDevicesAlertProps) => {
31
+ const { t } = useTranslation();
32
+ const [isMassModalOpen, setIsMassModalOpen] = React.useState(false);
33
+ const [isConfirmModalOpen, setIsConfirmModalOpen] = React.useState(false);
34
+
35
+ const openResumeModal = React.useCallback((modalType: ModalType) => {
36
+ if (modalType === 'mass') {
37
+ setIsMassModalOpen(true);
38
+ } else {
39
+ setIsConfirmModalOpen(true);
40
+ }
41
+ }, []);
42
+
43
+ const closeResumeModal = React.useCallback(
44
+ (modalType: ModalType, hasResumed?: boolean) => {
45
+ if (hasResumed) {
46
+ onResumeComplete?.();
47
+ }
48
+ if (modalType === 'mass') {
49
+ setIsMassModalOpen(false);
50
+ } else {
51
+ setIsConfirmModalOpen(false);
52
+ }
53
+ },
54
+ [onResumeComplete],
55
+ );
56
+
57
+ const getMainMessage = () => {
58
+ const suspendedCountStr = suspendedCount.toString();
59
+ switch (mode) {
60
+ case 'device':
61
+ return t(
62
+ "This device is suspended because its local configuration is newer than the server's record. It will not receive updates until it is resumed.",
63
+ );
64
+ case 'fleet':
65
+ return (
66
+ <Trans t={t} count={suspendedCount}>
67
+ <strong>{suspendedCountStr}</strong> <strong>devices in this fleet</strong> are suspended because their
68
+ local configuration is newer than the server&apos;s record. These devices will not receive updates until
69
+ they are resumed.
70
+ </Trans>
71
+ );
72
+ default:
73
+ return (
74
+ <Trans t={t} count={suspendedCount}>
75
+ <strong>{suspendedCountStr}</strong> devices are suspended because their local configuration is newer than
76
+ the server&apos;s record. These devices will not receive updates until they are resumed.
77
+ </Trans>
78
+ );
79
+ }
80
+ };
81
+
82
+ const getActions = () => {
83
+ const actionButtons: React.ReactElement[] = [];
84
+
85
+ if (extraAction) {
86
+ actionButtons.push(
87
+ <AlertActionLink key="extra-resume-action" onClick={() => openResumeModal('confirm')}>
88
+ {extraAction.actionText}
89
+ </AlertActionLink>,
90
+ );
91
+ }
92
+
93
+ actionButtons.push(
94
+ <AlertActionLink key="resume-suspended-devices" onClick={() => openResumeModal('mass')}>
95
+ {t('Resume suspended devices')}
96
+ </AlertActionLink>,
97
+ );
98
+
99
+ return actionButtons;
100
+ };
101
+
102
+ return (
103
+ <>
104
+ <Alert variant="danger" isInline title={t('Suspended devices detected')} actionLinks={getActions()}>
105
+ <Stack hasGutter>
106
+ <StackItem>{getMainMessage()}</StackItem>
107
+ <StackItem>
108
+ {mode === 'fleet' ? (
109
+ <Trans t={t}>
110
+ <strong>Warning:</strong> Please review this fleet&apos;s configuration before taking action. Resuming a
111
+ device will cause it to apply the current specification, which may be older than what is on the device.
112
+ </Trans>
113
+ ) : (
114
+ <Trans t={t}>
115
+ <strong>Warning:</strong> Please review device configurations before taking action. Resuming a device
116
+ will cause it to apply the current specification, which may be older than what is on the device.
117
+ </Trans>
118
+ )}
119
+ </StackItem>
120
+ </Stack>
121
+ </Alert>
122
+ {isMassModalOpen && (
123
+ <MassResumeDevicesModal
124
+ onClose={(hasResumed) => {
125
+ closeResumeModal('mass', hasResumed);
126
+ }}
127
+ />
128
+ )}
129
+ {isConfirmModalOpen && extraAction && (
130
+ <ResumeDevicesModal
131
+ mode={mode === 'global' ? 'device' : mode}
132
+ title={extraAction.title}
133
+ selector={extraAction.requestSelector}
134
+ expectedCount={suspendedCount}
135
+ onClose={(hasResumed) => {
136
+ closeResumeModal('confirm', hasResumed);
137
+ }}
138
+ />
139
+ )}
140
+ </>
141
+ );
142
+ };
143
+
144
+ export default SuspendedDevicesAlert;
@@ -0,0 +1,6 @@
1
+ .fctl-system-restore-banners {
2
+ padding: 1rem 0;
3
+ display: flex;
4
+ flex-direction: column;
5
+ gap: 1rem;
6
+ }