diff --git a/.github/workflows/Close_Stale_Issues_and_PRs.yml b/.github/workflows/Close_Stale_Issues_and_PRs.yml index 96b5dc6d5e14..88607a42675d 100644 --- a/.github/workflows/Close_Stale_Issues_and_PRs.yml +++ b/.github/workflows/Close_Stale_Issues_and_PRs.yml @@ -13,6 +13,6 @@ jobs: stale-issue-message: 'This issue is stale because it has been open 10 days with no activity. We will close this issue soon. If you want this feature implemented you can contribute it. See: https://docs.cipp.app/dev-documentation/contributing-to-the-code . Please notify the team if you are working on this yourself.' close-issue-message: 'This issue was closed because it has been stalled for 14 days with no activity.' stale-issue-label: 'no-activity' - exempt-issue-labels: 'planned,bug' + exempt-issue-labels: 'planned,bug,roadmap' days-before-stale: 9 days-before-close: 5 diff --git a/public/version.json b/public/version.json index a85ab8e9a599..64ee67a6cf4b 100644 --- a/public/version.json +++ b/public/version.json @@ -1,3 +1,3 @@ { - "version": "7.5.1" + "version": "7.5.2" } \ No newline at end of file diff --git a/src/components/CippCards/CippExchangeInfoCard.jsx b/src/components/CippCards/CippExchangeInfoCard.jsx index 524ec7acce4d..03f4473c745a 100644 --- a/src/components/CippCards/CippExchangeInfoCard.jsx +++ b/src/components/CippCards/CippExchangeInfoCard.jsx @@ -29,6 +29,17 @@ export const CippExchangeInfoCard = (props) => { { name: "ActiveSync", enabled: exchangeData?.MailboxActiveSyncEnabled }, ]; + // Define mailbox hold types array + const holds = [ + { name: "Compliance Tag Hold", enabled: exchangeData?.ComplianceTagHold }, + { name: "Retention Hold", enabled: exchangeData?.RetentionHold }, + { name: "Litigation Hold", enabled: exchangeData?.LitigationHold }, + { name: "In-Place Hold", enabled: exchangeData?.InPlaceHold }, + { name: "eDiscovery Hold", enabled: exchangeData?.EDiscoveryHold }, + { name: "Purview Retention Hold", enabled: exchangeData?.PurviewRetentionHold }, + { name: "Excluded from Org-Wide Hold", enabled: exchangeData?.ExcludedFromOrgWideHold }, + ]; + return ( { ) } /> + {/* Combine all mailbox hold types into a single PropertyListItem */} + ) : ( - getCippFormatting(exchangeData?.LitigationHold, "LitigationHold") +
+ {holds.map((hold) => ( + : } + color={hold.enabled ? "success" : "default"} + variant="outlined" + size="small" + sx={{ mr: 1, mb: 1 }} + /> + ))} +
) } /> diff --git a/src/components/CippComponents/CippCentralSearch.jsx b/src/components/CippComponents/CippCentralSearch.jsx index 6a90c4e35b9d..a3a8c214b5af 100644 --- a/src/components/CippComponents/CippCentralSearch.jsx +++ b/src/components/CippComponents/CippCentralSearch.jsx @@ -96,6 +96,12 @@ export const CippCentralSearch = ({ handleClose, open }) => { label="Search any menu item or page in CIPP" onChange={handleChange} onKeyDown={handleKeyDown} + onFocus={(event) => { + // Select all text on focus if there's content + if (event.target.value) { + event.target.select(); + } + }} value={searchValue} autoFocus /> @@ -106,10 +112,7 @@ export const CippCentralSearch = ({ handleClose, open }) => { {filteredItems.map((item, index) => ( - + handleCardClick(item.path)} aria-label={`Navigate to ${item.title}`} diff --git a/src/components/CippComponents/CippCustomVariables.jsx b/src/components/CippComponents/CippCustomVariables.jsx index 18319a4064e1..ce27670a9aed 100644 --- a/src/components/CippComponents/CippCustomVariables.jsx +++ b/src/components/CippComponents/CippCustomVariables.jsx @@ -64,7 +64,7 @@ const CippCustomVariables = ({ id }) => { url: "/api/ExecCippReplacemap", data: { Action: "!AddEdit", - customerId: id, + tenantId: id, }, relatedQueryKeys: [`CustomVariables_${id}`], }, @@ -77,7 +77,7 @@ const CippCustomVariables = ({ id }) => { data: { Action: "Delete", RowKey: "RowKey", - customerId: id, + tenantId: id, }, relatedQueryKeys: [`CustomVariables_${id}`], multiPost: false, @@ -100,7 +100,7 @@ const CippCustomVariables = ({ id }) => { title={id === "AllTenants" ? "Global Variables" : "Custom Variables"} actions={actions} api={{ - url: `/api/ExecCippReplacemap?Action=List&customerId=${id}`, + url: `/api/ExecCippReplacemap?Action=List&tenantId=${id}`, dataKey: "Results", }} simpleColumns={["RowKey", "Value"]} @@ -147,7 +147,7 @@ const CippCustomVariables = ({ id }) => { api={{ type: "POST", url: "/api/ExecCippReplacemap", - data: { Action: "AddEdit", customerId: id }, + data: { Action: "AddEdit", tenantId: id }, relatedQueryKeys: [`CustomVariables_${id}`], }} /> diff --git a/src/components/CippComponents/CippExchangeActions.jsx b/src/components/CippComponents/CippExchangeActions.jsx index 79ad7ee2e619..b947a19603fa 100644 --- a/src/components/CippComponents/CippExchangeActions.jsx +++ b/src/components/CippComponents/CippExchangeActions.jsx @@ -14,6 +14,7 @@ import { Outbox, NotificationImportant, DataUsage, + MailLock, } from "@mui/icons-material"; export const CippExchangeActions = () => { @@ -187,6 +188,21 @@ export const CippExchangeActions = () => { }, ], }, + { + label: "Set Retention Hold", + type: "POST", + url: "/api/ExecSetRetentionHold", + data: { UPN: "UPN", Identity: "Id" }, + confirmText: "What do you want to set Retention Hold to?", + icon: , + fields: [ + { + type: "switch", + name: "disable", + label: "Disable Retention Hold", + }, + ], + }, { label: "Set Mailbox Locale", type: "POST", diff --git a/src/components/CippComponents/CippTenantSelector.jsx b/src/components/CippComponents/CippTenantSelector.jsx index c21f75c58525..ee87c0ca3fdf 100644 --- a/src/components/CippComponents/CippTenantSelector.jsx +++ b/src/components/CippComponents/CippTenantSelector.jsx @@ -197,7 +197,7 @@ export const CippTenantSelector = (props) => { }, { label: "Exchange Portal", - link: `https://admin.exchange.microsoft.com/?landingpage=homepage&form=mac_sidebar&delegatedOrg=${currentTenant?.value}`, + link: `https://admin.cloud.microsoft/exchange?landingpage=homepage&form=mac_sidebar&delegatedOrg=${currentTenant?.value}`, icon: , }, { diff --git a/src/components/CippComponents/CollapsibleChipList.js b/src/components/CippComponents/CollapsibleChipList.js new file mode 100644 index 000000000000..23a9586e8034 --- /dev/null +++ b/src/components/CippComponents/CollapsibleChipList.js @@ -0,0 +1,29 @@ +import React, { useState } from "react"; +import { Box, Link } from "@mui/material"; + +export const CollapsibleChipList = ({ children, maxItems = 4 }) => { + const [expanded, setExpanded] = useState(false); + const childArray = React.Children.toArray(children); + const hasMoreItems = childArray.length > maxItems; + + const toggleExpanded = (e) => { + e.preventDefault(); + setExpanded(!expanded); + }; + + return ( + + {expanded ? childArray : childArray.slice(0, maxItems)} + + {hasMoreItems && ( + + {expanded ? "Show less" : `+${childArray.length - maxItems} more`} + + )} + + ); +}; diff --git a/src/components/CippFormPages/CippExchangeSettingsForm.jsx b/src/components/CippFormPages/CippExchangeSettingsForm.jsx index 66d8bb4b6ecb..db924f6476d1 100644 --- a/src/components/CippFormPages/CippExchangeSettingsForm.jsx +++ b/src/components/CippFormPages/CippExchangeSettingsForm.jsx @@ -56,6 +56,8 @@ const CippExchangeSettingsForm = (props) => { setRelatedQueryKeys([`Mailbox-${userId}`]); } else if (type === "ooo") { setRelatedQueryKeys([`ooo-${userId}`]); + } else if (type === "recipientLimits") { + setRelatedQueryKeys([`Mailbox-${userId}`]); } const values = formControl.getValues(); @@ -65,6 +67,13 @@ const CippExchangeSettingsForm = (props) => { ...values[type], }; + // Format data for recipient limits + if (type === "recipientLimits") { + data.Identity = currentSettings.Mailbox[0].Identity; + data.recipientLimit = values[type].MaxRecipients; + delete data.MaxRecipients; + } + //remove all nulls and undefined values Object.keys(data).forEach((key) => { if (data[key] === "" || data[key] === null) { @@ -76,6 +85,7 @@ const CippExchangeSettingsForm = (props) => { calendar: "/api/ExecEditCalendarPermissions", forwarding: "/api/ExecEmailForward", ooo: "/api/ExecSetOoO", + recipientLimits: "/api/ExecSetRecipientLimits", }; postRequest.mutate({ url: url[type], @@ -91,116 +101,164 @@ const CippExchangeSettingsForm = (props) => { const sections = [ { id: "mailboxPermissions", - cardLabelBox: "-", // This can be an icon or text label + cardLabelBox: "-", text: "Mailbox Permissions", subtext: "Manage mailbox permissions for users", formContent: ( - - - currentSettings?.Permissions?.some( - (perm) => - perm.AccessRights === "FullAccess" && perm.User === user.userPrincipalName - ) - ).map((user) => ({ - value: user.userPrincipalName, - label: `${user.displayName} (${user.userPrincipalName})`, - })) || [] - } - formControl={formControl} - /> - ({ - value: user.userPrincipalName, - label: `${user.displayName} (${user.userPrincipalName})`, - })) || [] - } - formControl={formControl} - /> - ({ - value: user.userPrincipalName, - label: `${user.displayName} (${user.userPrincipalName})`, - })) || [] - } - formControl={formControl} - /> - ({ - value: user.userPrincipalName, - label: `${user.displayName} (${user.userPrincipalName})`, - })) || [] - } - formControl={formControl} - /> - - currentSettings?.Permissions?.some( - (perm) => perm.AccessRights === "SendAs" && perm.User === user.userPrincipalName - ) - ).map((user) => ({ - value: user.userPrincipalName, - label: `${user.displayName} (${user.userPrincipalName})`, - })) || [] - } - formControl={formControl} - /> - ({ - value: user.userPrincipalName, - label: `${user.displayName} (${user.userPrincipalName})`, - })) || [] - } - formControl={formControl} - /> - - currentSettings?.Permissions?.some( - (perm) => - perm.AccessRights === "SendOnBehalf" && perm.User === user.userPrincipalName - ) - ).map((user) => ({ - value: user.userPrincipalName, - label: `${user.displayName} (${user.userPrincipalName})`, - })) || [] - } - formControl={formControl} - /> + + {/* Full Access Section */} + + Full Access + + Manage who has full access to this mailbox + + + + currentSettings?.Permissions?.some( + (perm) => + perm.AccessRights === "FullAccess" && perm.User === user.userPrincipalName + ) + ).map((user) => ({ + value: user.userPrincipalName, + label: `${user.displayName} (${user.userPrincipalName})`, + })) || [] + } + formControl={formControl} + /> + ({ + value: user.userPrincipalName, + label: `${user.displayName} (${user.userPrincipalName})`, + })) || [] + } + formControl={formControl} + /> + ({ + value: user.userPrincipalName, + label: `${user.displayName} (${user.userPrincipalName})`, + })) || [] + } + formControl={formControl} + /> + + + + {/* Send As Section */} + + Send As + + Manage who can send emails as this user + + + + currentSettings?.Permissions?.some( + (perm) => perm.AccessRights === "SendAs" && perm.User === user.userPrincipalName + ) + ).map((user) => ({ + value: user.userPrincipalName, + label: `${user.displayName} (${user.userPrincipalName})`, + })) || [] + } + formControl={formControl} + /> + ({ + value: user.userPrincipalName, + label: `${user.displayName} (${user.userPrincipalName})`, + })) || [] + } + formControl={formControl} + /> + + + + {/* Send On Behalf Section */} + + Send On Behalf + + Manage who can send emails on behalf of this user + + + + currentSettings?.Permissions?.some( + (perm) => + perm.AccessRights === "SendOnBehalf" && perm.User === user.userPrincipalName + ) + ).map((user) => ({ + value: user.userPrincipalName, + label: `${user.displayName} (${user.userPrincipalName})`, + })) || [] + } + formControl={formControl} + /> + ({ + value: user.userPrincipalName, + label: `${user.displayName} (${user.userPrincipalName})`, + })) || [] + } + formControl={formControl} + /> + + + @@ -458,6 +516,39 @@ const CippExchangeSettingsForm = (props) => { ), }, + { + id: "recipientLimits", + cardLabelBox: "RL", + text: "Recipient Limits", + subtext: "Set the maximum number of recipients per message", + formContent: ( + + + + + + + + + + + + + + ), + }, ]; return ( diff --git a/src/components/CippTable/CippDataTable.js b/src/components/CippTable/CippDataTable.js index 25beed16fb5d..058f97c09772 100644 --- a/src/components/CippTable/CippDataTable.js +++ b/src/components/CippTable/CippDataTable.js @@ -336,7 +336,103 @@ export const CippDataTable = (props) => { return aVal > bVal ? 1 : -1; }, }, + filterFns: { + notContains: (row, columnId, value) => { + const rowValue = row.getValue(columnId); + if (rowValue === null || rowValue === undefined) { + return false; + } + + const stringValue = String(rowValue); + if ( + stringValue.includes("[object Object]") || + !stringValue.toLowerCase().includes(value.toLowerCase()) + ) { + return true; + } else { + return false; + } + }, + regex: (row, columnId, value) => { + try { + const regex = new RegExp(value, "i"); + const rowValue = row.getValue(columnId); + if (typeof rowValue === "string" && !rowValue.includes("[object Object]")) { + return regex.test(rowValue); + } + return false; + } catch (error) { + // If regex is invalid, don't filter + return true; + } + }, + }, enableGlobalFilterModes: true, + renderGlobalFilterModeMenuItems: ({ internalFilterOptions, onSelectFilterMode }) => { + // add custom filter options + const customFilterOptions = [ + { + option: "regex", + label: "Regex", + symbol: "(.*)", + }, + ]; + + // add to the internalFilterOptions if not already present + customFilterOptions.forEach((filterOption) => { + if (!internalFilterOptions.some((option) => option.option === filterOption.option)) { + internalFilterOptions.push(filterOption); + } + }); + + internalFilterOptions.map((filterOption) => ( + onSelectFilterMode(filterOption.option)} + sx={{ + display: "flex", + alignItems: "center", + gap: "0.5rem", + }} + > + {filterOption.symbol} + {filterOption.label} + + )); + }, + renderColumnFilterModeMenuItems: ({ internalFilterOptions, onSelectFilterMode }) => { + // add custom filter options + const customFilterOptions = [ + { + option: "notContains", + label: "Not Contains", + symbol: "!*", + }, + { + option: "regex", + label: "Regex", + symbol: "(.*)", + }, + ]; + + // combine default and custom filter options + const combinedFilterOptions = [...internalFilterOptions, ...customFilterOptions]; + + return combinedFilterOptions.map((filterOption) => ( + onSelectFilterMode(filterOption.option)} + sx={{ + display: "flex", + alignItems: "center", + gap: "0.5rem", + }} + > + {filterOption.symbol} + {filterOption.label} + + )); + }, }); useEffect(() => { diff --git a/src/components/linearProgressWithLabel.jsx b/src/components/linearProgressWithLabel.jsx index e6650ac86615..ffac26c2086b 100644 --- a/src/components/linearProgressWithLabel.jsx +++ b/src/components/linearProgressWithLabel.jsx @@ -6,7 +6,7 @@ export const LinearProgressWithLabel = (props) => { - {`${Math.round(props.value)}% ${props?.addedLabel}`} + {`${Math.round(props.value)}% ${props?.addedLabel ?? ""}`} ); }; diff --git a/src/contexts/settings-context.js b/src/contexts/settings-context.js index 49b3eafedc6c..a265c9fd09aa 100644 --- a/src/contexts/settings-context.js +++ b/src/contexts/settings-context.js @@ -70,6 +70,7 @@ const storeSettings = (value) => { const initialSettings = { direction: "ltr", paletteMode: "light", + currentTheme: { value: "light", label: "light" }, pinNav: true, currentTenant: null, showDevtools: false, @@ -95,6 +96,10 @@ export const SettingsProvider = (props) => { const restored = restoreSettings(); if (restored) { + if (!restored.currentTheme && restored.paletteMode) { + restored.currentTheme = { value: restored.paletteMode, label: restored.paletteMode }; + } + setState((prevState) => ({ ...prevState, ...restored, @@ -129,6 +134,7 @@ export const SettingsProvider = (props) => { return !isEqual(initialSettings, { direction: state.direction, paletteMode: state.paletteMode, + currentTheme: state.currentTheme, pinNav: state.pinNav, }); }, [state]); diff --git a/src/data/languageList.json b/src/data/languageList.json index 3bd0ec3d8e82..fea7ddddc8ca 100644 --- a/src/data/languageList.json +++ b/src/data/languageList.json @@ -238,5 +238,11 @@ "Geographic area": "Vietnam", "tag": "vi-VN", "LCID": "1066" + }, + { + "language": "Welsh", + "Geographic area": "Wales", + "tag": "cy-GB", + "LCID": "1106" } ] diff --git a/src/data/portals.json b/src/data/portals.json index d5fdbd5ebeb0..2a3d78cdd3ae 100644 --- a/src/data/portals.json +++ b/src/data/portals.json @@ -2,8 +2,8 @@ { "label": "M365 Portal", "name": "M365_Portal", - "url": "https://admin.microsoft.com/Partner/BeginClientSession.aspx?CTID=customerId&CSDEST=o365admincenter", - "variable": "customerId", + "url": "https://admin.cloud.microsoft/?delegatedOrg=initialDomainName", + "variable": "initialDomainName", "target": "_blank", "external": true, "icon": "GlobeAltIcon" @@ -11,8 +11,8 @@ { "label": "Exchange Portal", "name": "Exchange_Portal", - "url": "https://admin.exchange.microsoft.com/?landingpage=homepage&form=mac_sidebar&delegatedOrg=defaultDomainName#", - "variable": "defaultDomainName", + "url": "https://admin.cloud.microsoft/exchange?landingpage=homepage&form=mac_sidebar&delegatedOrg=initialDomainName#", + "variable": "initialDomainName", "target": "_blank", "external": true, "icon": "Mail" @@ -29,8 +29,8 @@ { "label": "Teams Portal", "name": "Teams_Portal", - "url": "https://admin.teams.microsoft.com/?delegatedOrg=defaultDomainName", - "variable": "defaultDomainName", + "url": "https://admin.teams.microsoft.com/?delegatedOrg=initialDomainName", + "variable": "initialDomainName", "target": "_blank", "external": true, "icon": "FilePresent" @@ -56,8 +56,8 @@ { "label": "SharePoint Admin", "name": "SharePoint_Admin", - "url": "https://admin.microsoft.com/Partner/beginclientsession.aspx?CTID=customerId&CSDEST=SharePoint", - "variable": "customerId", + "url": "/api/ListSharePointAdminUrl?tenantFilter=defaultDomainName", + "variable": "defaultDomainName", "target": "_blank", "external": true, "icon": "Share" @@ -80,4 +80,4 @@ "external": true, "icon": "ShieldMoon" } -] +] \ No newline at end of file diff --git a/src/data/standards.json b/src/data/standards.json index 92ef2e2806f5..6abcb77d9791 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -779,8 +779,8 @@ "name": "standards.StaleEntraDevices", "cat": "Entra (AAD) Standards", "tag": ["CIS"], - "helpText": "Cleans up Entra devices that have not connected/signed in for the specified number of days.", - "docsDescription": "Cleans up Entra devices that have not connected/signed in for the specified number of days. First disables and later deletes the devices. More info can be found in the [Microsoft documentation](https://learn.microsoft.com/en-us/entra/identity/devices/manage-stale-devices)", + "helpText": "Remediate is currently not available. Cleans up Entra devices that have not connected/signed in for the specified number of days.", + "docsDescription": "Remediate is currently not available. Cleans up Entra devices that have not connected/signed in for the specified number of days. First disables and later deletes the devices. More info can be found in the [Microsoft documentation](https://learn.microsoft.com/en-us/entra/identity/devices/manage-stale-devices)", "addedComponent": [ { "type": "number", @@ -3404,6 +3404,27 @@ "powershellEquivalent": "Set-CsTenantFederationConfiguration", "recommendedBy": [] }, + { + "name": "standards.TeamsMeetingRecordingExpiration", + "cat": "Teams Standards", + "tag": [], + "helpText": "Sets the default number of days after which Teams meeting recordings automatically expire. Valid values are -1 (Never Expire) or between 1 and 99999. The default value is 120 days.", + "docsDescription": "Allows administrators to configure a default expiration period (in days) for Teams meeting recordings. Recordings older than this period will be automatically moved to the recycle bin. This setting helps manage storage consumption and enforce data retention policies.", + "addedComponent": [ + { + "type": "number", + "name": "standards.TeamsMeetingRecordingExpiration.ExpirationDays", + "label": "Recording Expiration Days (e.g., 365)", + "required": true + } + ], + "label": "Set Teams Meeting Recording Expiration", + "impact": "Medium Impact", + "impactColour": "warning", + "addedDate": "2025-04-17", + "powershellEquivalent": "Set-CsTeamsMeetingPolicy -Identity Global -MeetingRecordingExpirationDays ", + "recommendedBy": [] + }, { "name": "standards.TeamsMessagingPolicy", "cat": "Teams Standards", @@ -3494,8 +3515,8 @@ "cat": "Device Management Standards", "tag": [], "disabledFeatures": { - "report": true, - "warn": true, + "report": false, + "warn": false, "remediate": false }, "helpText": "Deploy the Autopilot Status Page, which shows progress during device setup through Autopilot.", @@ -3567,8 +3588,8 @@ "cat": "Device Management Standards", "tag": [], "disabledFeatures": { - "report": true, - "warn": true, + "report": false, + "warn": false, "remediate": false }, "helpText": "Assign the appropriate Autopilot profile to streamline device deployment.", @@ -3593,6 +3614,7 @@ "type": "autoComplete", "multiple": false, "creatable": false, + "required": false, "name": "standards.AutopilotProfile.Languages", "label": "Languages", "api": { diff --git a/src/layouts/top-nav.js b/src/layouts/top-nav.js index f41183ba0f11..ec7f06c7f3dd 100644 --- a/src/layouts/top-nav.js +++ b/src/layouts/top-nav.js @@ -44,6 +44,7 @@ export const TopNav = (props) => { const themeName = settings.currentTheme?.value === "light" ? "dark" : "light"; settings.handleUpdate({ currentTheme: { value: themeName, label: themeName }, + paletteMode: themeName, }); }, [settings]); diff --git a/src/pages/endpoint/applications/list/add.jsx b/src/pages/endpoint/applications/list/add.jsx index 254162f48e4b..d82b40deeceb 100644 --- a/src/pages/endpoint/applications/list/add.jsx +++ b/src/pages/endpoint/applications/list/add.jsx @@ -139,7 +139,6 @@ const ApplicationDeploymentForm = () => { options={[ { value: "datto", label: "Datto RMM" }, { value: "syncro", label: "Syncro RMM" }, - { value: "immy", label: "ImmyBot" }, { value: "huntress", label: "Huntress" }, { value: "automate", label: "CW Automate" }, { value: "cwcommand", label: "CW Command" }, @@ -208,26 +207,6 @@ const ApplicationDeploymentForm = () => { {/* Similar blocks for other rmmname values */} - {/* For "immy" */} - - {selectedTenants?.map((tenant, index) => ( - - - - ))} - - {/* For "huntress" */} { type="autoComplete" label="Select Package" name="packageSearch" - options={winGetSearchResults.data?.data?.map((item) => ({ - value: item, - label: `${item.applicationName} - ${item.packagename}`, - }))} + options={ + winGetSearchResults.data?.data + ? winGetSearchResults.data?.data?.map((item) => ({ + value: item, + label: `${item.applicationName} - ${item.packagename}`, + })) + : [] + } multiple={false} formControl={formControl} isFetching={winGetSearchResults.isLoading} @@ -492,11 +475,12 @@ const ApplicationDeploymentForm = () => { label="Select Package" name="packageSearch" options={ - ChocosearchResults.isSuccess && - ChocosearchResults.data?.data?.Results?.map((item) => ({ - value: item, - label: `${item.applicationName} - ${item.packagename}`, - })) + ChocosearchResults.isSuccess && ChocosearchResults.data?.data + ? ChocosearchResults.data?.data?.Results?.map((item) => ({ + value: item, + label: `${item.applicationName} - ${item.packagename}`, + })) + : [] } multiple={false} formControl={formControl} diff --git a/src/pages/endpoint/applications/list/index.js b/src/pages/endpoint/applications/list/index.js index cfde01fbfce0..d10fe0727555 100644 --- a/src/pages/endpoint/applications/list/index.js +++ b/src/pages/endpoint/applications/list/index.js @@ -14,7 +14,7 @@ const Page = () => { type: "POST", url: "/api/ExecAssignApp", data: { - AssignTo: "AllUsers", + AssignTo: "!AllUsers", ID: "id", }, confirmText: "Are you sure you want to assign this app to all users?", @@ -26,7 +26,7 @@ const Page = () => { type: "POST", url: "/api/ExecAssignApp", data: { - AssignTo: "AllDevices", + AssignTo: "!AllDevices", ID: "id", }, confirmText: "Are you sure you want to assign this app to all devices?", @@ -38,7 +38,7 @@ const Page = () => { type: "POST", url: "/api/ExecAssignApp", data: { - AssignTo: "Both", + AssignTo: "!Both", ID: "id", }, confirmText: "Are you sure you want to assign this app to all users and devices?", diff --git a/src/pages/identity/administration/users/add.jsx b/src/pages/identity/administration/users/add.jsx index 6db4b2a4bbf6..dfbdc7891f03 100644 --- a/src/pages/identity/administration/users/add.jsx +++ b/src/pages/identity/administration/users/add.jsx @@ -56,7 +56,7 @@ const Page = () => { label="Copy properties from another user" multiple={false} select={ - "id,userPrincipalName,displayName,givenName,surname,mailNickname,jobTitle,department,streetAddress,postalCode,companyName,mobilePhone,businessPhones,usageLocation" + "id,userPrincipalName,displayName,givenName,surname,mailNickname,jobTitle,department,streetAddress,postalCode,companyName,mobilePhone,businessPhones,usageLocation,office" } addedField={{ groupType: "calculatedGroupType", @@ -74,6 +74,7 @@ const Page = () => { mobilePhone: "mobilePhone", businessPhones: "businessPhones", usageLocation: "usageLocation", + office: "office", }} /> diff --git a/src/pages/tenant/gdap-management/onboarding/start.js b/src/pages/tenant/gdap-management/onboarding/start.js index 3a14356c6cd8..0880f78a5192 100644 --- a/src/pages/tenant/gdap-management/onboarding/start.js +++ b/src/pages/tenant/gdap-management/onboarding/start.js @@ -496,7 +496,7 @@ const Page = () => { value: getCippFormatting( currentInvite ? currentInvite.RoleMappings - : currentRelationship?.addedFields?.accessDetails.unifiedRoles, + : currentRelationship?.addedFields?.accessDetails?.unifiedRoles, "unifiedRoles", "object" ), diff --git a/src/pages/tenant/standards/list-standards/index.js b/src/pages/tenant/standards/list-standards/index.js index 236eefb61925..2f0c84037842 100644 --- a/src/pages/tenant/standards/list-standards/index.js +++ b/src/pages/tenant/standards/list-standards/index.js @@ -115,7 +115,7 @@ const Page = () => { data: { ID: "GUID", }, - confirmText: "Are you sure you want to delete this template?", + confirmText: "Are you sure you want to delete [templateName]?", multiPost: false, }, ]; diff --git a/src/pages/tools/community-repos/index.js b/src/pages/tools/community-repos/index.js index 67eeafbe732b..beadf2237bf1 100644 --- a/src/pages/tools/community-repos/index.js +++ b/src/pages/tools/community-repos/index.js @@ -204,9 +204,9 @@ const Page = () => { { - createForm.setValue("Type", e.target.value); + createForm.setValue("type", e.target.value); }} > } label="User" /> @@ -214,7 +214,7 @@ const Page = () => { { const isText = type === "text"; @@ -58,6 +59,36 @@ export const getCippFormatting = (data, cellName, type, canReceive, flatten = tr portal_sharepoint: Description, }; + // Create a helper function to render chips with CollapsibleChipList + const renderChipList = (items, maxItems = 4) => { + if (!Array.isArray(items) || items.length === 0) { + return ; + } + + return ( + + {items.map((item, index) => { + // Avoid JSON.stringify which can cause circular reference errors + let key = index; + if (typeof item === "string" || typeof item === "number" || typeof item === "boolean") { + key = item; + } else if (typeof item === "object" && item?.label) { + key = `item-${item.label}-${index}`; + } + + return ( + + ); + })} + + ); + }; + //if the cellName starts with portal_, return text, or a link with an icon if (cellName.startsWith("portal_")) { const IconComponent = portalIcons[cellName]; @@ -214,24 +245,32 @@ export const getCippFormatting = (data, cellName, type, canReceive, flatten = tr if (Array.isArray(data)) { return isText ? data.join(", ") - : data.map((item) => ( - { + const itemText = item?.label ? item.label : item; + let icon = null; + + if (item?.type === "Group") { + icon = ( - ) : item?.type === "Tenant" ? ( + ); + } else { + icon = ( - ) : null + ); } - type="chip" - /> - )); + + return { + label: itemText, + icon: icon, + key: key, + }; + }) + ); } else { return isText ? ( data @@ -280,15 +319,10 @@ export const getCippFormatting = (data, cellName, type, canReceive, flatten = tr ? data .map((item) => (typeof item === "object" && item?.label ? item.label : item)) .join(", ") - : data.map( - (item) => - item && ( - - ) + : renderChipList( + data + .filter((item) => item) + .map((item) => (typeof item === "object" && item?.label ? item.label : item)) ); } } @@ -349,11 +383,7 @@ export const getCippFormatting = (data, cellName, type, canReceive, flatten = tr : typeof data === "string" ? data.split(", ") : []; - return isText - ? accessRights.join(", ") - : accessRights.map((accessRight) => ( - - )); + return isText ? accessRights.join(", ") : renderChipList(accessRights); } // Handle null or undefined data @@ -379,13 +409,11 @@ export const getCippFormatting = (data, cellName, type, canReceive, flatten = tr if (cellName === "From") { // if data is array if (Array.isArray(data)) { - return isText ? data.join(", ") : data.join(", "); + return isText ? data.join(", ") : renderChipList(data); } else { // split on ; , and create chips per email const emails = data.split(/;|,/); - return isText - ? emails.join(", ") - : emails.map((email) => ); + return isText ? emails.join(", ") : renderChipList(emails); } } @@ -394,10 +422,37 @@ export const getCippFormatting = (data, cellName, type, canReceive, flatten = tr if (!Array.isArray(data)) { data = [data]; } + + if (data.length === 0) { + return isText ? ( + "No data" + ) : ( + + ); + } + + const primaryEmail = data.find((email) => email.startsWith("SMTP:")); const emails = data.map((email) => email.replace(/smtp:/i, "")); - return isText - ? emails.join(", ") - : emails.map((email) => ); + return isText ? ( + emails.join(", ") + ) : ( + { + if (primaryEmail.includes(email)) { + return { + email: email, + primary: true, + }; + } else { + return { + email: email, + primary: false, + }; + } + })} + /> + ); } // Handle assigned licenses @@ -407,16 +462,21 @@ export const getCippFormatting = (data, cellName, type, canReceive, flatten = tr ? Array.isArray(translatedLicenses) ? translatedLicenses.join(", ") : translatedLicenses - : translatedLicenses.map((license) => ( - - )); + : Array.isArray(translatedLicenses) + ? renderChipList(translatedLicenses) + : translatedLicenses; } if (cellName === "unifiedRoles") { if (Array.isArray(data)) { const roles = data.map((role) => getCippRoleTranslation(role.roleDefinitionId)); - return isText ? roles.join(", ") : roles; + return isText ? roles.join(", ") : renderChipList(roles, 12); } + return isText ? ( + "No roles" + ) : ( + + ); } // Handle roleDefinitionId @@ -444,12 +504,53 @@ export const getCippFormatting = (data, cellName, type, canReceive, flatten = tr // if data is a json string, parse it and return a table if (typeof data === "string" && (data.startsWith("{") || data.startsWith("["))) { try { + const parsedData = JSON.parse(data); + + // parsedData is an array and only contains one element + if ( + Array.isArray(parsedData) && + parsedData.length === 1 && + typeof parsedData[0] !== "object" + ) { + // Handle boolean values + if (typeof parsedData[0] === "boolean") { + return isText ? ( + parsedData[0] ? ( + "Yes" + ) : ( + "No" + ) + ) : parsedData[0] ? ( + + ) : ( + + ); + } + + return isText ? ( + JSON.stringify(parsedData[0]) + ) : ( + + ); + } + + // Check if parsed data is a simple array of strings + if ( + Array.isArray(parsedData) && + parsedData.every((item) => typeof item === "string" || typeof item === "number") && + flatten + ) { + return isText ? parsedData.join(", ") : renderChipList(parsedData); + } return isText ? ( data ) : ( - + ); - } catch (e) {} + } catch (e) { + // If parsing fails, return the original string + return isText ? data : {data}; + } } if (cellName === "key") { @@ -571,20 +672,24 @@ export const getCippFormatting = (data, cellName, type, canReceive, flatten = tr if (Array.isArray(data) && data.every((item) => typeof item === "string") && flatten) { // if string matches json format, parse it if (data.every((item) => item.startsWith("{") || item.startsWith("["))) { - return isText ? ( - JSON.stringify(data) - ) : ( - JSON.parse(item))} - tableTitle={getCippTranslation(cellName)} - /> - ); + try { + const parsedData = data.map((item) => JSON.parse(item)); + // Check if parsedData contains simple strings + if (parsedData.every((item) => typeof item === "string")) { + return isText ? parsedData.join(", ") : renderChipList(parsedData); + } + return isText ? ( + JSON.stringify(data) + ) : ( + + ); + } catch (e) { + return isText ? JSON.stringify(data) : data.join(", "); + } } //if the array is empty, return "No data" - return isText - ? data.join(", ") - : data.map((item) => ); + return isText ? data.join(", ") : renderChipList(data); } // Handle objects diff --git a/src/utils/get-cipp-license-translation.js b/src/utils/get-cipp-license-translation.js index 9303303a9a27..9e36c5cc7db1 100644 --- a/src/utils/get-cipp-license-translation.js +++ b/src/utils/get-cipp-license-translation.js @@ -7,6 +7,10 @@ export const getCippLicenseTranslation = (licenseArray) => { licenseArray = [licenseArray]; } + if (!licenseArray || licenseArray.length === 0) { + return ["No Licenses Assigned"]; + } + licenseArray?.forEach((licenseAssignment) => { let found = false; for (let x = 0; x < M365Licenses.length; x++) {