Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion web/messages/en/modal.json
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,15 @@
"modal_assign_posture_check_locations_error": "Failed to assign locations to the posture check",
"modal_delete_posture_check_title": "Delete posture check",
"modal_delete_posture_check_content_empty": "Are you sure you want to delete this check?",
"modal_delete_posture_check_content": "Are you sure you want to delete this check? It’s currently used in {locations}. Removing it may change access criteria for users in these locations.",
"modal_delete_posture_check_content": "Are you sure you want to delete this check? It’s currently used in {locations}. Removing it may change access criteria for users in these locations and disconnect active VPN client sessions there.",
"modal_delete_posture_check_success": "Posture check deleted",
"modal_delete_posture_check_error": "Failed to delete posture check",
"modal_posture_assignment_warning_title": "Confirm posture check changes",
"modal_posture_assignment_warning_body_location": "These changes may disconnect active VPN client sessions for this location.\n\n{changes}",
"modal_posture_assignment_warning_body_postures": "These changes may disconnect active VPN client sessions for the affected locations.\n\n{changes}",
"modal_posture_assignment_warning_added": "Added:",
"modal_posture_assignment_warning_removed": "Removed:",
"modal_posture_rules_warning_body": "These rules are enforced the next time each device connects. Devices connected now are unaffected until they reconnect.",
"modal_assign_user_device_ip_title": "Device IP settings",
"modal_assign_user_device_ip_card_title": "{deviceName} IP settings",
"modal_assign_user_device_ip_assignment_description": "You can change the IP address for this device separately in each location/network one-by-one.",
Expand Down
2 changes: 0 additions & 2 deletions web/messages/en/postures.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,6 @@
"posture_checks_edit_defguard": "Defguard",
"posture_checks_edit_locations": "Assigned locations",
"posture_checks_edit_defguard_note": "\u201cDefguard versions\u201d includes major releases as well as all subsequent patch updates with fixes and improvements.",
"posture_checks_edit_delete_title": "Delete posture check",
"posture_checks_edit_delete_body": "Are you sure you want to delete posture check **{name}**? This action cannot be undone.",
"posture_checks_edit_save_success": "Posture check saved",
"posture_checks_edit_save_failed": "Failed to save posture check",
"posture_checks_edit_delete_success": "Posture check deleted",
Expand Down
42 changes: 15 additions & 27 deletions web/src/pages/EditLocationPage/EditLocationPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
canUseEnterpriseFeature,
} from '../../shared/utils/license';
import { smallestNetworkCapacity } from '../../shared/utils/network';
import { confirmLocationPostureChange } from '../../shared/utils/postureWarning';
import { Validate } from '../../shared/validate';
import postureCheckShield from './assets/posture_check_shield.png';
import { getPostureChecksSectionState } from './postureChecksSection';
Expand Down Expand Up @@ -393,17 +394,6 @@ const EditLocationForm = ({ location }: { location: NetworkLocation }) => {
[postureChecks],
);

const assignedPostureChecks = useMemo(() => {
const labelsById = new Map(
postureChecks.map((postureCheck) => [postureCheck.id, postureCheck.name]),
);

return location.posture_checks?.map((id) => ({
id,
label: labelsById.get(id) ?? String(id),
}));
}, [location.posture_checks, postureChecks]);

const serviceLocationLabelContent = useMemo(() => {
if (!serviceLocationLocked) return undefined;
return (
Expand Down Expand Up @@ -474,7 +464,7 @@ const EditLocationForm = ({ location }: { location: NetworkLocation }) => {
},
});

const { mutate: setLocationPostures, isPending: isUpdatingLocationPostures } =
const { mutateAsync: setLocationPosturesAsync, isPending: isUpdatingLocationPostures } =
useMutation({
mutationFn: (data: { postures: number[] }) =>
api.devicePosture.setLocationPostures(location.id, data),
Expand All @@ -486,6 +476,16 @@ const EditLocationForm = ({ location }: { location: NetworkLocation }) => {
},
});

const handlePostureSelection = (values: (string | number)[]) => {
const next = values.filter((value): value is number => typeof value === 'number');
confirmLocationPostureChange({
current: location.posture_checks ?? [],
next,
options: postureCheckOptions,
actionPromise: () => setLocationPosturesAsync({ postures: next }),
});
};

const openPostureChecksSelection = () => {
useSelectionModal.setState({
isOpen: true,
Expand All @@ -501,11 +501,7 @@ const EditLocationForm = ({ location }: { location: NetworkLocation }) => {
searchPlaceholder: m.controls_search(),
selected: new Set(location.posture_checks),
visibleItemsLimit: 4,
onSubmit: (values) => {
setLocationPostures({
postures: values.filter((value): value is number => typeof value === 'number'),
});
},
onSubmit: handlePostureSelection,
});
};

Expand Down Expand Up @@ -946,21 +942,13 @@ const EditLocationForm = ({ location }: { location: NetworkLocation }) => {
<div className="posture-checks-assigned-state">
<SelectMultiple
options={postureCheckOptions}
selected={
new Set(assignedPostureChecks?.map((postureCheck) => postureCheck.id))
}
selected={new Set(location.posture_checks)}
modalTitle={m.location_posture_checks_select()}
editText={m.location_posture_checks_edit()}
editIcon={IconKind.Edit}
toggleValue={false}
counterText={() => ''}
onSelectionChange={(values) => {
setLocationPostures({
postures: values.filter(
(value): value is number => typeof value === 'number',
),
});
}}
onSelectionChange={handlePostureSelection}
onToggleChange={() => {}}
selectionCustomItemRender={renderPostureCheckSelectionItem}
selectionModalProps={{
Expand Down
46 changes: 39 additions & 7 deletions web/src/pages/EditPostureCheckPage/EditPostureCheckPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,17 @@ import {
getDevicePostureVersionMetadataQueryOptions,
getLocationsQueryOptions,
} from '../../shared/query';
import { confirmPostureLocationChange } from '../../shared/utils/postureWarning';
import { buildAddPostureCheckRequest } from '../AddPostureCheckWizardPage/payload';
import { getDeletePostureCheckModalData } from '../PostureChecksPage/postureChecks';
import {
getPostureCheckVersionValues,
type PostureCheckVersionValues,
} from '../PostureChecksPage/types';
import {
type EditPostureCheckFormValues,
getInitialEditPostureCheckFormValues,
normalizeEditPostureCheckEnforcementFields,
normalizeEditPostureCheckFormValues,
} from './form';

Expand Down Expand Up @@ -115,6 +118,32 @@ const EditPostureCheckForm = ({
[defaults, values],
);

const rulesChanged = useMemo(
() =>
JSON.stringify(normalizeEditPostureCheckEnforcementFields(values)) !==
JSON.stringify(normalizeEditPostureCheckEnforcementFields(defaults)),
[defaults, values],
);

const handleSubmit = () => {
// The helper diffs the location sets itself and returns false when there is
// nothing to confirm, which covers the submit that changed only the name or
// description.
const modalOpened = confirmPostureLocationChange({
current: defaults.locations,
next: values.locations,
options: locationOptions,
actionPromise: () => saveMutation.mutateAsync(values),
// A posture assigned to no locations after this save enforces nothing
// anywhere, so the deferred-enforcement claim would be misleading.
deferredEnforcement: rulesChanged && values.locations.size > 0,
});

if (modalOpened) return;

saveMutation.mutate(values);
};

const updateValues = (
updater: (current: EditPostureCheckFormValues) => EditPostureCheckFormValues,
) => {
Expand All @@ -128,7 +157,7 @@ const EditPostureCheckForm = ({
<form
onSubmit={(event) => {
event.preventDefault();
void saveMutation.mutateAsync(values);
handleSubmit();
}}
>
<EditPageFormSection label={m.posture_checks_edit_general()}>
Expand Down Expand Up @@ -161,12 +190,15 @@ const EditPostureCheckForm = ({
text: m.controls_delete(),
disabled: saveMutation.isPending,
onClick: () => {
const assignedLocationNames = locationOptions
.filter((loc) => postureCheck.locations.includes(loc.id))
.map((loc) => loc.label);

openModal(ModalName.ConfirmAction, {
title: m.posture_checks_edit_delete_title(),
contentMd: m.posture_checks_edit_delete_body({ name: postureCheck.name }),
actionPromise: () => api.devicePosture.deleteDevicePosture(postureCheck.id),
invalidateKeys: [['device-posture'], ['network'], ['activity-log']],
submitProps: { text: m.controls_delete(), variant: 'critical' },
...getDeletePostureCheckModalData(
{ id: postureCheck.id, name: postureCheck.name },
assignedLocationNames,
),
onSuccess: () => {
Snackbar.default(m.posture_checks_edit_delete_success());
navigate({ to: '/acl/posture-checks', replace: true });
Expand All @@ -187,7 +219,7 @@ const EditPostureCheckForm = ({
disabled: saveDisabled,
loading: saveMutation.isPending,
onClick: () => {
void saveMutation.mutateAsync(values);
handleSubmit();
},
}}
/>
Expand Down
38 changes: 38 additions & 0 deletions web/src/pages/EditPostureCheckPage/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,47 @@ export const getInitialEditPostureCheckFormValues = (
};
};

const sortOperatingSystemState = (
operatingSystemState: EditPostureCheckFormValues['operatingSystemState'],
) =>
Object.fromEntries(
Object.entries(operatingSystemState).map(([os, state]) => [
os,
{ ...state, conditions: [...state.conditions].sort() },
]),
);

export const normalizeEditPostureCheckFormValues = (
values: EditPostureCheckFormValues,
) => ({
...values,
configuredOperatingSystems: [...values.configuredOperatingSystems].sort(),
locations: Array.from(values.locations).sort((left, right) => left - right),
operatingSystemState: sortOperatingSystemState(values.operatingSystemState),
});

/**
* Projection of enforcement-related fields for `rulesChanged` comparison:
* everything except `name`, `description` and `locations`. Derived from
* `normalizeEditPostureCheckFormValues` so both comparisons share one sort
* policy.
*/
export const normalizeEditPostureCheckEnforcementFields = (
values: EditPostureCheckFormValues,
) => {
const {
allowPrereleaseClient,
configuredOperatingSystems,
minimumDesktopClientVersion,
minimumMobileClientVersion,
operatingSystemState,
} = normalizeEditPostureCheckFormValues(values);

return {
allowPrereleaseClient,
configuredOperatingSystems,
minimumDesktopClientVersion,
minimumMobileClientVersion,
operatingSystemState,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const PostureCheckDrawerContent = ({ row, onClose }: ContentProps) => {
const { data: postureCheck } = useSuspenseQuery(getDevicePostureQueryOptions(row.id));
const { data: locations } = useSuspenseQuery(getLocationsQueryOptions);

const { mutate: assignLocations } = useMutation({
const { mutateAsync: assignLocationsAsync } = useMutation({
mutationFn: (locationIds: number[]) =>
api.devicePosture.setLocationsForDevicePosture(row.id, locationIds),
meta: {
Expand Down Expand Up @@ -74,7 +74,7 @@ const PostureCheckDrawerContent = ({ row, onClose }: ContentProps) => {
row,
locationOptions,
navigate,
assignLocations,
assignLocations: assignLocationsAsync,
onAfterEdit: onClose,
onAfterDelete: onClose,
duplicatePosture: () => duplicatePosture(row.id),
Expand Down
6 changes: 3 additions & 3 deletions web/src/pages/PostureChecksPage/PostureChecksTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const PostureChecksTable = ({
() => buildFilteredLocationOptions(locations),
[locations],
);
const { mutate: assignLocations } = useMutation({
const { mutateAsync: assignLocationsAsync } = useMutation({
mutationFn: ({
postureCheckId,
locations,
Expand Down Expand Up @@ -234,7 +234,7 @@ export const PostureChecksTable = ({
locationOptions,
navigate,
assignLocations: (locations) =>
assignLocations({ postureCheckId: row.id, locations }),
assignLocationsAsync({ postureCheckId: row.id, locations }),
duplicatePosture: () => duplicatePosture(row.id),
});

Expand All @@ -243,12 +243,12 @@ export const PostureChecksTable = ({
}),
],
[
assignLocations,
columnFilterOptions,
locationOptions,
navigate,
onRowClick,
duplicatePosture,
assignLocationsAsync,
],
);

Expand Down
11 changes: 9 additions & 2 deletions web/src/pages/PostureChecksPage/postureCheckMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { MenuItemsGroup } from '../../shared/defguard-ui/components/Menu/ty
import { Snackbar } from '../../shared/defguard-ui/providers/snackbar/snackbar';
import { openModal } from '../../shared/hooks/modalControls/modalsSubjects';
import { ModalName } from '../../shared/hooks/modalControls/modalTypes';
import { confirmPostureLocationChange } from '../../shared/utils/postureWarning';
import { getDeletePostureCheckModalData, type PostureCheckRow } from './postureChecks';

type LocationOption = SelectionOption<number>;
Expand All @@ -15,7 +16,7 @@ type BuildPostureCheckMenuArgs = {
row: PostureCheckRow;
locationOptions: LocationOption[];
navigate: ReturnType<typeof useNavigate>;
assignLocations: (locationIds: number[]) => void;
assignLocations: (locationIds: number[]) => Promise<unknown>;
duplicatePosture: () => void;
onAfterEdit?: () => void;
onAfterDelete?: () => void;
Expand Down Expand Up @@ -58,7 +59,13 @@ export const buildPostureCheckMenuItems = ({
options: locationOptions,
selected: new Set(row.locations),
onSubmit: (selected) => {
assignLocations(selected as number[]);
const next = selected as number[];
confirmPostureLocationChange({
current: row.locations,
next,
options: locationOptions,
actionPromise: () => assignLocations(next),
});
},
});
},
Expand Down
Loading
Loading