@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.
- package/dist/src/components/DetailsPage/DetailsPage.d.ts +2 -1
- package/dist/src/components/DetailsPage/DetailsPage.d.ts.map +1 -1
- package/dist/src/components/DetailsPage/DetailsPage.js +2 -1
- package/dist/src/components/DetailsPage/DetailsPage.js.map +1 -1
- package/dist/src/components/DetailsPage/DetailsPageActions.d.ts +10 -0
- package/dist/src/components/DetailsPage/DetailsPageActions.d.ts.map +1 -1
- package/dist/src/components/DetailsPage/DetailsPageActions.js +23 -1
- package/dist/src/components/DetailsPage/DetailsPageActions.js.map +1 -1
- package/dist/src/components/Device/DeviceDetails/DeviceDetailsPage.d.ts.map +1 -1
- package/dist/src/components/Device/DeviceDetails/DeviceDetailsPage.js +29 -3
- package/dist/src/components/Device/DeviceDetails/DeviceDetailsPage.js.map +1 -1
- package/dist/src/components/Device/DeviceDetails/DeviceDetailsTab.d.ts.map +1 -1
- package/dist/src/components/Device/DeviceDetails/DeviceDetailsTab.js +0 -4
- package/dist/src/components/Device/DeviceDetails/DeviceDetailsTab.js.map +1 -1
- package/dist/src/components/Device/DeviceDetails/DeviceDetailsTabContent/StatusContent.d.ts.map +1 -1
- package/dist/src/components/Device/DeviceDetails/DeviceDetailsTabContent/StatusContent.js +8 -2
- package/dist/src/components/Device/DeviceDetails/DeviceDetailsTabContent/StatusContent.js.map +1 -1
- package/dist/src/components/Device/DevicesPage/DecommissionedDeviceTableRow.d.ts.map +1 -1
- package/dist/src/components/Device/DevicesPage/DecommissionedDeviceTableRow.js +1 -3
- package/dist/src/components/Device/DevicesPage/DecommissionedDeviceTableRow.js.map +1 -1
- package/dist/src/components/Device/DevicesPage/DecommissionedDevicesTable.d.ts.map +1 -1
- package/dist/src/components/Device/DevicesPage/DecommissionedDevicesTable.js +0 -3
- package/dist/src/components/Device/DevicesPage/DecommissionedDevicesTable.js.map +1 -1
- package/dist/src/components/Device/DevicesPage/DeviceToolbarFilters.d.ts.map +1 -1
- package/dist/src/components/Device/DevicesPage/DeviceToolbarFilters.js +3 -1
- package/dist/src/components/Device/DevicesPage/DeviceToolbarFilters.js.map +1 -1
- package/dist/src/components/Device/DevicesPage/DevicesPage.d.ts.map +1 -1
- package/dist/src/components/Device/DevicesPage/DevicesPage.js +1 -1
- package/dist/src/components/Device/DevicesPage/DevicesPage.js.map +1 -1
- package/dist/src/components/Device/DevicesPage/EnrolledDeviceTableRow.d.ts +3 -1
- package/dist/src/components/Device/DevicesPage/EnrolledDeviceTableRow.d.ts.map +1 -1
- package/dist/src/components/Device/DevicesPage/EnrolledDeviceTableRow.js +12 -4
- package/dist/src/components/Device/DevicesPage/EnrolledDeviceTableRow.js.map +1 -1
- package/dist/src/components/Device/DevicesPage/EnrolledDevicesTable.d.ts +2 -1
- package/dist/src/components/Device/DevicesPage/EnrolledDevicesTable.d.ts.map +1 -1
- package/dist/src/components/Device/DevicesPage/EnrolledDevicesTable.js +7 -6
- package/dist/src/components/Device/DevicesPage/EnrolledDevicesTable.js.map +1 -1
- package/dist/src/components/Device/SystemdUnitsModal/TrackSystemdUnitsForm.js +1 -1
- package/dist/src/components/Device/SystemdUnitsModal/TrackSystemdUnitsForm.js.map +1 -1
- package/dist/src/components/Events/useEvents.d.ts.map +1 -1
- package/dist/src/components/Events/useEvents.js +12 -0
- package/dist/src/components/Events/useEvents.js.map +1 -1
- package/dist/src/components/Fleet/CreateFleet/steps/UpdatePolicyStep.js +1 -1
- package/dist/src/components/Fleet/CreateFleet/steps/UpdatePolicyStep.js.map +1 -1
- package/dist/src/components/Fleet/FleetDetails/FleetDetailsPage.d.ts.map +1 -1
- package/dist/src/components/Fleet/FleetDetails/FleetDetailsPage.js +4 -3
- package/dist/src/components/Fleet/FleetDetails/FleetDetailsPage.js.map +1 -1
- package/dist/src/components/Fleet/FleetDetails/FleetDevicesCharts.d.ts.map +1 -1
- package/dist/src/components/Fleet/FleetDetails/FleetDevicesCharts.js +7 -1
- package/dist/src/components/Fleet/FleetDetails/FleetDevicesCharts.js.map +1 -1
- package/dist/src/components/Fleet/FleetDetails/FleetRestoreBanner.d.ts +8 -0
- package/dist/src/components/Fleet/FleetDetails/FleetRestoreBanner.d.ts.map +1 -0
- package/dist/src/components/Fleet/FleetDetails/FleetRestoreBanner.js +36 -0
- package/dist/src/components/Fleet/FleetDetails/FleetRestoreBanner.js.map +1 -0
- package/dist/src/components/Fleet/FleetsPage.d.ts.map +1 -1
- package/dist/src/components/Fleet/FleetsPage.js +2 -0
- package/dist/src/components/Fleet/FleetsPage.js.map +1 -1
- package/dist/src/components/ListPage/ListPageActions.d.ts +3 -2
- package/dist/src/components/ListPage/ListPageActions.d.ts.map +1 -1
- package/dist/src/components/ListPage/ListPageActions.js +27 -1
- package/dist/src/components/ListPage/ListPageActions.js.map +1 -1
- package/dist/src/components/Masthead/CommandLineToolsPage.d.ts.map +1 -1
- package/dist/src/components/Masthead/CommandLineToolsPage.js +18 -14
- package/dist/src/components/Masthead/CommandLineToolsPage.js.map +1 -1
- package/dist/src/components/OverviewPage/Cards/Alerts/AlertsCard.d.ts.map +1 -1
- package/dist/src/components/OverviewPage/Cards/Alerts/AlertsCard.js +15 -5
- package/dist/src/components/OverviewPage/Cards/Alerts/AlertsCard.js.map +1 -1
- package/dist/src/components/OverviewPage/Cards/Status/DeviceStatusChart.d.ts.map +1 -1
- package/dist/src/components/OverviewPage/Cards/Status/DeviceStatusChart.js +7 -1
- package/dist/src/components/OverviewPage/Cards/Status/DeviceStatusChart.js.map +1 -1
- package/dist/src/components/OverviewPage/Overview.d.ts.map +1 -1
- package/dist/src/components/OverviewPage/Overview.js +4 -2
- package/dist/src/components/OverviewPage/Overview.js.map +1 -1
- package/dist/src/components/Status/StatusDisplay.d.ts +3 -1
- package/dist/src/components/Status/StatusDisplay.d.ts.map +1 -1
- package/dist/src/components/Status/StatusDisplay.js +8 -8
- package/dist/src/components/Status/StatusDisplay.js.map +1 -1
- package/dist/src/components/SystemRestore/PendingSyncDevicesAlert.d.ts +7 -0
- package/dist/src/components/SystemRestore/PendingSyncDevicesAlert.d.ts.map +1 -0
- package/dist/src/components/SystemRestore/PendingSyncDevicesAlert.js +22 -0
- package/dist/src/components/SystemRestore/PendingSyncDevicesAlert.js.map +1 -0
- package/dist/src/components/SystemRestore/SuspendedDevicesAlert.d.ts +16 -0
- package/dist/src/components/SystemRestore/SuspendedDevicesAlert.d.ts.map +1 -0
- package/dist/src/components/SystemRestore/SuspendedDevicesAlert.js +75 -0
- package/dist/src/components/SystemRestore/SuspendedDevicesAlert.js.map +1 -0
- package/dist/src/components/SystemRestore/SystemRestoreBanners.css +6 -0
- package/dist/src/components/SystemRestore/SystemRestoreBanners.d.ts +28 -0
- package/dist/src/components/SystemRestore/SystemRestoreBanners.d.ts.map +1 -0
- package/dist/src/components/SystemRestore/SystemRestoreBanners.js +38 -0
- package/dist/src/components/SystemRestore/SystemRestoreBanners.js.map +1 -0
- package/dist/src/components/charts/utils.js +1 -1
- package/dist/src/components/charts/utils.js.map +1 -1
- package/dist/src/components/common/OrganizationGuard.d.ts +13 -0
- package/dist/src/components/common/OrganizationGuard.d.ts.map +1 -0
- package/dist/src/components/common/OrganizationGuard.js +106 -0
- package/dist/src/components/common/OrganizationGuard.js.map +1 -0
- package/dist/src/components/common/OrganizationSelector.d.ts +8 -0
- package/dist/src/components/common/OrganizationSelector.d.ts.map +1 -0
- package/dist/src/components/common/OrganizationSelector.js +92 -0
- package/dist/src/components/common/OrganizationSelector.js.map +1 -0
- package/dist/src/components/common/PageNavigation.d.ts +4 -0
- package/dist/src/components/common/PageNavigation.d.ts.map +1 -0
- package/dist/src/components/common/PageNavigation.js +46 -0
- package/dist/src/components/common/PageNavigation.js.map +1 -0
- package/dist/src/components/form/FilterSelect.css +3 -4
- package/dist/src/components/form/validations.d.ts +9 -0
- package/dist/src/components/form/validations.d.ts.map +1 -1
- package/dist/src/components/form/validations.js +12 -1
- package/dist/src/components/form/validations.js.map +1 -1
- package/dist/src/components/modals/ResumeDevicesModal/ResumeDevicesModal.d.ts +15 -0
- package/dist/src/components/modals/ResumeDevicesModal/ResumeDevicesModal.d.ts.map +1 -0
- package/dist/src/components/modals/ResumeDevicesModal/ResumeDevicesModal.js +56 -0
- package/dist/src/components/modals/ResumeDevicesModal/ResumeDevicesModal.js.map +1 -0
- package/dist/src/components/modals/massModals/ResumeDevicesModal/MassResumeDevicesModal.d.ts +7 -0
- package/dist/src/components/modals/massModals/ResumeDevicesModal/MassResumeDevicesModal.d.ts.map +1 -0
- package/dist/src/components/modals/massModals/ResumeDevicesModal/MassResumeDevicesModal.js +265 -0
- package/dist/src/components/modals/massModals/ResumeDevicesModal/MassResumeDevicesModal.js.map +1 -0
- package/dist/src/components/modals/massModals/ResumeDevicesModal/ResumeAllDevicesConfirmationDialog.d.ts +9 -0
- package/dist/src/components/modals/massModals/ResumeDevicesModal/ResumeAllDevicesConfirmationDialog.d.ts.map +1 -0
- package/dist/src/components/modals/massModals/ResumeDevicesModal/ResumeAllDevicesConfirmationDialog.js +27 -0
- package/dist/src/components/modals/massModals/ResumeDevicesModal/ResumeAllDevicesConfirmationDialog.js.map +1 -0
- package/dist/src/hooks/useAccessReview.d.ts.map +1 -1
- package/dist/src/hooks/useAccessReview.js +17 -4
- package/dist/src/hooks/useAccessReview.js.map +1 -1
- package/dist/src/hooks/useAlertsEnabled.d.ts +2 -0
- package/dist/src/hooks/useAlertsEnabled.d.ts.map +1 -0
- package/dist/src/hooks/useAlertsEnabled.js +50 -0
- package/dist/src/hooks/useAlertsEnabled.js.map +1 -0
- package/dist/src/hooks/useAppContext.d.ts +3 -6
- package/dist/src/hooks/useAppContext.d.ts.map +1 -1
- package/dist/src/hooks/useAppContext.js +1 -0
- package/dist/src/hooks/useAppContext.js.map +1 -1
- package/dist/src/hooks/useFetch.d.ts +6 -7
- package/dist/src/hooks/useFetch.d.ts.map +1 -1
- package/dist/src/hooks/useFetch.js +2 -3
- package/dist/src/hooks/useFetch.js.map +1 -1
- package/dist/src/hooks/useFetchPeriodically.d.ts.map +1 -1
- package/dist/src/hooks/useFetchPeriodically.js +4 -9
- package/dist/src/hooks/useFetchPeriodically.js.map +1 -1
- package/dist/src/hooks/useSystemRestoreContext.d.ts +16 -0
- package/dist/src/hooks/useSystemRestoreContext.d.ts.map +1 -0
- package/dist/src/hooks/useSystemRestoreContext.js +45 -0
- package/dist/src/hooks/useSystemRestoreContext.js.map +1 -0
- package/dist/src/types/extraTypes.d.ts +17 -18
- package/dist/src/types/extraTypes.d.ts.map +1 -1
- package/dist/src/types/extraTypes.js +1 -6
- package/dist/src/types/extraTypes.js.map +1 -1
- package/dist/src/types/rbac.d.ts +1 -0
- package/dist/src/types/rbac.d.ts.map +1 -1
- package/dist/src/types/rbac.js +1 -0
- package/dist/src/types/rbac.js.map +1 -1
- package/dist/src/utils/api.d.ts +2 -15
- package/dist/src/utils/api.d.ts.map +1 -1
- package/dist/src/utils/api.js +1 -40
- package/dist/src/utils/api.js.map +1 -1
- package/dist/src/utils/devices.d.ts +2 -0
- package/dist/src/utils/devices.d.ts.map +1 -1
- package/dist/src/utils/devices.js +11 -1
- package/dist/src/utils/devices.js.map +1 -1
- package/dist/src/utils/organizationStorage.d.ts +4 -0
- package/dist/src/utils/organizationStorage.d.ts.map +1 -0
- package/dist/src/utils/organizationStorage.js +18 -0
- package/dist/src/utils/organizationStorage.js.map +1 -0
- package/dist/src/utils/query.d.ts +2 -0
- package/dist/src/utils/query.d.ts.map +1 -1
- package/dist/src/utils/query.js +16 -0
- package/dist/src/utils/query.js.map +1 -1
- package/dist/src/utils/status/common.d.ts +1 -0
- package/dist/src/utils/status/common.d.ts.map +1 -1
- package/dist/src/utils/status/common.js.map +1 -1
- package/dist/src/utils/status/devices.d.ts +5 -0
- package/dist/src/utils/status/devices.d.ts.map +1 -1
- package/dist/src/utils/status/devices.js +44 -5
- package/dist/src/utils/status/devices.js.map +1 -1
- package/dist/src/utils/status/fleet.js +1 -1
- package/dist/src/utils/status/fleet.js.map +1 -1
- package/dist/src/utils/status/repository.js +1 -1
- package/dist/src/utils/status/repository.js.map +1 -1
- package/package.json +1 -1
- package/src/components/DetailsPage/DetailsPage.tsx +3 -0
- package/src/components/DetailsPage/DetailsPageActions.tsx +45 -0
- package/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx +57 -5
- package/src/components/Device/DeviceDetails/DeviceDetailsTab.tsx +0 -5
- package/src/components/Device/DeviceDetails/DeviceDetailsTabContent/StatusContent.tsx +11 -3
- package/src/components/Device/DevicesPage/DecommissionedDeviceTableRow.tsx +0 -2
- package/src/components/Device/DevicesPage/DecommissionedDevicesTable.tsx +0 -3
- package/src/components/Device/DevicesPage/DeviceToolbarFilters.tsx +5 -1
- package/src/components/Device/DevicesPage/DevicesPage.tsx +1 -0
- package/src/components/Device/DevicesPage/EnrolledDeviceTableRow.tsx +15 -3
- package/src/components/Device/DevicesPage/EnrolledDevicesTable.tsx +11 -5
- package/src/components/Device/SystemdUnitsModal/TrackSystemdUnitsForm.tsx +1 -1
- package/src/components/Events/useEvents.ts +12 -0
- package/src/components/Fleet/CreateFleet/steps/UpdatePolicyStep.tsx +1 -1
- package/src/components/Fleet/FleetDetails/FleetDetailsPage.tsx +4 -5
- package/src/components/Fleet/FleetDetails/FleetDevicesCharts.tsx +9 -3
- package/src/components/Fleet/FleetDetails/FleetRestoreBanner.tsx +46 -0
- package/src/components/Fleet/FleetsPage.tsx +2 -0
- package/src/components/ListPage/ListPageActions.tsx +46 -3
- package/src/components/Masthead/CommandLineToolsPage.tsx +17 -14
- package/src/components/OverviewPage/Cards/Alerts/AlertsCard.tsx +19 -5
- package/src/components/OverviewPage/Cards/Status/DeviceStatusChart.tsx +8 -2
- package/src/components/OverviewPage/Overview.tsx +5 -2
- package/src/components/Status/StatusDisplay.tsx +32 -23
- package/src/components/SystemRestore/PendingSyncDevicesAlert.tsx +36 -0
- package/src/components/SystemRestore/SuspendedDevicesAlert.tsx +144 -0
- package/src/components/SystemRestore/SystemRestoreBanners.css +6 -0
- package/src/components/SystemRestore/SystemRestoreBanners.tsx +82 -0
- package/src/components/charts/utils.ts +1 -1
- package/src/components/common/OrganizationGuard.tsx +124 -0
- package/src/components/common/OrganizationSelector.tsx +192 -0
- package/src/components/common/PageNavigation.tsx +103 -0
- package/src/components/form/FilterSelect.css +3 -4
- package/src/components/form/validations.ts +14 -0
- package/src/components/modals/ResumeDevicesModal/ResumeDevicesModal.tsx +114 -0
- package/src/components/modals/massModals/ResumeDevicesModal/MassResumeDevicesModal.tsx +465 -0
- package/src/components/modals/massModals/ResumeDevicesModal/ResumeAllDevicesConfirmationDialog.tsx +55 -0
- package/src/hooks/useAccessReview.ts +20 -4
- package/src/hooks/useAlertsEnabled.ts +50 -0
- package/src/hooks/useAppContext.tsx +9 -7
- package/src/hooks/useFetch.ts +1 -3
- package/src/hooks/useFetchPeriodically.ts +4 -11
- package/src/hooks/useSystemRestoreContext.tsx +54 -0
- package/src/types/extraTypes.ts +17 -23
- package/src/types/rbac.ts +1 -0
- package/src/utils/api.ts +2 -51
- package/src/utils/devices.ts +11 -1
- package/src/utils/organizationStorage.ts +13 -0
- package/src/utils/query.ts +22 -0
- package/src/utils/status/common.ts +1 -0
- package/src/utils/status/devices.ts +49 -2
- package/src/utils/status/fleet.ts +1 -1
- package/src/utils/status/repository.ts +1 -1
- package/dist/src/hooks/useAlerts.d.ts +0 -26
- package/dist/src/hooks/useAlerts.d.ts.map +0 -1
- package/dist/src/hooks/useAlerts.js +0 -114
- package/dist/src/hooks/useAlerts.js.map +0 -1
- package/dist/src/utils/metrics.d.ts +0 -9
- package/dist/src/utils/metrics.d.ts.map +0 -1
- package/dist/src/utils/metrics.js +0 -48
- package/dist/src/utils/metrics.js.map +0 -1
- package/src/hooks/useAlerts.ts +0 -147
- package/src/utils/metrics.ts +0 -49
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Alert,
|
|
4
|
+
Button,
|
|
5
|
+
FormGroup,
|
|
6
|
+
MenuToggle,
|
|
7
|
+
MenuToggleElement,
|
|
8
|
+
Modal,
|
|
9
|
+
ModalVariant,
|
|
10
|
+
Radio,
|
|
11
|
+
Select,
|
|
12
|
+
SelectList,
|
|
13
|
+
SelectOption,
|
|
14
|
+
Spinner,
|
|
15
|
+
Stack,
|
|
16
|
+
StackItem,
|
|
17
|
+
Text,
|
|
18
|
+
TextContent,
|
|
19
|
+
} from '@patternfly/react-core';
|
|
20
|
+
import { Trans } from 'react-i18next';
|
|
21
|
+
import { Formik, useFormikContext } from 'formik';
|
|
22
|
+
import { DeviceList, DeviceResumeRequest, DeviceResumeResponse, Fleet } from '@flightctl/types';
|
|
23
|
+
|
|
24
|
+
import { FlightCtlLabel } from '../../../../types/extraTypes';
|
|
25
|
+
import { useTranslation } from '../../../../hooks/useTranslation';
|
|
26
|
+
import { useFetch } from '../../../../hooks/useFetch';
|
|
27
|
+
import LabelsField from '../../../form/LabelsField';
|
|
28
|
+
import FlightCtlForm from '../../../form/FlightCtlForm';
|
|
29
|
+
import { createMassResumeValidationSchema } from '../../../form/validations';
|
|
30
|
+
import { getErrorMessage } from '../../../../utils/error';
|
|
31
|
+
import { commonQueries } from '../../../../utils/query';
|
|
32
|
+
import { getApiListCount } from '../../../../utils/api';
|
|
33
|
+
import { fromAPILabel, labelToExactApiMatchString } from '../../../../utils/labels';
|
|
34
|
+
import { useFleets } from '../../../Fleet/useFleets';
|
|
35
|
+
import ResumeAllDevicesConfirmationDialog from './ResumeAllDevicesConfirmationDialog';
|
|
36
|
+
|
|
37
|
+
// Adds an artificial delay to make sure that the user notices the count is refreshing.
|
|
38
|
+
// This is specially needed when users switch between modes, and the selection for the new mode is already valid.
|
|
39
|
+
const showSpinnerBriefly = () => new Promise((resolve) => setTimeout(resolve, 450));
|
|
40
|
+
|
|
41
|
+
type MassResumeFormValues = {
|
|
42
|
+
mode: SelectionMode;
|
|
43
|
+
fleetId: string;
|
|
44
|
+
labels: FlightCtlLabel[];
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
interface MassResumeDevicesModalProps {
|
|
48
|
+
onClose: (hasResumed?: boolean) => void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
enum SelectionMode {
|
|
52
|
+
FLEET = 'fleet',
|
|
53
|
+
LABELS = 'labels',
|
|
54
|
+
ALL = 'all',
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const getSelectedFleetLabels = (fleets: Fleet[], fleetId: string) => {
|
|
58
|
+
const selectedFleet = fleets.find((fleet) => fleet.metadata.name === fleetId);
|
|
59
|
+
if (!selectedFleet) {
|
|
60
|
+
throw new Error('Selected fleet not found');
|
|
61
|
+
}
|
|
62
|
+
return fromAPILabel(selectedFleet.spec.selector?.matchLabels || {});
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const MassResumeDevicesModalContent = ({ onClose }: MassResumeDevicesModalProps) => {
|
|
66
|
+
const { t } = useTranslation();
|
|
67
|
+
const { get, post } = useFetch();
|
|
68
|
+
const { values, setFieldValue, isValid, dirty } = useFormikContext<MassResumeFormValues>();
|
|
69
|
+
|
|
70
|
+
const { fleets, isLoading: fleetsLoading } = useFleets({});
|
|
71
|
+
const [isFleetListOpen, setIsFleetSelectOpen] = React.useState(false);
|
|
72
|
+
const [isSubmitting, setIsSubmitting] = React.useState(false);
|
|
73
|
+
const [submitError, setSubmitError] = React.useState<string | undefined>(undefined);
|
|
74
|
+
const [showResumeAllConfirmation, setShowResumeAllConfirmation] = React.useState(false);
|
|
75
|
+
|
|
76
|
+
// Resume result state
|
|
77
|
+
const [resumedCount, setResumedCount] = React.useState<number | undefined>(undefined);
|
|
78
|
+
|
|
79
|
+
// Device count state
|
|
80
|
+
const [deviceCountNum, setDeviceCountNum] = React.useState<number>(0);
|
|
81
|
+
const [isCountLoading, setIsCountLoading] = React.useState(false);
|
|
82
|
+
const [countError, setCountError] = React.useState<string | null>(null);
|
|
83
|
+
|
|
84
|
+
const hasResumedAtLeastOne = resumedCount !== undefined && resumedCount > 0;
|
|
85
|
+
const hasResumedAllExpected = deviceCountNum > 0 && resumedCount === deviceCountNum;
|
|
86
|
+
const isSubmitEnabled =
|
|
87
|
+
(values.mode === SelectionMode.ALL || deviceCountNum > 0) &&
|
|
88
|
+
!isSubmitting &&
|
|
89
|
+
!isCountLoading &&
|
|
90
|
+
isValid &&
|
|
91
|
+
resumedCount === undefined;
|
|
92
|
+
const deviceCount = deviceCountNum.toString();
|
|
93
|
+
|
|
94
|
+
const loadMatchingDevicesCount = React.useCallback(
|
|
95
|
+
async (criteria: { fleetId?: string; labels?: FlightCtlLabel[]; all?: boolean }) => {
|
|
96
|
+
setIsCountLoading(true);
|
|
97
|
+
setCountError(null);
|
|
98
|
+
setDeviceCountNum(0);
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
let queryEndpoint: string;
|
|
102
|
+
|
|
103
|
+
if (criteria.all) {
|
|
104
|
+
queryEndpoint = commonQueries.getAllSuspendedDevicesCount();
|
|
105
|
+
} else {
|
|
106
|
+
const fleetLabels = criteria.fleetId
|
|
107
|
+
? getSelectedFleetLabels(fleets, criteria.fleetId)
|
|
108
|
+
: criteria.labels || [];
|
|
109
|
+
if (fleetLabels.length === 0) {
|
|
110
|
+
throw new Error('Invalid criteria: must provide either fleetId or labels');
|
|
111
|
+
}
|
|
112
|
+
queryEndpoint = commonQueries.getSuspendedDeviceCountByLabels(fleetLabels);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const deviceResult = await get<DeviceList>(queryEndpoint);
|
|
116
|
+
await showSpinnerBriefly();
|
|
117
|
+
setDeviceCountNum(getApiListCount(deviceResult) || 0);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
await showSpinnerBriefly();
|
|
120
|
+
setCountError(t('Failed to obtain the number of matching devices'));
|
|
121
|
+
} finally {
|
|
122
|
+
setIsCountLoading(false);
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
[get, t, fleets],
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const performResume = async () => {
|
|
129
|
+
setIsSubmitting(true);
|
|
130
|
+
setSubmitError(undefined);
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
let labels: FlightCtlLabel[];
|
|
134
|
+
|
|
135
|
+
if (values.mode === SelectionMode.ALL) {
|
|
136
|
+
labels = [];
|
|
137
|
+
} else {
|
|
138
|
+
if (values.mode === SelectionMode.FLEET) {
|
|
139
|
+
labels = getSelectedFleetLabels(fleets, values.fleetId);
|
|
140
|
+
} else {
|
|
141
|
+
labels = values.labels;
|
|
142
|
+
}
|
|
143
|
+
// This shouldn't happen due to validations, but since an empty label selector would target all existing devices,
|
|
144
|
+
// wake sure the UI does't accidentally submit a request with an empty selector
|
|
145
|
+
const emptySelection = labels.every((label) => label.key === '');
|
|
146
|
+
if (labels.length === 0 || emptySelection) {
|
|
147
|
+
throw new Error('The current selection would target all devices.');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const resumeRequest: DeviceResumeRequest = {
|
|
152
|
+
labelSelector: labels.map((label) => labelToExactApiMatchString(label)).join(','),
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const resumeResponse = await post<DeviceResumeRequest, DeviceResumeResponse>(
|
|
156
|
+
'deviceactions/resume',
|
|
157
|
+
resumeRequest,
|
|
158
|
+
);
|
|
159
|
+
setResumedCount(resumeResponse.resumedDevices || 0);
|
|
160
|
+
} catch (error) {
|
|
161
|
+
setSubmitError(getErrorMessage(error));
|
|
162
|
+
} finally {
|
|
163
|
+
setIsSubmitting(false);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const handleResume = () => {
|
|
168
|
+
if (values.mode === SelectionMode.ALL) {
|
|
169
|
+
setShowResumeAllConfirmation(true);
|
|
170
|
+
} else {
|
|
171
|
+
performResume();
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const handleSelectionModeChanged = (mode: SelectionMode) => {
|
|
176
|
+
setFieldValue('mode', mode);
|
|
177
|
+
|
|
178
|
+
if (mode === SelectionMode.ALL) {
|
|
179
|
+
// Load count for all suspended devices
|
|
180
|
+
loadMatchingDevicesCount({ all: true });
|
|
181
|
+
} else if (mode === SelectionMode.FLEET && values.fleetId) {
|
|
182
|
+
// If switching to a mode that already had a valid selection, we refresh the count
|
|
183
|
+
loadMatchingDevicesCount({ fleetId: values.fleetId });
|
|
184
|
+
} else if (mode === SelectionMode.LABELS && values.labels.length > 0) {
|
|
185
|
+
loadMatchingDevicesCount({ labels: values.labels });
|
|
186
|
+
} else {
|
|
187
|
+
// Clear the count if there isn't a valid selection
|
|
188
|
+
setDeviceCountNum(0);
|
|
189
|
+
setCountError(null);
|
|
190
|
+
setIsCountLoading(false);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const handleFleetSelected = (fleetId: string) => {
|
|
195
|
+
setFieldValue('fleetId', fleetId);
|
|
196
|
+
setIsFleetSelectOpen(false);
|
|
197
|
+
|
|
198
|
+
if (fleetId) {
|
|
199
|
+
loadMatchingDevicesCount({ fleetId });
|
|
200
|
+
} else {
|
|
201
|
+
setDeviceCountNum(0);
|
|
202
|
+
setCountError(null);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const handleLabelsChanged = (newLabels: FlightCtlLabel[], hasErrors: boolean) => {
|
|
207
|
+
if (hasErrors || newLabels.length === 0) {
|
|
208
|
+
setDeviceCountNum(0);
|
|
209
|
+
setCountError(null);
|
|
210
|
+
return;
|
|
211
|
+
} else if (newLabels.length > 0) {
|
|
212
|
+
loadMatchingDevicesCount({ labels: newLabels });
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
return (
|
|
217
|
+
<Modal
|
|
218
|
+
variant={ModalVariant.medium}
|
|
219
|
+
title={t('Resume devices')}
|
|
220
|
+
isOpen
|
|
221
|
+
onClose={() => onClose(hasResumedAtLeastOne)}
|
|
222
|
+
actions={[
|
|
223
|
+
<Button
|
|
224
|
+
key="resume"
|
|
225
|
+
variant="primary"
|
|
226
|
+
onClick={handleResume}
|
|
227
|
+
isLoading={isSubmitting}
|
|
228
|
+
isDisabled={!isSubmitEnabled}
|
|
229
|
+
>
|
|
230
|
+
{t('Resume selection')}
|
|
231
|
+
</Button>,
|
|
232
|
+
<Button key="cancel" variant="link" onClick={() => onClose(hasResumedAtLeastOne)} isDisabled={isSubmitting}>
|
|
233
|
+
{hasResumedAtLeastOne ? t('Close') : t('Cancel')}
|
|
234
|
+
</Button>,
|
|
235
|
+
]}
|
|
236
|
+
>
|
|
237
|
+
<Stack hasGutter>
|
|
238
|
+
<FlightCtlForm>
|
|
239
|
+
<StackItem>
|
|
240
|
+
<TextContent>
|
|
241
|
+
<Text>
|
|
242
|
+
{t(
|
|
243
|
+
"Following a system restore, devices have been identified with configurations newer than the server's records. To prevent data loss, they have been suspended from receiving updates.",
|
|
244
|
+
)}
|
|
245
|
+
</Text>
|
|
246
|
+
</TextContent>
|
|
247
|
+
</StackItem>
|
|
248
|
+
<StackItem>
|
|
249
|
+
<TextContent>
|
|
250
|
+
<Text>{t('Choose the criteria to select the devices to resume')}:</Text>
|
|
251
|
+
</TextContent>
|
|
252
|
+
</StackItem>
|
|
253
|
+
|
|
254
|
+
<StackItem>
|
|
255
|
+
<FormGroup isRequired fieldId="selection-mode">
|
|
256
|
+
<Radio
|
|
257
|
+
label={t('Fleet')}
|
|
258
|
+
id="selectionModeFleet"
|
|
259
|
+
name="selectionMode"
|
|
260
|
+
isChecked={values.mode === SelectionMode.FLEET}
|
|
261
|
+
onChange={() => {
|
|
262
|
+
handleSelectionModeChanged(SelectionMode.FLEET);
|
|
263
|
+
}}
|
|
264
|
+
description={t('Resume all suspended devices associated with a given fleet')}
|
|
265
|
+
/>
|
|
266
|
+
<Radio
|
|
267
|
+
label={t('Labels')}
|
|
268
|
+
id="selectionModeLabels"
|
|
269
|
+
name="selectionMode"
|
|
270
|
+
isChecked={values.mode === SelectionMode.LABELS}
|
|
271
|
+
onChange={() => {
|
|
272
|
+
handleSelectionModeChanged(SelectionMode.LABELS);
|
|
273
|
+
}}
|
|
274
|
+
description={t('Resume all suspended devices matching the specified labels')}
|
|
275
|
+
/>
|
|
276
|
+
<Radio
|
|
277
|
+
label={t('All suspended devices')}
|
|
278
|
+
id="selectionModeAll"
|
|
279
|
+
name="selectionMode"
|
|
280
|
+
isChecked={values.mode === SelectionMode.ALL}
|
|
281
|
+
onChange={() => {
|
|
282
|
+
handleSelectionModeChanged(SelectionMode.ALL);
|
|
283
|
+
}}
|
|
284
|
+
description={t('Resume all suspended devices')}
|
|
285
|
+
/>
|
|
286
|
+
</FormGroup>
|
|
287
|
+
</StackItem>
|
|
288
|
+
|
|
289
|
+
{values.mode === SelectionMode.FLEET && (
|
|
290
|
+
<StackItem>
|
|
291
|
+
<FormGroup label={t('Fleet')} isRequired fieldId="fleetId">
|
|
292
|
+
<Select
|
|
293
|
+
id="fleetSelection"
|
|
294
|
+
isOpen={isFleetListOpen}
|
|
295
|
+
selected={values.fleetId}
|
|
296
|
+
onSelect={(_, selection) => handleFleetSelected(selection as string)}
|
|
297
|
+
onOpenChange={(isOpen) => setIsFleetSelectOpen(isOpen)}
|
|
298
|
+
toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
|
|
299
|
+
<MenuToggle
|
|
300
|
+
ref={toggleRef}
|
|
301
|
+
onClick={() => setIsFleetSelectOpen(!isFleetListOpen)}
|
|
302
|
+
isExpanded={isFleetListOpen}
|
|
303
|
+
isDisabled={fleetsLoading}
|
|
304
|
+
style={{ width: '100%' }}
|
|
305
|
+
>
|
|
306
|
+
{values.fleetId || t('Select a fleet')}
|
|
307
|
+
</MenuToggle>
|
|
308
|
+
)}
|
|
309
|
+
shouldFocusToggleOnSelect
|
|
310
|
+
>
|
|
311
|
+
<SelectList>
|
|
312
|
+
{fleets.map((fleet) => {
|
|
313
|
+
const fleetId = fleet.metadata.name || '';
|
|
314
|
+
const deviceSelectorStr = Object.entries(fleet.spec?.selector?.matchLabels || {})
|
|
315
|
+
.map(([key, value]) => (value ? `${key}=${value}` : key))
|
|
316
|
+
.join(',');
|
|
317
|
+
|
|
318
|
+
let description = '';
|
|
319
|
+
if (deviceSelectorStr) {
|
|
320
|
+
description = `${t('Device selector labels')}: ${deviceSelectorStr}`;
|
|
321
|
+
} else {
|
|
322
|
+
description = t('This fleet does not select any devices');
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return (
|
|
326
|
+
<SelectOption
|
|
327
|
+
key={fleetId}
|
|
328
|
+
value={fleetId}
|
|
329
|
+
description={description}
|
|
330
|
+
isDisabled={!deviceSelectorStr}
|
|
331
|
+
>
|
|
332
|
+
{fleetId}
|
|
333
|
+
</SelectOption>
|
|
334
|
+
);
|
|
335
|
+
})}
|
|
336
|
+
</SelectList>
|
|
337
|
+
</Select>
|
|
338
|
+
</FormGroup>
|
|
339
|
+
</StackItem>
|
|
340
|
+
)}
|
|
341
|
+
|
|
342
|
+
{values.mode === SelectionMode.LABELS && (
|
|
343
|
+
<StackItem>
|
|
344
|
+
<FormGroup label={t('Device labels')} fieldId="labelsSelection" isRequired>
|
|
345
|
+
<LabelsField name="labels" onChangeCallback={handleLabelsChanged} />
|
|
346
|
+
</FormGroup>
|
|
347
|
+
</StackItem>
|
|
348
|
+
)}
|
|
349
|
+
</FlightCtlForm>
|
|
350
|
+
|
|
351
|
+
{isValid && dirty && resumedCount === undefined && (
|
|
352
|
+
<StackItem>
|
|
353
|
+
{isCountLoading ? (
|
|
354
|
+
<Alert variant="info" isInline title={t('Refreshing device count')}>
|
|
355
|
+
<Spinner size="md" className="pf-v5-u-mr-sm" />
|
|
356
|
+
{t('Checking how many suspended devices match your criteria...')}
|
|
357
|
+
</Alert>
|
|
358
|
+
) : countError ? (
|
|
359
|
+
<Alert variant="warning" isInline title={t('Unable to refresh device count')}>
|
|
360
|
+
{countError}
|
|
361
|
+
</Alert>
|
|
362
|
+
) : deviceCountNum > 0 ? (
|
|
363
|
+
<Alert
|
|
364
|
+
variant={values.mode === SelectionMode.ALL ? 'warning' : 'success'}
|
|
365
|
+
isInline
|
|
366
|
+
title={t('Devices found')}
|
|
367
|
+
>
|
|
368
|
+
{values.mode === SelectionMode.FLEET ? (
|
|
369
|
+
<Trans t={t}>
|
|
370
|
+
<strong>{deviceCount}</strong> suspended devices are currently associated with fleet{' '}
|
|
371
|
+
<strong>{values.fleetId}</strong>.
|
|
372
|
+
</Trans>
|
|
373
|
+
) : values.mode === SelectionMode.LABELS ? (
|
|
374
|
+
<Trans t={t}>
|
|
375
|
+
<strong>{deviceCount}</strong> suspended devices match the specified labels.
|
|
376
|
+
</Trans>
|
|
377
|
+
) : (
|
|
378
|
+
<>
|
|
379
|
+
<Trans t={t} count={deviceCountNum}>
|
|
380
|
+
You are about to resume all <strong>{deviceCount}</strong> suspended devices.
|
|
381
|
+
</Trans>
|
|
382
|
+
{t(
|
|
383
|
+
'This action is irreversible and will allow all affected devices to receive new configuration updates from the server.',
|
|
384
|
+
)}
|
|
385
|
+
</>
|
|
386
|
+
)}
|
|
387
|
+
</Alert>
|
|
388
|
+
) : (
|
|
389
|
+
<Alert variant="warning" isInline title={t('No devices found')}>
|
|
390
|
+
{values.mode === SelectionMode.FLEET ? (
|
|
391
|
+
<Trans t={t}>
|
|
392
|
+
No suspended devices are associated with fleet <strong>{values.fleetId}</strong>.
|
|
393
|
+
</Trans>
|
|
394
|
+
) : values.mode === SelectionMode.ALL ? (
|
|
395
|
+
t('No suspended devices found.')
|
|
396
|
+
) : (
|
|
397
|
+
t('No suspended devices match the specified labels.')
|
|
398
|
+
)}
|
|
399
|
+
</Alert>
|
|
400
|
+
)}
|
|
401
|
+
</StackItem>
|
|
402
|
+
)}
|
|
403
|
+
|
|
404
|
+
{submitError && (
|
|
405
|
+
<StackItem>
|
|
406
|
+
<Alert isInline variant="danger" title={t('Resume devices failed')}>
|
|
407
|
+
{submitError}
|
|
408
|
+
</Alert>
|
|
409
|
+
</StackItem>
|
|
410
|
+
)}
|
|
411
|
+
{resumedCount !== undefined && hasResumedAllExpected && (
|
|
412
|
+
<StackItem>
|
|
413
|
+
<Alert isInline variant="success" title={t('Resume successful')}>
|
|
414
|
+
{t('{{ resumedCount }} devices were resumed', { resumedCount })}
|
|
415
|
+
</Alert>
|
|
416
|
+
</StackItem>
|
|
417
|
+
)}
|
|
418
|
+
|
|
419
|
+
{resumedCount !== undefined && !hasResumedAllExpected && (
|
|
420
|
+
<StackItem>
|
|
421
|
+
<Alert isInline variant="warning" title={t('Resumed with warnings')}>
|
|
422
|
+
{t('{{ expectedCount }} devices to resume, and {{ resumedCount }} resumed successfully', {
|
|
423
|
+
expectedCount: deviceCountNum,
|
|
424
|
+
resumedCount,
|
|
425
|
+
})}
|
|
426
|
+
</Alert>
|
|
427
|
+
</StackItem>
|
|
428
|
+
)}
|
|
429
|
+
</Stack>
|
|
430
|
+
{showResumeAllConfirmation && (
|
|
431
|
+
<ResumeAllDevicesConfirmationDialog
|
|
432
|
+
deviceCountNum={deviceCountNum}
|
|
433
|
+
onClose={(doConfirm) => {
|
|
434
|
+
setShowResumeAllConfirmation(false);
|
|
435
|
+
if (doConfirm) {
|
|
436
|
+
performResume();
|
|
437
|
+
}
|
|
438
|
+
}}
|
|
439
|
+
/>
|
|
440
|
+
)}
|
|
441
|
+
</Modal>
|
|
442
|
+
);
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
const MassResumeDevicesModal = ({ onClose }: MassResumeDevicesModalProps) => {
|
|
446
|
+
const { t } = useTranslation();
|
|
447
|
+
|
|
448
|
+
return (
|
|
449
|
+
<Formik<MassResumeFormValues>
|
|
450
|
+
initialValues={{
|
|
451
|
+
mode: SelectionMode.FLEET,
|
|
452
|
+
fleetId: '',
|
|
453
|
+
labels: [],
|
|
454
|
+
}}
|
|
455
|
+
validationSchema={createMassResumeValidationSchema(t)}
|
|
456
|
+
onSubmit={() => {
|
|
457
|
+
// This will be handled by the inner component
|
|
458
|
+
}}
|
|
459
|
+
>
|
|
460
|
+
<MassResumeDevicesModalContent onClose={onClose} />
|
|
461
|
+
</Formik>
|
|
462
|
+
);
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
export default MassResumeDevicesModal;
|
package/src/components/modals/massModals/ResumeDevicesModal/ResumeAllDevicesConfirmationDialog.tsx
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Button, Modal, ModalVariant, Stack, StackItem, Text, TextContent } from '@patternfly/react-core';
|
|
3
|
+
import { Trans } from 'react-i18next';
|
|
4
|
+
|
|
5
|
+
import { useTranslation } from '../../../../hooks/useTranslation';
|
|
6
|
+
|
|
7
|
+
interface ResumeAllDevicesConfirmationModalProps {
|
|
8
|
+
deviceCountNum: number;
|
|
9
|
+
onClose: (doConfirm: boolean) => void;
|
|
10
|
+
isSubmitting?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const ResumeAllDevicesConfirmationModal = ({ deviceCountNum, onClose }: ResumeAllDevicesConfirmationModalProps) => {
|
|
14
|
+
const { t } = useTranslation();
|
|
15
|
+
|
|
16
|
+
const deviceCount = deviceCountNum.toString();
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<Modal
|
|
20
|
+
variant={ModalVariant.small}
|
|
21
|
+
title={t('Resume all {{ deviceCount }} devices?', { deviceCount })}
|
|
22
|
+
isOpen
|
|
23
|
+
onClose={() => onClose(false)}
|
|
24
|
+
actions={[
|
|
25
|
+
<Button key="confirm" variant="primary" onClick={() => onClose(true)}>
|
|
26
|
+
{t('Resume all devices')}
|
|
27
|
+
</Button>,
|
|
28
|
+
<Button key="cancel" variant="link" onClick={() => onClose(false)}>
|
|
29
|
+
{t('Cancel')}
|
|
30
|
+
</Button>,
|
|
31
|
+
]}
|
|
32
|
+
>
|
|
33
|
+
<Stack hasGutter>
|
|
34
|
+
<StackItem>
|
|
35
|
+
<TextContent>
|
|
36
|
+
<Text>
|
|
37
|
+
<Trans t={t} count={deviceCountNum}>
|
|
38
|
+
You are about to resume all <strong>{deviceCount}</strong> suspended devices.
|
|
39
|
+
</Trans>
|
|
40
|
+
</Text>
|
|
41
|
+
</TextContent>
|
|
42
|
+
<TextContent>
|
|
43
|
+
<Text>
|
|
44
|
+
{t(
|
|
45
|
+
'This action is irreversible and will allow all affected devices to receive new configuration updates from the server.',
|
|
46
|
+
)}
|
|
47
|
+
</Text>
|
|
48
|
+
</TextContent>
|
|
49
|
+
</StackItem>
|
|
50
|
+
</Stack>
|
|
51
|
+
</Modal>
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export default ResumeAllDevicesConfirmationModal;
|
|
@@ -14,19 +14,35 @@ export const useAccessReview = (kind: RESOURCE, verb: VERB): AccessReviewResult
|
|
|
14
14
|
fetch: { checkPermissions },
|
|
15
15
|
} = useAppContext();
|
|
16
16
|
React.useEffect(() => {
|
|
17
|
+
let isMounted = true;
|
|
18
|
+
|
|
17
19
|
const doItAsync = async () => {
|
|
20
|
+
if (!isMounted) return;
|
|
21
|
+
|
|
18
22
|
setIsLoading(true);
|
|
19
23
|
try {
|
|
20
24
|
const allowed = await checkPermissions(kind, verb);
|
|
21
|
-
|
|
25
|
+
if (isMounted) {
|
|
26
|
+
setIsAllowed(allowed);
|
|
27
|
+
}
|
|
22
28
|
} catch (err) {
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
if (isMounted) {
|
|
30
|
+
setError(getErrorMessage(err));
|
|
31
|
+
setIsAllowed(false);
|
|
32
|
+
}
|
|
25
33
|
} finally {
|
|
26
|
-
|
|
34
|
+
if (isMounted) {
|
|
35
|
+
setIsLoading(false);
|
|
36
|
+
}
|
|
27
37
|
}
|
|
28
38
|
};
|
|
39
|
+
|
|
29
40
|
doItAsync();
|
|
41
|
+
|
|
42
|
+
// Cleanup function to prevent state updates after unmount
|
|
43
|
+
return () => {
|
|
44
|
+
isMounted = false;
|
|
45
|
+
};
|
|
30
46
|
}, [kind, verb, checkPermissions]);
|
|
31
47
|
|
|
32
48
|
return [isAllowed, isLoading, error];
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { RESOURCE, VERB } from '../types/rbac';
|
|
3
|
+
import { useAccessReview } from './useAccessReview';
|
|
4
|
+
import { useFetch } from './useFetch';
|
|
5
|
+
|
|
6
|
+
// Alerts are considered disabled if the service returns either 501 (Not Implemented) or 500
|
|
7
|
+
const isDisabledAlertManagerService = (error: Error): boolean =>
|
|
8
|
+
Number(error.message) === 501 || Number(error.message) === 500;
|
|
9
|
+
|
|
10
|
+
export const useAlertsEnabled = (): boolean => {
|
|
11
|
+
const { get } = useFetch();
|
|
12
|
+
const [alertsEnabled, setAlertsEnabled] = React.useState(false);
|
|
13
|
+
|
|
14
|
+
const [canListAlerts, alertsLoading] = useAccessReview(RESOURCE.ALERTS, VERB.LIST);
|
|
15
|
+
|
|
16
|
+
React.useEffect(() => {
|
|
17
|
+
let abortController: AbortController;
|
|
18
|
+
|
|
19
|
+
const checkAlertServiceEnabled = async () => {
|
|
20
|
+
try {
|
|
21
|
+
abortController = new AbortController();
|
|
22
|
+
await get('alerts', abortController.signal);
|
|
23
|
+
setAlertsEnabled(true);
|
|
24
|
+
} catch (err) {
|
|
25
|
+
if (!abortController.signal.aborted) {
|
|
26
|
+
if (isDisabledAlertManagerService(err as Error)) {
|
|
27
|
+
setAlertsEnabled(false);
|
|
28
|
+
} else {
|
|
29
|
+
// For other errors, assume alerts are enabled but there's a temporary issue
|
|
30
|
+
setAlertsEnabled(true);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
if (!alertsLoading && canListAlerts) {
|
|
37
|
+
// Check only if we know that the user has permissions to read the alerts
|
|
38
|
+
checkAlertServiceEnabled();
|
|
39
|
+
} else {
|
|
40
|
+
// If user doesn't have permissions, set to disabled
|
|
41
|
+
setAlertsEnabled(false);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return () => {
|
|
45
|
+
abortController?.abort();
|
|
46
|
+
};
|
|
47
|
+
}, [get, alertsLoading, canListAlerts]);
|
|
48
|
+
|
|
49
|
+
return alertsEnabled;
|
|
50
|
+
};
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
useSearchParams,
|
|
14
14
|
} from 'react-router-dom';
|
|
15
15
|
import { PatchRequest } from '@flightctl/types';
|
|
16
|
-
import { CliArtifactsResponse } from '@flightctl/ui-components/src/types/extraTypes';
|
|
17
16
|
import { ROUTE } from './useNavigate';
|
|
18
17
|
import { RESOURCE, VERB } from '../types/rbac';
|
|
19
18
|
|
|
@@ -71,16 +70,18 @@ export type AppContextProps = {
|
|
|
71
70
|
fetch: {
|
|
72
71
|
getWsEndpoint: (deviceId: string) => string;
|
|
73
72
|
get: <R>(kind: string, abortSignal?: AbortSignal) => Promise<R>;
|
|
74
|
-
post: <
|
|
75
|
-
|
|
73
|
+
post: <TRequest, TResponse = TRequest>(
|
|
74
|
+
kind: string,
|
|
75
|
+
data: TRequest,
|
|
76
|
+
abortSignal?: AbortSignal,
|
|
77
|
+
) => Promise<TResponse>;
|
|
78
|
+
put: <TRequest>(kind: string, data: TRequest, abortSignal?: AbortSignal) => Promise<TRequest>;
|
|
76
79
|
remove: <R>(kind: string, abortSignal?: AbortSignal) => Promise<R>;
|
|
77
80
|
patch: <R>(kind: string, patches: PatchRequest, abortSignal?: AbortSignal) => Promise<R>;
|
|
78
81
|
checkPermissions: (resource: RESOURCE, verb: VERB) => Promise<boolean>;
|
|
82
|
+
// All methods to the UI proxy are handled in the same method - returns raw Response
|
|
83
|
+
proxyFetch: (endpoint: string, requestInit: RequestInit) => Promise<Response>;
|
|
79
84
|
};
|
|
80
|
-
// Extra fetch functions
|
|
81
|
-
getAlerts?: <R>(abortSignal?: AbortSignal) => Promise<R>;
|
|
82
|
-
getMetrics?: <R>(query: string, abortSignal?: AbortSignal) => Promise<R>;
|
|
83
|
-
getCliArtifacts?: (abortSignal?: AbortSignal) => Promise<CliArtifactsResponse>;
|
|
84
85
|
};
|
|
85
86
|
|
|
86
87
|
export const AppContext = React.createContext<AppContextProps>({
|
|
@@ -113,6 +114,7 @@ export const AppContext = React.createContext<AppContextProps>({
|
|
|
113
114
|
remove: async () => ({}) as any,
|
|
114
115
|
patch: async () => ({}) as any,
|
|
115
116
|
checkPermissions: async () => true,
|
|
117
|
+
proxyFetch: async () => ({}) as any,
|
|
116
118
|
},
|
|
117
119
|
/* eslint-enable */
|
|
118
120
|
});
|
package/src/hooks/useFetch.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { useAppContext } from './useAppContext';
|
|
2
2
|
|
|
3
3
|
export const useFetch = () => {
|
|
4
|
-
const { fetch
|
|
4
|
+
const { fetch } = useAppContext();
|
|
5
5
|
|
|
6
6
|
return {
|
|
7
7
|
...fetch,
|
|
8
|
-
getCliArtifacts,
|
|
9
|
-
getMetrics,
|
|
10
8
|
};
|
|
11
9
|
};
|