Skip to content

Commit dad942c

Browse files
authored
Merge pull request KelvinTegelaar#4552 from KelvinTegelaar/dev
Dev to hotfix
2 parents 2e0baac + 956786f commit dad942c

File tree

20 files changed

+1194
-216
lines changed

20 files changed

+1194
-216
lines changed

generate-placeholders.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ const pages = [
8383
{ title: "Profiles", path: "/endpoint/autopilot/list-profiles" },
8484
{ title: "Add Profile", path: "/endpoint/autopilot/add-profile" },
8585
{ title: "Status Pages", path: "/endpoint/autopilot/list-status-pages" },
86-
{ title: "Add Status Page", path: "/endpoint/autopilot/add-status-page" },
8786
{ title: "Devices", path: "/endpoint/MEM/devices" },
8887
{ title: "Configuration Policies", path: "/endpoint/MEM/list-policies" },
8988
{ title: "Compliance Policies", path: "/endpoint/MEM/list-compliance-policies" },

public/version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"version": "8.3.1"
2+
"version": "8.3.2"
33
}

src/components/CippCards/CippExchangeInfoCard.jsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,14 @@ export const CippExchangeInfoCard = (props) => {
101101
{getCippFormatting(exchangeData?.BlockedForSpam, "BlockedForSpam")}
102102
</Typography>
103103
</Grid>
104+
<Grid size={{ xs: 12, md: 12 }}>
105+
<Typography variant="inherit" color="text.primary" gutterBottom>
106+
Retention Policy:
107+
</Typography>
108+
<Typography variant="inherit">
109+
{getCippFormatting(exchangeData?.RetentionPolicy, "RetentionPolicy")}
110+
</Typography>
111+
</Grid>
104112
</Grid>
105113
)
106114
}

src/components/CippCards/CippStandardsDialog.jsx

Lines changed: 341 additions & 123 deletions
Large diffs are not rendered by default.

src/components/CippComponents/CippAutocomplete.jsx

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
TextField,
77
IconButton,
88
} from "@mui/material";
9-
import { useEffect, useState, useMemo, useCallback } from "react";
9+
import { useEffect, useState, useMemo, useCallback, useRef } from "react";
1010
import { useSettings } from "../../hooks/use-settings";
1111
import { getCippError } from "../../utils/get-cipp-error";
1212
import { ApiGetCallWithPagination } from "../../api/ApiCall";
@@ -78,6 +78,7 @@ export const CippAutoComplete = (props) => {
7878

7979
const [usedOptions, setUsedOptions] = useState(options);
8080
const [getRequestInfo, setGetRequestInfo] = useState({ url: "", waiting: false, queryKey: "" });
81+
const hasPreselectedRef = useRef(false);
8182
const filter = createFilterOptions({
8283
stringify: (option) => JSON.stringify(option),
8384
});
@@ -207,15 +208,32 @@ export const CippAutoComplete = (props) => {
207208
return finalOptions;
208209
}, [api, usedOptions, options, removeOptions, sortOptions]);
209210

210-
// Dedicated effect for handling preselected value
211+
// Dedicated effect for handling preselected value - only runs once
211212
useEffect(() => {
212-
if (preselectedValue && !defaultValue && !value && memoizedOptions.length > 0) {
213-
const preselectedOption = memoizedOptions.find((option) => option.value === preselectedValue);
213+
if (preselectedValue && memoizedOptions.length > 0 && !hasPreselectedRef.current) {
214+
// Check if we should skip preselection due to existing defaultValue
215+
const hasDefaultValue =
216+
defaultValue && (Array.isArray(defaultValue) ? defaultValue.length > 0 : true);
214217

215-
if (preselectedOption) {
216-
const newValue = multiple ? [preselectedOption] : preselectedOption;
217-
if (onChange) {
218-
onChange(newValue, newValue?.addedFields);
218+
if (!hasDefaultValue) {
219+
// For multiple mode, check if value is empty array or null/undefined
220+
// For single mode, check if value is null/undefined
221+
const shouldPreselect = multiple
222+
? !value || (Array.isArray(value) && value.length === 0)
223+
: !value;
224+
225+
if (shouldPreselect) {
226+
const preselectedOption = memoizedOptions.find(
227+
(option) => option.value === preselectedValue
228+
);
229+
230+
if (preselectedOption) {
231+
const newValue = multiple ? [preselectedOption] : preselectedOption;
232+
hasPreselectedRef.current = true; // Mark that we've preselected
233+
if (onChange) {
234+
onChange(newValue, newValue?.addedFields);
235+
}
236+
}
219237
}
220238
}
221239
}

src/components/CippComponents/CippCalendarPermissionsDialog.jsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ const CippCalendarPermissionsDialog = ({ formHook, combinedOptions, isUserGroupL
1717
}
1818
}, [isEditor, formHook]);
1919

20+
// default SendNotificationToUser to false on mount
21+
useEffect(() => {
22+
formHook.setValue("SendNotificationToUser", false);
23+
}, [formHook]);
24+
25+
// Only certain permission levels support sending a notification when calendar permissions are added
26+
const notifyAllowed = ["AvailabilityOnly", "LimitedDetails", "Reviewer", "Editor"];
27+
const isNotifyAllowed = notifyAllowed.includes(permissionLevel?.value ?? permissionLevel);
28+
2029
return (
2130
<Stack spacing={3} sx={{ mt: 1 }}>
2231
<Box>
@@ -80,6 +89,29 @@ const CippCalendarPermissionsDialog = ({ formHook, combinedOptions, isUserGroupL
8089
</span>
8190
</Tooltip>
8291
</Box>
92+
93+
<Box>
94+
<Tooltip
95+
title={
96+
!isNotifyAllowed
97+
? `Send notification is only supported for: ${notifyAllowed.join(", ")}`
98+
: ""
99+
}
100+
followCursor
101+
placement="right"
102+
>
103+
<span>
104+
<CippFormComponent
105+
type="switch"
106+
label="Send notification"
107+
name="SendNotificationToUser"
108+
formControl={formHook}
109+
disabled={!isNotifyAllowed}
110+
sx={{ ml: 1.5, mt: 0, mb: 0 }}
111+
/>
112+
</span>
113+
</Tooltip>
114+
</Box>
83115
</Stack>
84116
);
85117
};

src/components/CippComponents/CippExchangeActions.jsx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,51 @@ export const CippExchangeActions = () => {
198198
multiPost: false,
199199
condition: (row) => row.ArchiveGuid === "00000000-0000-0000-0000-000000000000",
200200
},
201+
{
202+
label: "Set Retention Policy",
203+
type: "POST",
204+
url: "/api/ExecSetMailboxRetentionPolicies",
205+
icon: <MailLock />,
206+
confirmText: "Set the specified retention policy for selected mailboxes?",
207+
multiPost: false,
208+
fields: [
209+
{
210+
type: "autoComplete",
211+
name: "policyName",
212+
label: "Retention Policy",
213+
multiple: false,
214+
creatable: false,
215+
validators: { required: "Please select a retention policy" },
216+
api: {
217+
url: "/api/ExecManageRetentionPolicies",
218+
labelField: "Name",
219+
valueField: "Name",
220+
queryKey: `RetentionPolicies-${tenant}`,
221+
data: {
222+
tenantFilter: tenant,
223+
},
224+
},
225+
},
226+
],
227+
customDataformatter: (rows, action, formData) => {
228+
const mailboxArray = Array.isArray(rows) ? rows : [rows];
229+
230+
// Extract mailbox identities - using UPN as the identifier
231+
const mailboxes = mailboxArray.map(mailbox => mailbox.UPN);
232+
233+
// Handle autocomplete selection - could be string or object
234+
const policyName = typeof formData.policyName === 'object'
235+
? formData.policyName.value
236+
: formData.policyName;
237+
238+
return {
239+
PolicyName: policyName,
240+
Mailboxes: mailboxes,
241+
tenantFilter: tenant
242+
};
243+
},
244+
color: "primary",
245+
},
201246
{
202247
label: "Enable Auto-Expanding Archive",
203248
type: "POST",

src/components/CippComponents/CippPolicyDeployDrawer.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ export const CippPolicyDeployDrawer = ({
110110
required={true}
111111
disableClearable={false}
112112
allTenants={true}
113+
preselectedEnabled={true}
113114
type="multiple"
114115
/>
115116
<CippFormComponent

src/components/CippComponents/CippUserActions.jsx

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,7 @@ export const CippUserActions = () => {
435435
},
436436
confirmText: "Are you sure you want to clear the Immutable ID for [userPrincipalName]?",
437437
multiPost: false,
438-
condition: (row) => !row.onPremisesSyncEnabled && row?.onPremisesImmutableId && canWriteUser,
438+
condition: (row) => !row?.onPremisesSyncEnabled && row?.onPremisesImmutableId && canWriteUser,
439439
},
440440
{
441441
label: "Revoke all user sessions",
@@ -465,17 +465,19 @@ export const CippUserActions = () => {
465465
customFunction: (users, action, formData) => {
466466
// Handle both single user and multiple users
467467
const userData = Array.isArray(users) ? users : [users];
468-
468+
469469
// Store users in session storage to avoid URL length limits
470-
sessionStorage.setItem('patchWizardUsers', JSON.stringify(userData));
471-
470+
sessionStorage.setItem("patchWizardUsers", JSON.stringify(userData));
471+
472472
// Use Next.js router for internal navigation
473-
import('next/router').then(({ default: router }) => {
474-
router.push('/identity/administration/users/patch-wizard');
475-
}).catch(() => {
476-
// Fallback to window.location if router is not available
477-
window.location.href = '/identity/administration/users/patch-wizard';
478-
});
473+
import("next/router")
474+
.then(({ default: router }) => {
475+
router.push("/identity/administration/users/patch-wizard");
476+
})
477+
.catch(() => {
478+
// Fallback to window.location if router is not available
479+
window.location.href = "/identity/administration/users/patch-wizard";
480+
});
479481
},
480482
condition: () => canWriteUser,
481483
},

src/components/CippTable/CippDataTable.js

Lines changed: 60 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,20 @@ export const CippDataTable = (props) => {
200200
};
201201

202202
const table = useMaterialReactTable({
203+
muiTableBodyCellProps: {
204+
onCopy: (e) => {
205+
const sel = window.getSelection()?.toString() ?? "";
206+
if (sel) {
207+
e.preventDefault();
208+
e.stopPropagation();
209+
e.nativeEvent?.stopImmediatePropagation?.();
210+
e.clipboardData.setData("text/plain", sel);
211+
if (navigator.clipboard?.writeText) {
212+
navigator.clipboard.writeText(sel).catch(() => {});
213+
}
214+
}
215+
},
216+
},
203217
mrtTheme: (theme) => ({
204218
baseBackgroundColor: theme.palette.background.paper,
205219
}),
@@ -215,66 +229,66 @@ export const CippDataTable = (props) => {
215229
muiTableHeadCellProps: {
216230
sx: {
217231
// Target the filter row cells
218-
'& .MuiTableCell-root': {
219-
padding: '8px 16px',
232+
"& .MuiTableCell-root": {
233+
padding: "8px 16px",
220234
},
221235
// Target the Autocomplete component in filter cells
222-
'& .MuiAutocomplete-root': {
223-
width: '100%',
236+
"& .MuiAutocomplete-root": {
237+
width: "100%",
224238
},
225239
// Force the tags container to be single line with ellipsis
226-
'& .MuiAutocomplete-root .MuiInputBase-root': {
227-
height: '40px !important',
228-
overflow: 'hidden',
229-
textOverflow: 'ellipsis',
230-
whiteSpace: 'nowrap',
231-
display: 'flex',
232-
flexWrap: 'nowrap',
240+
"& .MuiAutocomplete-root .MuiInputBase-root": {
241+
height: "40px !important",
242+
overflow: "hidden",
243+
textOverflow: "ellipsis",
244+
whiteSpace: "nowrap",
245+
display: "flex",
246+
flexWrap: "nowrap",
233247
},
234248
// Target the tags container specifically
235-
'& .MuiAutocomplete-root .MuiInputBase-root .MuiInputBase-input': {
236-
height: '24px',
237-
minHeight: '24px',
238-
maxHeight: '24px',
249+
"& .MuiAutocomplete-root .MuiInputBase-root .MuiInputBase-input": {
250+
height: "24px",
251+
minHeight: "24px",
252+
maxHeight: "24px",
239253
},
240254
// Target regular input fields (not in Autocomplete)
241-
'& .MuiInputBase-root': {
242-
height: '40px !important',
255+
"& .MuiInputBase-root": {
256+
height: "40px !important",
243257
},
244258
// Ensure all input fields have consistent styling
245-
'& .MuiInputBase-input': {
246-
height: '24px',
247-
minHeight: '24px',
248-
maxHeight: '24px',
259+
"& .MuiInputBase-input": {
260+
height: "24px",
261+
minHeight: "24px",
262+
maxHeight: "24px",
249263
},
250264
// Target the specific chip class mentioned
251-
'& .MuiChip-label.MuiChip-labelMedium': {
252-
maxWidth: '80px',
253-
overflow: 'hidden',
254-
textOverflow: 'ellipsis',
255-
whiteSpace: 'nowrap',
256-
padding: '0 4px',
265+
"& .MuiChip-label.MuiChip-labelMedium": {
266+
maxWidth: "80px",
267+
overflow: "hidden",
268+
textOverflow: "ellipsis",
269+
whiteSpace: "nowrap",
270+
padding: "0 4px",
257271
},
258272
// Make chips smaller overall and add title attribute for tooltip
259-
'& .MuiChip-root': {
260-
height: '24px',
261-
maxHeight: '24px',
273+
"& .MuiChip-root": {
274+
height: "24px",
275+
maxHeight: "24px",
262276
// This adds a tooltip effect using the browser's native tooltip
263-
'&::before': {
264-
content: 'attr(data-label)',
265-
display: 'none',
277+
"&::before": {
278+
content: "attr(data-label)",
279+
display: "none",
266280
},
267-
'&:hover::before': {
268-
display: 'block',
269-
position: 'absolute',
270-
top: '-25px',
271-
left: '0',
272-
backgroundColor: 'rgba(0, 0, 0, 0.8)',
273-
color: 'white',
274-
padding: '4px 8px',
275-
borderRadius: '4px',
276-
fontSize: '12px',
277-
whiteSpace: 'nowrap',
281+
"&:hover::before": {
282+
display: "block",
283+
position: "absolute",
284+
top: "-25px",
285+
left: "0",
286+
backgroundColor: "rgba(0, 0, 0, 0.8)",
287+
color: "white",
288+
padding: "4px 8px",
289+
borderRadius: "4px",
290+
fontSize: "12px",
291+
whiteSpace: "nowrap",
278292
zIndex: 9999,
279293
},
280294
},
@@ -570,7 +584,7 @@ export const CippDataTable = (props) => {
570584
</Scrollbar>
571585
) : (
572586
// Render the table inside a Card
573-
(<Card style={{ width: "100%" }} {...props.cardProps}>
587+
<Card style={{ width: "100%" }} {...props.cardProps}>
574588
{cardButton || !hideTitle ? (
575589
<>
576590
<CardHeader action={cardButton} title={hideTitle ? "" : title} />
@@ -602,7 +616,7 @@ export const CippDataTable = (props) => {
602616
)}
603617
</Scrollbar>
604618
</CardContent>
605-
</Card>)
619+
</Card>
606620
)}
607621
<CippOffCanvas
608622
isFetching={getRequestData.isFetching}

0 commit comments

Comments
 (0)