From 0ff11516d55ff531eb38ce6a96252e4f96c46ab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Wed, 28 May 2025 19:08:23 +0200 Subject: [PATCH 01/25] Feat: Add new standard for Direct Send --- src/data/standards.json | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/data/standards.json b/src/data/standards.json index 4dba29061348..f08dd9203e84 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -1612,6 +1612,38 @@ "powershellEquivalent": "Set-OrganizationConfig -BookingsEnabled", "recommendedBy": [] }, + { + "name": "standards.EXODirectSend", + "cat": "Exchange Standards", + "tag": [], + "helpText": "Sets the state of Direct Send in Exchange Online. Direct Send allows applications to send emails directly to Exchange Online mailboxes as the tenants domains, without requiring authentication.", + "docsDescription": "Controls whether applications can use Direct Send to send emails directly to Exchange Online mailboxes as the tenants domains, without requiring authentication. A detailed explanation from Microsoft can be found [here.](https://learn.microsoft.com/en-us/exchange/mail-flow-best-practices/how-to-set-up-a-multifunction-device-or-application-to-send-email-using-microsoft-365-or-office-365)", + "addedComponent": [ + { + "type": "autoComplete", + "multiple": false, + "creatable": false, + "label": "Select value", + "name": "standards.EXODirectSend.state", + "options": [ + { + "label": "Enabled", + "value": "enabled" + }, + { + "label": "Disabled", + "value": "disabled" + } + ] + } + ], + "label": "Set Direct Send state", + "impact": "Medium Impact", + "impactColour": "warning", + "addedDate": "2025-05-28", + "powershellEquivalent": "Set-OrganizationConfig -RejectDirectSend $true/$false", + "recommendedBy": [] + }, { "name": "standards.DisableOutlookAddins", "cat": "Exchange Standards", From bfa04ce3c4deff006eca3bdba0614c842c322e45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Wed, 28 May 2025 19:24:53 +0200 Subject: [PATCH 02/25] reset from after submit --- src/pages/teams-share/teams/list-team/add.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/teams-share/teams/list-team/add.jsx b/src/pages/teams-share/teams/list-team/add.jsx index 3d78e515ee0a..28a92cc6df65 100644 --- a/src/pages/teams-share/teams/list-team/add.jsx +++ b/src/pages/teams-share/teams/list-team/add.jsx @@ -28,6 +28,7 @@ const TeamsAddTeamForm = () => { title="Add Team" backButtonTitle="Teams Overview" postUrl="/api/AddTeam" + resetForm={true} customDataformatter={(values) => { const shippedValues = { tenantID: tenantDomain, From 5c5addedb99f6af250730037d63cd8a167c8e2e1 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 28 May 2025 13:35:20 -0400 Subject: [PATCH 03/25] remove beta tag --- src/pages/cipp/super-admin/cipp-roles/index.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/pages/cipp/super-admin/cipp-roles/index.js b/src/pages/cipp/super-admin/cipp-roles/index.js index 9f2ea69fbfa5..5f0585c956d6 100644 --- a/src/pages/cipp/super-admin/cipp-roles/index.js +++ b/src/pages/cipp/super-admin/cipp-roles/index.js @@ -12,14 +12,10 @@ const Page = () => { - CIPP roles can be used to restrict permissions for users with the 'editor' or - 'readonly' roles in CIPP. They can be limited to a subset of tenants and API - permissions. To restrict direct API access, create a role with the name 'CIPP-API'. + CIPP roles can be used to restrict permissions for users with the 'editor' or 'readonly' + roles in CIPP. They can be limited to a subset of tenants and API permissions. To + restrict direct API access, create a role with the name 'CIPP-API'. - }> - This functionality is in beta and should be treated as such. The custom role must be - added to the user in SWA in conjunction with the base role. (e.g. editor,mycustomrole) - From c9297eca304a3c8c2528ddd9f7d6c3174cceb427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Wed, 28 May 2025 20:05:22 +0200 Subject: [PATCH 04/25] Feat: Add delete team action --- src/pages/teams-share/teams/list-team/index.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/pages/teams-share/teams/list-team/index.js b/src/pages/teams-share/teams/list-team/index.js index fb3580cc2622..1c1bb645a813 100644 --- a/src/pages/teams-share/teams/list-team/index.js +++ b/src/pages/teams-share/teams/list-team/index.js @@ -1,7 +1,7 @@ import { Layout as DashboardLayout } from "/src/layouts/index.js"; import { CippTablePage } from "/src/components/CippComponents/CippTablePage.jsx"; import { Button } from "@mui/material"; -import { GroupAdd } from "@mui/icons-material"; +import { Delete, GroupAdd } from "@mui/icons-material"; import Link from "next/link"; import { Edit } from "@mui/icons-material"; @@ -16,6 +16,19 @@ const Page = () => { color: "warning", icon: , }, + { + label: "Delete Team", + type: "POST", + url: "/api/ExecGroupsDelete", + icon: , + data: { + ID: "id", + GroupType: "!Microsoft 365", + DisplayName: "displayName", + }, + confirmText: "Are you sure you want to delete this team?", + multiPost: false, + }, ]; return ( From f9d9bc1db00a96c65db5ea92765d248ff44c7eec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Wed, 28 May 2025 21:26:39 +0200 Subject: [PATCH 05/25] chore: update licenses data to newest from MS --- src/data/M365Licenses.json | 98 +++++++++++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) diff --git a/src/data/M365Licenses.json b/src/data/M365Licenses.json index aa7ef62fb439..b15c28da101b 100644 --- a/src/data/M365Licenses.json +++ b/src/data/M365Licenses.json @@ -13079,6 +13079,102 @@ "Service_Plan_Id": "89f1c4c8-0878-40f7-804d-869c9128ab5d", "Service_Plans_Included_Friendly_Names": "Power Platform Connectors in Microsoft 365 Copilot" }, + { + "Product_Display_Name": "Microsoft 365 Copilot for Sales", + "String_Id": "Microsoft_Copilot_for_Sales", + "GUID": "15f2e9fc-b782-4f73-bf51-81d8b7fff6f4", + "Service_Plan_Name": "COPILOT_STUDIO_IN_COPILOT_FOR_M365", + "Service_Plan_Id": "fe6c28b3-d468-44ea-bbd0-a10a5167435c", + "Service_Plans_Included_Friendly_Names": "Copilot Studio in Copilot for M365" + }, + { + "Product_Display_Name": "Microsoft 365 Copilot for Sales", + "String_Id": "Microsoft_Copilot_for_Sales", + "GUID": "15f2e9fc-b782-4f73-bf51-81d8b7fff6f4", + "Service_Plan_Name": "GRAPH_CONNECTORS_COPILOT", + "Service_Plan_Id": "82d30987-df9b-4486-b146-198b21d164c7", + "Service_Plans_Included_Friendly_Names": "Graph Connectors in Microsoft 365 Copilot" + }, + { + "Product_Display_Name": "Microsoft 365 Copilot for Sales", + "String_Id": "Microsoft_Copilot_for_Sales", + "GUID": "15f2e9fc-b782-4f73-bf51-81d8b7fff6f4", + "Service_Plan_Name": "M365_COPILOT_INTELLIGENT_SEARCH", + "Service_Plan_Id": "931e4a88-a67f-48b5-814f-16a5f1e6028d", + "Service_Plans_Included_Friendly_Names": "Intelligent Search" + }, + { + "Product_Display_Name": "Microsoft 365 Copilot for Sales", + "String_Id": "Microsoft_Copilot_for_Sales", + "GUID": "15f2e9fc-b782-4f73-bf51-81d8b7fff6f4", + "Service_Plan_Name": "M365_COPILOT_SHAREPOINT", + "Service_Plan_Id": "0aedf20c-091d-420b-aadf-30c042609612", + "Service_Plans_Included_Friendly_Names": "Microsoft 365 Copilot for SharePoint" + }, + { + "Product_Display_Name": "Microsoft 365 Copilot for Sales", + "String_Id": "Microsoft_Copilot_for_Sales", + "GUID": "15f2e9fc-b782-4f73-bf51-81d8b7fff6f4", + "Service_Plan_Name": "M365_COPILOT_TEAMS", + "Service_Plan_Id": "b95945de-b3bd-46db-8437-f2beb6ea2347", + "Service_Plans_Included_Friendly_Names": "Microsoft 365 Copilot in Microsoft Teams" + }, + { + "Product_Display_Name": "Microsoft 365 Copilot for Sales", + "String_Id": "Microsoft_Copilot_for_Sales", + "GUID": "15f2e9fc-b782-4f73-bf51-81d8b7fff6f4", + "Service_Plan_Name": "M365_COPILOT_APPS", + "Service_Plan_Id": "a62f8878-de10-42f3-b68f-6149a25ceb97", + "Service_Plans_Included_Friendly_Names": "Microsoft 365 Copilot in Productivity Apps" + }, + { + "Product_Display_Name": "Microsoft 365 Copilot for Sales", + "String_Id": "Microsoft_Copilot_for_Sales", + "GUID": "15f2e9fc-b782-4f73-bf51-81d8b7fff6f4", + "Service_Plan_Name": "Microsoft_Copilot_for_Sales", + "Service_Plan_Id": "a2194428-ead1-4fc1-bb81-ab8675125f42", + "Service_Plans_Included_Friendly_Names": "Microsoft Copilot for Sales" + }, + { + "Product_Display_Name": "Microsoft 365 Copilot for Sales", + "String_Id": "Microsoft_Copilot_for_Sales", + "GUID": "15f2e9fc-b782-4f73-bf51-81d8b7fff6f4", + "Service_Plan_Name": "Microsoft_Copilot_for_Sales_PowerAutomate", + "Service_Plan_Id": "0c1c2af2-6c51-43c7-9c55-fa487ac147ff", + "Service_Plans_Included_Friendly_Names": "Microsoft Copilot for Sales with Power Automate" + }, + { + "Product_Display_Name": "Microsoft 365 Copilot for Sales", + "String_Id": "Microsoft_Copilot_for_Sales", + "GUID": "15f2e9fc-b782-4f73-bf51-81d8b7fff6f4", + "Service_Plan_Name": "M365_COPILOT_BUSINESS_CHAT", + "Service_Plan_Id": "3f30311c-6b1e-48a4-ab79-725b469da960", + "Service_Plans_Included_Friendly_Names": "Microsoft Copilot with Graph-grounded chat" + }, + { + "Product_Display_Name": "Microsoft 365 Copilot for Sales", + "String_Id": "Microsoft_Copilot_for_Sales", + "GUID": "15f2e9fc-b782-4f73-bf51-81d8b7fff6f4", + "Service_Plan_Name": "WORKPLACE_ANALYTICS_INSIGHTS_USER", + "Service_Plan_Id": "b622badb-1b45-48d5-920f-4b27a2c0996c", + "Service_Plans_Included_Friendly_Names": "Microsoft Viva Insights" + }, + { + "Product_Display_Name": "Microsoft 365 Copilot for Sales", + "String_Id": "Microsoft_Copilot_for_Sales", + "GUID": "15f2e9fc-b782-4f73-bf51-81d8b7fff6f4", + "Service_Plan_Name": "WORKPLACE_ANALYTICS_INSIGHTS_BACKEND", + "Service_Plan_Id": "ff7b261f-d98b-415b-827c-42a3fdf015af", + "Service_Plans_Included_Friendly_Names": "Microsoft Viva Insights Backend" + }, + { + "Product_Display_Name": "Microsoft 365 Copilot for Sales", + "String_Id": "Microsoft_Copilot_for_Sales", + "GUID": "15f2e9fc-b782-4f73-bf51-81d8b7fff6f4", + "Service_Plan_Name": "M365_COPILOT_CONNECTORS", + "Service_Plan_Id": "89f1c4c8-0878-40f7-804d-869c9128ab5d", + "Service_Plans_Included_Friendly_Names": "Power Platform Connectors in Microsoft 365 Copilot" + }, { "Product_Display_Name": "Microsoft Copilot for Microsoft 365", "String_Id": "M365_Copilot", @@ -39109,7 +39205,7 @@ "GUID": "0f13a262-dc6f-4800-8dc6-a62f72c95fad", "Service_Plan_Name": "CDSAICAPACITY_PERUSER", "Service_Plan_Id": "91f50f7b-2204-4803-acac-5cf5668b8b39", - "Service_Plans_Included_Friendly_Names": "DO NOT USE - AI Builder capacity Per User add-on" + "Service_Plans_Included_Friendly_Names": "AI Builder capacity Per User add-on" }, { "Product_Display_Name": "PowerApps & Flow GCC Test - O365 & Dyn365 Plans", From 82f4fb18afc43a26a550c68e65c32f50603824bb Mon Sep 17 00:00:00 2001 From: ngms-psh Date: Wed, 28 May 2025 21:47:01 +0200 Subject: [PATCH 06/25] Fixed issue where global settings was not updating with tenant select --- .../email/spamfilter/list-quarantine-policies/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/email/spamfilter/list-quarantine-policies/index.js b/src/pages/email/spamfilter/list-quarantine-policies/index.js index 29be070c7f1f..fa301cb631f4 100644 --- a/src/pages/email/spamfilter/list-quarantine-policies/index.js +++ b/src/pages/email/spamfilter/list-quarantine-policies/index.js @@ -30,7 +30,7 @@ const Page = () => { const GlobalQuarantinePolicy = ApiGetCall({ url: "/api/ListQuarantinePolicy", data: { tenantFilter: currentTenant, type: "GlobalQuarantinePolicy" }, - queryKey: "GlobalQuarantinePolicy", + queryKey: `GlobalQuarantinePolicy-${currentTenant}`, }); // Get the policy data regardless of array or object @@ -304,7 +304,7 @@ const Page = () => { const infoBarData = [ { icon: , - data: globalQuarantineData?.EndUserSpamNotificationFrequency, + data: globalQuarantineData?.EndUserSpamNotificationFrequency ?? "n/a", name: "Notification Frequency", }, { @@ -390,7 +390,7 @@ const Page = () => { Name: "Name", Identity: "Guid", }, - relatedQueryKeys: ["GlobalQuarantinePolicy"], + relatedQueryKeys: [`GlobalQuarantinePolicy-${currentTenant}`], confirmText: "Are you sure you want to update Global Quarantine settings?", }} From 2aad4d4b82db12bdf3a1c87101c61559bc9e2fd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Thu, 29 May 2025 01:51:36 +0200 Subject: [PATCH 07/25] feat: Update language label to include geographic area in AutopilotProfileForm --- src/pages/endpoint/autopilot/list-profiles/add.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/endpoint/autopilot/list-profiles/add.jsx b/src/pages/endpoint/autopilot/list-profiles/add.jsx index 364e3fb19835..26fd85204b24 100644 --- a/src/pages/endpoint/autopilot/list-profiles/add.jsx +++ b/src/pages/endpoint/autopilot/list-profiles/add.jsx @@ -68,9 +68,9 @@ const AutopilotProfileForm = () => { type="autoComplete" label="Language" name="languages" - options={languageList.map(({ language, tag }) => ({ + options={languageList.map(({ language, tag, "Geographic area": geographicArea }) => ({ value: tag, - label: language, + label: `${language} - ${geographicArea}`, // Format as "language - geographic area" for display }))} formControl={formControl} multiple={false} From 26182e34e7d1e216339ce11f6adfcc6b7f3f9a12 Mon Sep 17 00:00:00 2001 From: Jr7468 Date: Thu, 29 May 2025 01:31:29 +0100 Subject: [PATCH 08/25] Added a section to remove proxy addresses and set primary addresses --- .../administration/users/user/exchange.jsx | 107 +++++++++++++++++- 1 file changed, 104 insertions(+), 3 deletions(-) diff --git a/src/pages/identity/administration/users/user/exchange.jsx b/src/pages/identity/administration/users/user/exchange.jsx index 2ce3578fa876..82ecccce694d 100644 --- a/src/pages/identity/administration/users/user/exchange.jsx +++ b/src/pages/identity/administration/users/user/exchange.jsx @@ -4,7 +4,7 @@ import { useRouter } from "next/router"; import { ApiGetCall } from "/src/api/ApiCall"; import CippFormSkeleton from "/src/components/CippFormPages/CippFormSkeleton"; import CalendarIcon from "@heroicons/react/24/outline/CalendarIcon"; -import { Check, Error, Mail, Fingerprint, Launch } from "@mui/icons-material"; +import { Check, Error, Mail, Fingerprint, Launch, Delete, Star } from "@mui/icons-material"; import { HeaderedTabbedLayout } from "../../../../../layouts/HeaderedTabbedLayout"; import tabOptions from "./tabOptions"; import { CippTimeAgo } from "../../../../../components/CippComponents/CippTimeAgo"; @@ -18,16 +18,20 @@ import CippExchangeSettingsForm from "../../../../../components/CippFormPages/Ci import { useForm } from "react-hook-form"; import { Alert, Button, Collapse, CircularProgress, Typography } from "@mui/material"; import { CippApiResults } from "../../../../../components/CippComponents/CippApiResults"; -import { Block, PlayArrow, DeleteForever } from "@mui/icons-material"; +import { Block, PlayArrow } from "@mui/icons-material"; import { CippPropertyListCard } from "../../../../../components/CippCards/CippPropertyListCard"; import { getCippTranslation } from "../../../../../utils/get-cipp-translation"; import { getCippFormatting } from "../../../../../utils/get-cipp-formatting"; import CippExchangeActions from "../../../../../components/CippComponents/CippExchangeActions"; +import { CippApiDialog } from "../../../../../components/CippComponents/CippApiDialog"; +import { useDialog } from "../../../../../hooks/use-dialog"; const Page = () => { const userSettingsDefaults = useSettings(); const [waiting, setWaiting] = useState(false); const [showDetails, setShowDetails] = useState(false); + const [actionData, setActionData] = useState({ ready: false }); + const createDialog = useDialog(); const router = useRouter(); const { userId } = router.query; @@ -221,7 +225,7 @@ const Page = () => { { label: "Remove Mailbox Rule", type: "POST", - icon: , + icon: , url: "/api/ExecRemoveMailboxRule", data: { ruleId: "Identity", @@ -287,6 +291,90 @@ const Page = () => { }, ]; + const proxyAddressActions = [ + { + label: "Make Primary", + type: "POST", + icon: , + url: "/api/SetUserAliases", + data: { + id: userId, + tenantFilter: userSettingsDefaults.currentTenant, + MakePrimary: "Address", + }, + confirmText: "Are you sure you want to make this the primary proxy address?", + multiPost: false, + relatedQueryKeys: `ListUsers-${userId}`, + }, + { + label: "Remove Proxy Address", + type: "POST", + icon: , + url: "/api/SetUserAliases", + data: { + id: userId, + tenantFilter: userSettingsDefaults.currentTenant, + RemovedAliases: "Address", + }, + confirmText: "Are you sure you want to remove this proxy address?", + multiPost: false, + relatedQueryKeys: `ListUsers-${userId}`, + }, + ]; + + const proxyAddressesCard = [ + { + id: 1, + cardLabelBox: { + cardLabelBoxHeader: graphUserRequest.isFetching ? ( + + ) : graphUserRequest.data?.[0]?.proxyAddresses?.length !== 0 ? ( + + ) : ( + + ), + }, + text: "Current Proxy Addresses", + subtext: graphUserRequest.data?.[0]?.proxyAddresses?.length > 1 + ? "Proxy addresses are configured for this user" + : "No proxy addresses configured for this user", + statusColor: "green.main", + table: { + title: "Proxy Addresses", + hideTitle: true, + data: graphUserRequest.data?.[0]?.proxyAddresses?.map(address => ({ + Address: address, + Type: address.startsWith('SMTP:') ? 'Primary' : 'Alias', + })) || [], + refreshFunction: () => graphUserRequest.refetch(), + isFetching: graphUserRequest.isFetching, + simpleColumns: ["Address", "Type"], + actions: proxyAddressActions, + offCanvas: { + children: (data) => { + return ( + + ); + }, + }, + }, + }, + ]; + return ( { + { )} + {actionData.ready && ( + + )} ); }; From b0844f7413784605ec3ce8265aa7f631f9e6c0f1 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 28 May 2025 22:59:48 -0400 Subject: [PATCH 09/25] fix lazy loaded dev tools --- src/pages/_app.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/pages/_app.js b/src/pages/_app.js index 5b544487c04d..9302cf5592a9 100644 --- a/src/pages/_app.js +++ b/src/pages/_app.js @@ -34,8 +34,8 @@ import { usePathname } from "next/navigation"; import { useRouter } from "next/router"; import { persistQueryClient } from "@tanstack/react-query-persist-client"; import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister"; + TimeAgo.addDefaultLocale(en); -import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; const queryClient = new QueryClient(); const clientSideEmotionCache = createEmotionCache(); @@ -70,7 +70,7 @@ const App = (props) => { if (!queryKey || !queryKey.length) { return false; } - const queryKeyString = String(queryKey[0] || ''); + const queryKeyString = String(queryKey[0] || ""); const excludeFromPersisting = excludeQueryKeys.some((key) => queryKeyString.includes(key) ); @@ -132,6 +132,12 @@ const App = (props) => { }, ]; + const ReactQueryDevtoolsProduction = React.lazy(() => + import("@tanstack/react-query-devtools/build/modern/production.js").then((d) => ({ + default: d.ReactQueryDevtools, + })) + ); + return ( @@ -178,7 +184,7 @@ const App = (props) => { {settings.isInitialized && settings?.showDevtools === true ? ( - + ) : null} From b27a0db4eff2f4deb70fc68b3b88d93083f4dd60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Thu, 29 May 2025 16:57:57 +0200 Subject: [PATCH 10/25] fix: add missing templates cat --- src/data/standards.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/data/standards.json b/src/data/standards.json index 4dba29061348..3da35ed0e3f2 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -4060,6 +4060,7 @@ { "name": "standards.ExchangeConnectorTemplate", "label": "Exchange Connector Template", + "cat": "Templates", "disabledFeatures": { "report": true, "warn": true, From 0edc9549e5083db2ecc4f704304f0e8cde2cc481 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 29 May 2025 15:35:07 -0400 Subject: [PATCH 11/25] add api offline page --- src/components/PrivateRoute.js | 14 +++ src/pages/api-offline.js | 163 +++++++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 src/pages/api-offline.js diff --git a/src/components/PrivateRoute.js b/src/components/PrivateRoute.js index 3541eeda2d96..c4e2e403fb15 100644 --- a/src/components/PrivateRoute.js +++ b/src/components/PrivateRoute.js @@ -1,6 +1,7 @@ import { ApiGetCall } from "../api/ApiCall.jsx"; import UnauthenticatedPage from "../pages/unauthenticated.js"; import LoadingPage from "../pages/loading.js"; +import ApiOfflinePage from "../pages/api-offline.js"; export const PrivateRoute = ({ children, routeType }) => { const { @@ -12,6 +13,7 @@ export const PrivateRoute = ({ children, routeType }) => { } = ApiGetCall({ url: "/api/me", queryKey: "authmecipp", + retry: 2, // Reduced retry count to show offline message sooner }); const session = ApiGetCall({ @@ -26,6 +28,18 @@ export const PrivateRoute = ({ children, routeType }) => { return ; } + // Check if the API is offline (404 error from /api/me endpoint) + // Or other network errors that would indicate API is unavailable + if ( + error?.response?.status === 404 || // API endpoint not found + error?.response?.status === 502 || // Service unavailable + (error && !error.response) || // Network error (no response) + error?.code === "ECONNABORTED" || // Connection timeout + error?.message?.includes("Network Error") // Generic network error + ) { + return ; + } + // if not logged into swa if (null === session?.data?.clientPrincipal || session?.data === undefined) { return ; diff --git a/src/pages/api-offline.js b/src/pages/api-offline.js new file mode 100644 index 000000000000..d0d089686d07 --- /dev/null +++ b/src/pages/api-offline.js @@ -0,0 +1,163 @@ +import { + Box, + Button, + Container, + Stack, + Alert, + CircularProgress, + Typography, + SvgIcon, +} from "@mui/material"; +import { Grid } from "@mui/system"; +import Head from "next/head"; +import { useState, useEffect } from "react"; +import axios from "axios"; +import { CippImageCard } from "../components/CippCards/CippImageCard"; +import { XMarkIcon } from "@heroicons/react/24/outline"; +import { ErrorOutlineOutlined } from "@mui/icons-material"; + +const ApiOfflinePage = () => { + const [testingConnection, setTestingConnection] = useState(false); + const [testResult, setTestResult] = useState(null); + const [apiVersion, setApiVersion] = useState(null); + + // Check API version when component mounts + useEffect(() => { + const checkApiVersion = async () => { + try { + const response = await axios.get("/version.json", { timeout: 5000 }); + setApiVersion(response.data?.version || "Unknown"); + } catch (error) { + console.error("Failed to fetch API version:", error); + } + }; + + checkApiVersion(); + }, []); + + const handleTestConnection = async () => { + setTestingConnection(true); + setTestResult(null); + + try { + // Try to ping the API + await axios.get("/api/me", { timeout: 45000 }); + setTestResult({ success: true, message: "Connection successful! Try refreshing the page." }); + } catch (error) { + let errorMessage = "Connection failed."; + + if (error.response) { + // Request was made and server responded with a status code outside of 2xx range + errorMessage = `API responded with status: ${error.response.status}`; + if (error.response.status === 404) { + errorMessage += " (API endpoint not found)"; + } + } else if (error.request) { + // Request was made but no response received + errorMessage = "No response received from API. Check if your Function App is running."; + } else { + // Error in setting up the request + errorMessage = `Error: ${error.message}`; + } + + setTestResult({ success: false, message: errorMessage }); + } finally { + setTestingConnection(false); + } + }; + + // We're now using Typography components directly in the JSX + // instead of generating a help text string + + return ( + <> + + API Offline + + + + + + + + + + + CIPP API Unreachable + + } + text={ + + + The CIPP API appears to be offline or out of date. + {apiVersion && ( + + Frontend Version: {apiVersion} + + )} + + + + If you are self-hosting CIPP, please ensure your Function App is running and + up to date. If you are using the hosted version, please check your + subscription in GitHub. + + + } + linkText={testingConnection ? "Testing Connection..." : "Test API Connection"} + onButtonClick={handleTestConnection} + /> + + {testResult && ( + + + {testResult.message} + {testResult.success && ( + + )} + + + )} + + {testingConnection && ( + + + + )} + + + + + + + ); +}; + +export default ApiOfflinePage; From fa5e6341767d52eee9c18ad8196c332338430875 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 29 May 2025 16:07:51 -0400 Subject: [PATCH 12/25] better handling for unauthenticated routes --- src/components/PrivateRoute.js | 8 +-- src/pages/loading.js | 64 ++++++++++++------------ src/pages/unauthenticated.js | 91 ++++++++++++++++------------------ 3 files changed, 80 insertions(+), 83 deletions(-) diff --git a/src/components/PrivateRoute.js b/src/components/PrivateRoute.js index c4e2e403fb15..94338755b23b 100644 --- a/src/components/PrivateRoute.js +++ b/src/components/PrivateRoute.js @@ -10,6 +10,7 @@ export const PrivateRoute = ({ children, routeType }) => { isLoading, isSuccess, refetch, + header, } = ApiGetCall({ url: "/api/me", queryKey: "authmecipp", @@ -35,7 +36,8 @@ export const PrivateRoute = ({ children, routeType }) => { error?.response?.status === 502 || // Service unavailable (error && !error.response) || // Network error (no response) error?.code === "ECONNABORTED" || // Connection timeout - error?.message?.includes("Network Error") // Generic network error + error?.message?.includes("Network Error") || // Generic network error + header?.contentType === "text/html" // If the response is HTML, likely an error page ) { return ; } @@ -60,7 +62,7 @@ export const PrivateRoute = ({ children, routeType }) => { } if (null !== profile?.clientPrincipal && undefined !== profile) { - roles = profile?.clientPrincipal?.userRoles; + roles = profile?.clientPrincipal?.userRoles ?? []; } else if (null === profile?.clientPrincipal || undefined === profile) { return ; } @@ -70,7 +72,7 @@ export const PrivateRoute = ({ children, routeType }) => { const blockedRoles = ["anonymous", "authenticated"]; const userRoles = roles?.filter((role) => !blockedRoles.includes(role)) ?? []; const isAuthenticated = userRoles.length > 0 && !error; - const isAdmin = roles.includes("admin") || roles.includes("superadmin"); + const isAdmin = roles?.includes("admin") || roles?.includes("superadmin"); if (routeType === "admin") { return !isAdmin ? : children; } else { diff --git a/src/pages/loading.js b/src/pages/loading.js index a77fef84904a..930702aadc53 100644 --- a/src/pages/loading.js +++ b/src/pages/loading.js @@ -2,7 +2,7 @@ import { Box, Container, Stack } from "@mui/material"; import { Grid } from "@mui/system"; import Head from "next/head"; import { CippImageCard } from "../components/CippCards/CippImageCard"; -import { Layout as DashboardLayout } from "../layouts/index.js"; + import { ApiGetCall } from "../api/ApiCall"; import { useState, useEffect } from "react"; @@ -32,39 +32,37 @@ const Page = () => { return ( <> - - - Loading - - - - - - - - + + Loading + + + + + + + - - - - + + + + ); }; diff --git a/src/pages/unauthenticated.js b/src/pages/unauthenticated.js index c9105b01fd6f..0c1808459687 100644 --- a/src/pages/unauthenticated.js +++ b/src/pages/unauthenticated.js @@ -2,7 +2,6 @@ import { Box, Container, Stack } from "@mui/material"; import { Grid } from "@mui/system"; import Head from "next/head"; import { CippImageCard } from "../components/CippCards/CippImageCard"; -import { Layout as DashboardLayout } from "../layouts/index.js"; import { ApiGetCall } from "../api/ApiCall"; import { useState, useEffect } from "react"; @@ -32,53 +31,51 @@ const Page = () => { }, [orgData, blockedRoles]); return ( <> - - - 401 - Access Denied - - - - - - - {(orgData.isSuccess || swaStatus.isSuccess) && Array.isArray(userRoles) && ( - 0 - ? "Return to Home" - : "Login" - } - link={ - swaStatus?.data?.clientPrincipal !== null && userRoles.length > 0 - ? "/" - : `/.auth/login/aad?post_login_redirect_uri=${encodeURIComponent( - window.location.href - )}` - } - /> - )} - + + 401 - Access Denied + + + + + + + {(orgData.isSuccess || swaStatus.isSuccess) && Array.isArray(userRoles) && ( + 0 + ? "Return to Home" + : "Login" + } + link={ + swaStatus?.data?.clientPrincipal !== null && userRoles.length > 0 + ? "/" + : `/.auth/login/aad?post_login_redirect_uri=${encodeURIComponent( + window.location.href + )}` + } + /> + )} - - - - + + + + ); }; From c8cf9bef762c54acfa0bd9addce7723cd803ff64 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 29 May 2025 16:23:58 -0400 Subject: [PATCH 13/25] tweak auth checks --- src/components/PrivateRoute.js | 38 ++++++++++++++-------------------- src/pages/api-offline.js | 7 ++++++- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/components/PrivateRoute.js b/src/components/PrivateRoute.js index 94338755b23b..7d68687e374a 100644 --- a/src/components/PrivateRoute.js +++ b/src/components/PrivateRoute.js @@ -4,14 +4,7 @@ import LoadingPage from "../pages/loading.js"; import ApiOfflinePage from "../pages/api-offline.js"; export const PrivateRoute = ({ children, routeType }) => { - const { - data: profile, - error, - isLoading, - isSuccess, - refetch, - header, - } = ApiGetCall({ + const apiRoles = ApiGetCall({ url: "/api/me", queryKey: "authmecipp", retry: 2, // Reduced retry count to show offline message sooner @@ -25,19 +18,18 @@ export const PrivateRoute = ({ children, routeType }) => { }); // Check if the session is still loading before determining authentication status - if (session.isLoading || isLoading) { + if (session.isLoading || apiRoles.isLoading) { return ; } + console.log(apiRoles); + // Check if the API is offline (404 error from /api/me endpoint) // Or other network errors that would indicate API is unavailable if ( - error?.response?.status === 404 || // API endpoint not found - error?.response?.status === 502 || // Service unavailable - (error && !error.response) || // Network error (no response) - error?.code === "ECONNABORTED" || // Connection timeout - error?.message?.includes("Network Error") || // Generic network error - header?.contentType === "text/html" // If the response is HTML, likely an error page + apiRoles?.error?.response?.status === 404 || // API endpoint not found + apiRoles?.error?.response?.status === 502 || // Service unavailable + (apiRoles?.isSuccess && !apiRoles?.data?.clientPrincipal) // No client principal data, indicating API might be offline ) { return ; } @@ -51,19 +43,19 @@ export const PrivateRoute = ({ children, routeType }) => { if ( session?.isSuccess && - isSuccess && - undefined !== profile && + apiRoles?.isSuccess && + undefined !== apiRoles?.data?.clientPrincipal && session?.data?.clientPrincipal?.userDetails && - profile?.userDetails && - session?.data?.clientPrincipal?.userDetails !== profile?.userDetails + apiRoles?.data?.clientPrincipal?.userDetails && + session?.data?.clientPrincipal?.userDetails !== apiRoles?.data?.clientPrincipal?.userDetails ) { // refetch the profile if the user details are different refetch(); } - if (null !== profile?.clientPrincipal && undefined !== profile) { - roles = profile?.clientPrincipal?.userRoles ?? []; - } else if (null === profile?.clientPrincipal || undefined === profile) { + if (null !== apiRoles?.data?.clientPrincipal && undefined !== apiRoles?.data) { + roles = apiRoles?.data?.clientPrincipal?.userRoles ?? []; + } else if (null === apiRoles?.data?.clientPrincipal || undefined === apiRoles?.data) { return ; } if (null === roles) { @@ -71,7 +63,7 @@ export const PrivateRoute = ({ children, routeType }) => { } else { const blockedRoles = ["anonymous", "authenticated"]; const userRoles = roles?.filter((role) => !blockedRoles.includes(role)) ?? []; - const isAuthenticated = userRoles.length > 0 && !error; + const isAuthenticated = userRoles.length > 0 && !apiRoles?.error; const isAdmin = roles?.includes("admin") || roles?.includes("superadmin"); if (routeType === "admin") { return !isAdmin ? : children; diff --git a/src/pages/api-offline.js b/src/pages/api-offline.js index d0d089686d07..d225eff80608 100644 --- a/src/pages/api-offline.js +++ b/src/pages/api-offline.js @@ -20,6 +20,7 @@ const ApiOfflinePage = () => { const [testingConnection, setTestingConnection] = useState(false); const [testResult, setTestResult] = useState(null); const [apiVersion, setApiVersion] = useState(null); + const [contentType, setContentType] = useState(null); // Check API version when component mounts useEffect(() => { @@ -41,7 +42,11 @@ const ApiOfflinePage = () => { try { // Try to ping the API - await axios.get("/api/me", { timeout: 45000 }); + const testCall = await axios.get("/api/me", { timeout: 45000 }); + setContentType(testCall.headers["content-type"]); + if (contentType && !contentType.includes("application/json")) { + throw new Error(`Unexpected content type: ${contentType}. Expected application/json.`); + } setTestResult({ success: true, message: "Connection successful! Try refreshing the page." }); } catch (error) { let errorMessage = "Connection failed."; From 7e6265d5d15309ab6b9e899ed735d5d9f9b39a46 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 29 May 2025 16:35:21 -0400 Subject: [PATCH 14/25] auth tweaks --- src/components/PrivateRoute.js | 2 -- src/pages/api-offline.js | 10 +++++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/PrivateRoute.js b/src/components/PrivateRoute.js index 7d68687e374a..d08c03a7fbf6 100644 --- a/src/components/PrivateRoute.js +++ b/src/components/PrivateRoute.js @@ -22,8 +22,6 @@ export const PrivateRoute = ({ children, routeType }) => { return ; } - console.log(apiRoles); - // Check if the API is offline (404 error from /api/me endpoint) // Or other network errors that would indicate API is unavailable if ( diff --git a/src/pages/api-offline.js b/src/pages/api-offline.js index d225eff80608..5870d6d4aaf6 100644 --- a/src/pages/api-offline.js +++ b/src/pages/api-offline.js @@ -20,7 +20,6 @@ const ApiOfflinePage = () => { const [testingConnection, setTestingConnection] = useState(false); const [testResult, setTestResult] = useState(null); const [apiVersion, setApiVersion] = useState(null); - const [contentType, setContentType] = useState(null); // Check API version when component mounts useEffect(() => { @@ -43,9 +42,9 @@ const ApiOfflinePage = () => { try { // Try to ping the API const testCall = await axios.get("/api/me", { timeout: 45000 }); - setContentType(testCall.headers["content-type"]); - if (contentType && !contentType.includes("application/json")) { - throw new Error(`Unexpected content type: ${contentType}. Expected application/json.`); + console.log("API Test Call Response:", testCall); + if (!testCall.headers["content-type"]?.includes("application/json")) { + throw new Error("API did not return the expected response."); } setTestResult({ success: true, message: "Connection successful! Try refreshing the page." }); } catch (error) { @@ -55,7 +54,8 @@ const ApiOfflinePage = () => { // Request was made and server responded with a status code outside of 2xx range errorMessage = `API responded with status: ${error.response.status}`; if (error.response.status === 404) { - errorMessage += " (API endpoint not found)"; + errorMessage += + " (API endpoint not found, this can be the case if your Function App is on Version 7 or below)"; } } else if (error.request) { // Request was made but no response received From 2d45ca4bbbcc8e89292305b6207a2f89afc7f444 Mon Sep 17 00:00:00 2001 From: Jr7468 Date: Fri, 30 May 2025 00:25:42 +0100 Subject: [PATCH 15/25] Add functionality to add proxy addresses with a dialog interface, including input validation and submission handling. --- .../administration/users/user/exchange.jsx | 117 +++++++++++++++++- 1 file changed, 114 insertions(+), 3 deletions(-) diff --git a/src/pages/identity/administration/users/user/exchange.jsx b/src/pages/identity/administration/users/user/exchange.jsx index 82ecccce694d..9f938ee29e7c 100644 --- a/src/pages/identity/administration/users/user/exchange.jsx +++ b/src/pages/identity/administration/users/user/exchange.jsx @@ -4,7 +4,7 @@ import { useRouter } from "next/router"; import { ApiGetCall } from "/src/api/ApiCall"; import CippFormSkeleton from "/src/components/CippFormPages/CippFormSkeleton"; import CalendarIcon from "@heroicons/react/24/outline/CalendarIcon"; -import { Check, Error, Mail, Fingerprint, Launch, Delete, Star } from "@mui/icons-material"; +import { Check, Error, Mail, Fingerprint, Launch, Delete, Star, Close } from "@mui/icons-material"; import { HeaderedTabbedLayout } from "../../../../../layouts/HeaderedTabbedLayout"; import tabOptions from "./tabOptions"; import { CippTimeAgo } from "../../../../../components/CippComponents/CippTimeAgo"; @@ -16,7 +16,7 @@ import { CippExchangeInfoCard } from "../../../../../components/CippCards/CippEx import { useEffect, useState } from "react"; import CippExchangeSettingsForm from "../../../../../components/CippFormPages/CippExchangeSettingsForm"; import { useForm } from "react-hook-form"; -import { Alert, Button, Collapse, CircularProgress, Typography } from "@mui/material"; +import { Alert, Button, Collapse, CircularProgress, Typography, TextField, Dialog, DialogTitle, DialogContent, DialogActions, IconButton } from "@mui/material"; import { CippApiResults } from "../../../../../components/CippComponents/CippApiResults"; import { Block, PlayArrow } from "@mui/icons-material"; import { CippPropertyListCard } from "../../../../../components/CippCards/CippPropertyListCard"; @@ -31,6 +31,10 @@ const Page = () => { const [waiting, setWaiting] = useState(false); const [showDetails, setShowDetails] = useState(false); const [actionData, setActionData] = useState({ ready: false }); + const [showAddAliasDialog, setShowAddAliasDialog] = useState(false); + const [newAliases, setNewAliases] = useState(''); + const [isSubmitting, setIsSubmitting] = useState(false); + const [submitResult, setSubmitResult] = useState(null); const createDialog = useDialog(); const router = useRouter(); const { userId } = router.query; @@ -322,13 +326,52 @@ const Page = () => { }, ]; + const handleAddAliases = () => { + const aliases = newAliases + .split('\n') + .map(alias => alias.trim()) + .filter(alias => alias); + if (aliases.length > 0) { + setIsSubmitting(true); + setSubmitResult(null); + fetch('/api/SetUserAliases', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + id: userId, + tenantFilter: userSettingsDefaults.currentTenant, + AddedAliases: aliases.join(','), + userPrincipalName: graphUserRequest.data?.[0]?.userPrincipalName, + }), + }) + .then(response => response.json()) + .then(data => { + setSubmitResult({ success: true, message: 'Aliases added successfully' }); + graphUserRequest.refetch(); + setTimeout(() => { + setShowAddAliasDialog(false); + setNewAliases(''); + setSubmitResult(null); + }, 1500); + }) + .catch(error => { + setSubmitResult({ success: false, message: 'Failed to add aliases' }); + }) + .finally(() => { + setIsSubmitting(false); + }); + } + }; + const proxyAddressesCard = [ { id: 1, cardLabelBox: { cardLabelBoxHeader: graphUserRequest.isFetching ? ( - ) : graphUserRequest.data?.[0]?.proxyAddresses?.length !== 0 ? ( + ) : graphUserRequest.data?.[0]?.proxyAddresses?.length > 1 ? ( ) : ( @@ -372,6 +415,19 @@ const Page = () => { }, }, }, + children: ( + + + + ), }, ]; @@ -475,6 +531,61 @@ const Page = () => { row={actionData.data} /> )} + setShowAddAliasDialog(false)} + maxWidth="sm" + fullWidth + > + + + Add Proxy Addresses + setShowAddAliasDialog(false)} size="small"> + + + + + + + setNewAliases(e.target.value)} + placeholder="One alias per line" + variant="outlined" + disabled={isSubmitting} + /> + {submitResult && ( + + {submitResult.message} + + )} + + + + + + + ); }; From b4090ecb18950f1e8bb34ec0b4b0a01c2e2b4b8c Mon Sep 17 00:00:00 2001 From: Zac Richards <107489668+Zacgoose@users.noreply.github.com> Date: Fri, 30 May 2025 09:47:56 +0800 Subject: [PATCH 16/25] Make it possible to ignore disabled apps --- src/data/alerts.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/data/alerts.json b/src/data/alerts.json index e71be4e1547f..a691815fa9bc 100644 --- a/src/data/alerts.json +++ b/src/data/alerts.json @@ -153,6 +153,10 @@ "name": "HuntressRogueApps", "label": "Alert on Huntress Rogue Apps detected", "recommendedRunInterval": "4h", - "description": "Huntress has provided a repository of known rogue apps that are commonly used in BEC, data exfiltration and other Microsoft 365 attacks. This alert will notify you if any of these apps are detected in the selected tenant(s). For more information, see https://huntresslabs.github.io/rogueapps/." + "description": "Huntress has provided a repository of known rogue apps that are commonly used in BEC, data exfiltration and other Microsoft 365 attacks. This alert will notify you if any of these apps are detected in the selected tenant(s). For more information, see https://huntresslabs.github.io/rogueapps/.", + "requiresInput": true, + "inputType": "switch", + "inputLabel": "Ignore Disabled Apps?", + "inputName": "IgnoreDisabledApps" } ] From eb5f4476429b1f0ecb899930d02cb0f764943dae Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 29 May 2025 23:36:11 -0400 Subject: [PATCH 17/25] move react query tools initialization out of App --- src/pages/_app.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/pages/_app.js b/src/pages/_app.js index 9302cf5592a9..2f0a405111d9 100644 --- a/src/pages/_app.js +++ b/src/pages/_app.js @@ -35,6 +35,11 @@ import { useRouter } from "next/router"; import { persistQueryClient } from "@tanstack/react-query-persist-client"; import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister"; +const ReactQueryDevtoolsProduction = React.lazy(() => + import("@tanstack/react-query-devtools/build/modern/production.js").then((d) => ({ + default: d.ReactQueryDevtools, + })) +); TimeAgo.addDefaultLocale(en); const queryClient = new QueryClient(); @@ -132,12 +137,6 @@ const App = (props) => { }, ]; - const ReactQueryDevtoolsProduction = React.lazy(() => - import("@tanstack/react-query-devtools/build/modern/production.js").then((d) => ({ - default: d.ReactQueryDevtools, - })) - ); - return ( From 474fdf4b19b3a7058caf0992055786fbc8a409a8 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 29 May 2025 23:41:57 -0400 Subject: [PATCH 18/25] fix role list --- .../CippIntegrations/CippApiClientManagement.jsx | 12 ++++++------ src/data/Extensions.json | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/CippIntegrations/CippApiClientManagement.jsx b/src/components/CippIntegrations/CippApiClientManagement.jsx index 5e7db0b495d5..740c89658f4a 100644 --- a/src/components/CippIntegrations/CippApiClientManagement.jsx +++ b/src/components/CippIntegrations/CippApiClientManagement.jsx @@ -82,8 +82,8 @@ const CippApiClientManagement = () => { api: { url: "/api/ListCustomRole", queryKey: "CustomRoleList", - labelField: "RowKey", - valueField: "RowKey", + labelField: "RoleName", + valueField: "RoleName", showRefresh: true, }, }, @@ -316,8 +316,8 @@ const CippApiClientManagement = () => { api: { url: "/api/ListCustomRole", queryKey: "CustomRoleList", - labelField: "RowKey", - valueField: "RowKey", + labelField: "RoleName", + valueField: "RoleName", showRefresh: true, }, placeholder: "Choose a role from the Custom Role list.", @@ -384,8 +384,8 @@ const CippApiClientManagement = () => { api: { url: "/api/ListCustomRole", queryKey: "CustomRoleList", - labelField: "RowKey", - valueField: "RowKey", + labelField: "RoleName", + valueField: "RoleName", showRefresh: true, }, }, diff --git a/src/data/Extensions.json b/src/data/Extensions.json index badbfc046cf9..0380de412de6 100644 --- a/src/data/Extensions.json +++ b/src/data/Extensions.json @@ -85,8 +85,8 @@ "api": { "url": "/api/ListCustomRole", "queryKey": "CustomRoles", - "labelField": "RowKey", - "valueField": "RowKey" + "labelField": "RoleName", + "valueField": "RoleName" }, "multiple": true, "condition": { From d00b811905c2f6c023123d6e9b82ab481378f945 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 29 May 2025 23:42:48 -0400 Subject: [PATCH 19/25] update text --- src/components/CippIntegrations/CippApiClientManagement.jsx | 6 +++--- src/data/Extensions.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/CippIntegrations/CippApiClientManagement.jsx b/src/components/CippIntegrations/CippApiClientManagement.jsx index 740c89658f4a..65914ea19dd0 100644 --- a/src/components/CippIntegrations/CippApiClientManagement.jsx +++ b/src/components/CippIntegrations/CippApiClientManagement.jsx @@ -78,7 +78,7 @@ const CippApiClientManagement = () => { multiple: false, creatable: false, label: "Select Role", - placeholder: "Choose a role from the Custom Role list.", + placeholder: "Choose a role from the CIPP Role list.", api: { url: "/api/ListCustomRole", queryKey: "CustomRoleList", @@ -320,7 +320,7 @@ const CippApiClientManagement = () => { valueField: "RoleName", showRefresh: true, }, - placeholder: "Choose a role from the Custom Role list.", + placeholder: "Choose a role from the CIPP Role list.", }, { type: "autoComplete", @@ -380,7 +380,7 @@ const CippApiClientManagement = () => { multiple: false, creatable: false, label: "Select Role", - placeholder: "Choose a role from the Custom Role list.", + placeholder: "Choose a role from the CIPP Role list.", api: { url: "/api/ListCustomRole", queryKey: "CustomRoleList", diff --git a/src/data/Extensions.json b/src/data/Extensions.json index 0380de412de6..4d8bcc01386c 100644 --- a/src/data/Extensions.json +++ b/src/data/Extensions.json @@ -81,7 +81,7 @@ { "type": "autoComplete", "name": "Sherweb.AllowedCustomRoles", - "label": "Select custom roles that are allowed to purchase licenses", + "label": "Select CIPP roles that are allowed to purchase licenses", "api": { "url": "/api/ListCustomRole", "queryKey": "CustomRoles", From d66c6319714828237f85aa1dc228fa4f9e40b217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Fri, 30 May 2025 11:19:22 +0200 Subject: [PATCH 20/25] Chore: Update license files to newest from MS --- src/data/M365Licenses.json | 872 +++++++++++++++++++++++++++++++++++++ 1 file changed, 872 insertions(+) diff --git a/src/data/M365Licenses.json b/src/data/M365Licenses.json index b15c28da101b..d206d18e2f67 100644 --- a/src/data/M365Licenses.json +++ b/src/data/M365Licenses.json @@ -1,4 +1,20 @@ [ + { + "Product_Display_Name": "10-Year Audit Log Retention Add On", + "String_Id": "10_ALR_ADDON", + "GUID": "c2e41e49-e2a2-4c55-832a-cf13ffba1d6a", + "Service_Plan_Name": "Auditing_10Year_ Retention_ Add_On", + "Service_Plan_Id": "7d16094b-4db8-41ff-a182-372a90a85407", + "Service_Plans_Included_Friendly_Names": "Auditing 10Year Retention Add On" + }, + { + "Product_Display_Name": "Advanced Communications", + "String_Id": "ADV_COMMS", + "GUID": "e4654015-5daf-4a48-9b37-4f309dddd88b", + "Service_Plan_Name": "TEAMS_ADVCOMMS", + "Service_Plan_Id": "604ec28a-ae18-4bc6-91b0-11da94504ba9", + "Service_Plans_Included_Friendly_Names": "Microsoft 365 Advanced Communications" + }, { "Product_Display_Name": "AI Builder Capacity add-on", "String_Id": "CDSAICAPACITY", @@ -55,6 +71,30 @@ "Service_Plan_Id": "2e6ffd72-52d1-4541-8f6c-938f9a8d4cdc", "Service_Plans_Included_Friendly_Names": "Microsoft Application Protection and Governance (D)" }, + { + "Product_Display_Name": "Azure Information Protection Premium P1 for Government", + "String_Id": "RIGHTSMANAGEMENT_CE_GOV\t", + "GUID": "78362de1-6942-4bb8-83a1-a32aa67e6e2c", + "Service_Plan_Name": "EXCHANGE_S_FOUNDATION_GOV", + "Service_Plan_Id": "922ba911-5694-4e99-a794-73aed9bfeec8", + "Service_Plans_Included_Friendly_Names": "Exchange Foundation for Government" + }, + { + "Product_Display_Name": "Azure Information Protection Premium P1 for Government", + "String_Id": "RIGHTSMANAGEMENT_CE_GOV\t", + "GUID": "78362de1-6942-4bb8-83a1-a32aa67e6e2c", + "Service_Plan_Name": "RMS_S_PREMIUM_GOV", + "Service_Plan_Id": "1b66aedf-8ca1-4f73-af76-ec76c6180f98", + "Service_Plans_Included_Friendly_Names": "Azure Information Protection Premium P1 for GCC" + }, + { + "Product_Display_Name": "Azure Information Protection Premium P1 for Government", + "String_Id": "RIGHTSMANAGEMENT_CE_GOV\t", + "GUID": "78362de1-6942-4bb8-83a1-a32aa67e6e2c", + "Service_Plan_Name": "RMS_S_ENTERPRISE_GOV", + "Service_Plan_Id": "6a76346d-5d6e-4051-9fe3-ed3f312b5597", + "Service_Plans_Included_Friendly_Names": "Azure Rights Management" + }, { "Product_Display_Name": "Career Coach for faculty", "String_Id": "CAREERCOACH_FACULTY", @@ -1279,6 +1319,30 @@ "Service_Plan_Id": "7e6d7d78-73de-46ba-83b1-6d25117334ba", "Service_Plans_Included_Friendly_Names": "Power Automate for Dynamics 365" }, + { + "Product_Display_Name": "Dynamics 365 Customer Service Voice Channel Add-in", + "String_Id": "DYN365_CS_VOICE", + "GUID": "dadd2312-b5b1-4fa0-8c15-0903de3e2303", + "Service_Plan_Name": "DYN365_CS_VOICE", + "Service_Plan_Id": "f6ec6dfa-2402-468d-a455-89be11116d43", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 for Customer Service Voice Add-in" + }, + { + "Product_Display_Name": "Dynamics 365 Customer Service Voice Channel Add-in", + "String_Id": "DYN365_CS_VOICE", + "GUID": "dadd2312-b5b1-4fa0-8c15-0903de3e2303", + "Service_Plan_Name": "POWER_VIRTUAL_AGENTS_D365_CS_VOICE", + "Service_Plan_Id": "a3dce1be-e9ca-453a-9483-e69a5b46ce98", + "Service_Plans_Included_Friendly_Names": "Power Virtual Agents for Customer Service Voice" + }, + { + "Product_Display_Name": "Dynamics 365 Customer Service Voice Channel Add-in", + "String_Id": "DYN365_CS_VOICE", + "GUID": "dadd2312-b5b1-4fa0-8c15-0903de3e2303", + "Service_Plan_Name": "EXCHANGE_S_FOUNDATION", + "Service_Plan_Id": "113feb6c-3fe4-4440-bddc-54d774bf0318", + "Service_Plans_Included_Friendly_Names": "Exchange Foundation" + }, { "Product_Display_Name": "Dynamics 365 Customer Insights Standalone", "String_Id": "DYN365_CUSTOMER_INSIGHTS_BASE", @@ -1967,6 +2031,118 @@ "Service_Plan_Id": "7e6d7d78-73de-46ba-83b1-6d25117334ba", "Service_Plans_Included_Friendly_Names": "Power Automate for Dynamics 365" }, + { + "Product_Display_Name": "Dynamics 365 for Finance Attach to Qualifying Dynamics 365 Base Offer", + "String_Id": "DYN365_FINANCE_ATTACH", + "GUID": "d721f2e4-099b-4105-b40e-872e46cad402", + "Service_Plan_Name": "Power_Pages_Internal_User", + "Service_Plan_Id": "60bf28f9-2b70-4522-96f7-335f5e06c941", + "Service_Plans_Included_Friendly_Names": "Power Pages Internal User" + }, + { + "Product_Display_Name": "Dynamics 365 for Finance Attach to Qualifying Dynamics 365 Base Offer", + "String_Id": "DYN365_FINANCE_ATTACH", + "GUID": "d721f2e4-099b-4105-b40e-872e46cad402", + "Service_Plan_Name": "CDS_AI_Capacity_FI", + "Service_Plan_Id": "5d85ec34-44e5-43b6-a9aa-d1b4c1d3aa3b", + "Service_Plans_Included_Friendly_Names": "AI Builder Capacity Add-on" + }, + { + "Product_Display_Name": "Dynamics 365 for Finance Attach to Qualifying Dynamics 365 Base Offer", + "String_Id": "DYN365_FINANCE_ATTACH", + "GUID": "d721f2e4-099b-4105-b40e-872e46cad402", + "Service_Plan_Name": "DYN365_CDS_FINANCE", + "Service_Plan_Id": "e95d7060-d4d9-400a-a2bd-a244bf0b609e", + "Service_Plans_Included_Friendly_Names": "Common Data Service for Dynamics 365 Finance" + }, + { + "Product_Display_Name": "Dynamics 365 for Finance Attach to Qualifying Dynamics 365 Base Offer", + "String_Id": "DYN365_FINANCE_ATTACH", + "GUID": "d721f2e4-099b-4105-b40e-872e46cad402", + "Service_Plan_Name": "DYN365_REGULATORY_SERVICE", + "Service_Plan_Id": "c7657ae3-c0b0-4eed-8c1d-6a7967bd9c65", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 for Finance and Operations, Enterprise edition - Regulatory Service" + }, + { + "Product_Display_Name": "Dynamics 365 for Finance Attach to Qualifying Dynamics 365 Base Offer", + "String_Id": "DYN365_FINANCE_ATTACH", + "GUID": "d721f2e4-099b-4105-b40e-872e46cad402", + "Service_Plan_Name": "D365_Finance_Attach", + "Service_Plan_Id": "223e33cb-eee0-462d-b1bd-e9a5febf8e85", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 for Finance Attach" + }, + { + "Product_Display_Name": "Dynamics 365 for Finance Attach to Qualifying Dynamics 365 Base Offer", + "String_Id": "DYN365_FINANCE_ATTACH", + "GUID": "d721f2e4-099b-4105-b40e-872e46cad402", + "Service_Plan_Name": "EXCHANGE_S_FOUNDATION", + "Service_Plan_Id": "113feb6c-3fe4-4440-bddc-54d774bf0318", + "Service_Plans_Included_Friendly_Names": "Exchange Foundation" + }, + { + "Product_Display_Name": "Dynamics 365 Finance Attach to Qualifying Base Offer Embedded with Project Management & Accounting", + "String_Id": "DYN365_FINANCE_ATTACH_ISVEMB_PROJOPS", + "GUID": "db5bd06c-b99a-4c54-98e9-90fea5164c88", + "Service_Plan_Name": "D365_ProjectOperationsCDSAttach", + "Service_Plan_Id": "e564d403-7eaf-4c91-b92f-bb0dc62026e1", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 Project Operations CDS Attach" + }, + { + "Product_Display_Name": "Dynamics 365 Finance Attach to Qualifying Base Offer Embedded with Project Management & Accounting", + "String_Id": "DYN365_FINANCE_ATTACH_ISVEMB_PROJOPS", + "GUID": "db5bd06c-b99a-4c54-98e9-90fea5164c88", + "Service_Plan_Name": "Power_Pages_Internal_User", + "Service_Plan_Id": "60bf28f9-2b70-4522-96f7-335f5e06c941", + "Service_Plans_Included_Friendly_Names": "Power Pages Internal User" + }, + { + "Product_Display_Name": "Dynamics 365 Finance Attach to Qualifying Base Offer Embedded with Project Management & Accounting", + "String_Id": "DYN365_FINANCE_ATTACH_ISVEMB_PROJOPS", + "GUID": "db5bd06c-b99a-4c54-98e9-90fea5164c88", + "Service_Plan_Name": "CDS_AI_Capacity_FI", + "Service_Plan_Id": "5d85ec34-44e5-43b6-a9aa-d1b4c1d3aa3b", + "Service_Plans_Included_Friendly_Names": "AI Builder Capacity Add-on" + }, + { + "Product_Display_Name": "Dynamics 365 Finance Attach to Qualifying Base Offer Embedded with Project Management & Accounting", + "String_Id": "DYN365_FINANCE_ATTACH_ISVEMB_PROJOPS", + "GUID": "db5bd06c-b99a-4c54-98e9-90fea5164c88", + "Service_Plan_Name": "D365_Finance_Attach", + "Service_Plan_Id": "223e33cb-eee0-462d-b1bd-e9a5febf8e85", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 for Finance Attach" + }, + { + "Product_Display_Name": "Dynamics 365 Finance Attach to Qualifying Base Offer Embedded with Project Management & Accounting", + "String_Id": "DYN365_FINANCE_ATTACH_ISVEMB_PROJOPS", + "GUID": "db5bd06c-b99a-4c54-98e9-90fea5164c88", + "Service_Plan_Name": "D365_ProjectOperationsAttach", + "Service_Plan_Id": "fa7675bd-6717-40e7-8172-d0bbcbe1ab12", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 Project Operations Attach" + }, + { + "Product_Display_Name": "Dynamics 365 Finance Attach to Qualifying Base Offer Embedded with Project Management & Accounting", + "String_Id": "DYN365_FINANCE_ATTACH_ISVEMB_PROJOPS", + "GUID": "db5bd06c-b99a-4c54-98e9-90fea5164c88", + "Service_Plan_Name": "EXCHANGE_S_FOUNDATION", + "Service_Plan_Id": "113feb6c-3fe4-4440-bddc-54d774bf0318", + "Service_Plans_Included_Friendly_Names": "Exchange Foundation" + }, + { + "Product_Display_Name": "Dynamics 365 Finance Attach to Qualifying Base Offer Embedded with Project Management & Accounting", + "String_Id": "DYN365_FINANCE_ATTACH_ISVEMB_PROJOPS", + "GUID": "db5bd06c-b99a-4c54-98e9-90fea5164c88", + "Service_Plan_Name": "PROJECT_FOR_PROJECT_OPERATIONS_ATTACH", + "Service_Plan_Id": "6d8e07c6-9613-484f-8cc1-a66c5c3979bb", + "Service_Plans_Included_Friendly_Names": "Project for Project Operations Attach" + }, + { + "Product_Display_Name": "Dynamics 365 Finance Attach to Qualifying Base Offer Embedded with Project Management & Accounting", + "String_Id": "DYN365_FINANCE_ATTACH_ISVEMB_PROJOPS", + "GUID": "db5bd06c-b99a-4c54-98e9-90fea5164c88", + "Service_Plan_Name": "SHAREPOINTSTANDARD", + "Service_Plan_Id": "c7699d2e-19aa-44de-8edf-1736da088ca1", + "Service_Plans_Included_Friendly_Names": "SharePoint (Plan 1)" + }, { "Product_Display_Name": "Dynamics 365 for Case Management Enterprise Edition", "String_Id": "DYN365_ENTERPRISE_CASE_MANAGEMENT", @@ -2263,6 +2439,454 @@ "Service_Plan_Id": "7e6d7d78-73de-46ba-83b1-6d25117334ba", "Service_Plans_Included_Friendly_Names": "Power Automate for Dynamics 365" }, + { + "Product_Display_Name": "Dynamics 365 Contact Center", + "String_Id": "DYNAMICS_365_CONTACT_CENTER", + "GUID": "dfb1700c-013e-4132-8bce-0d319c43a95d", + "Service_Plan_Name": "DYN365_CC", + "Service_Plan_Id": "2a9d72b3-1714-440f-babf-bf92bf9683d8", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 Contact Center" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center", + "String_Id": "DYNAMICS_365_CONTACT_CENTER", + "GUID": "dfb1700c-013e-4132-8bce-0d319c43a95d", + "Service_Plan_Name": "DYN365_CS_MESSAGING_TPS", + "Service_Plan_Id": "47c2b191-a5fb-4129-b690-00c474d2f623", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 Customer Service Digital Messaging add-on" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center", + "String_Id": "DYNAMICS_365_CONTACT_CENTER", + "GUID": "dfb1700c-013e-4132-8bce-0d319c43a95d", + "Service_Plan_Name": "DYN365_CS_VOICE", + "Service_Plan_Id": "f6ec6dfa-2402-468d-a455-89be11116d43", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 for Customer Service Voice Add-in" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center", + "String_Id": "DYNAMICS_365_CONTACT_CENTER", + "GUID": "dfb1700c-013e-4132-8bce-0d319c43a95d", + "Service_Plan_Name": "Power_Pages_Internal_User", + "Service_Plan_Id": "60bf28f9-2b70-4522-96f7-335f5e06c941", + "Service_Plans_Included_Friendly_Names": "Power Pages Internal User" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center", + "String_Id": "DYNAMICS_365_CONTACT_CENTER", + "GUID": "dfb1700c-013e-4132-8bce-0d319c43a95d", + "Service_Plan_Name": "POWER_VIRTUAL_AGENTS_D365_CS_VOICE", + "Service_Plan_Id": "a3dce1be-e9ca-453a-9483-e69a5b46ce98", + "Service_Plans_Included_Friendly_Names": "Power Virtual Agents for Customer Service Voice" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center", + "String_Id": "DYNAMICS_365_CONTACT_CENTER", + "GUID": "dfb1700c-013e-4132-8bce-0d319c43a95d", + "Service_Plan_Name": "EXCHANGE_S_FOUNDATION", + "Service_Plan_Id": "113feb6c-3fe4-4440-bddc-54d774bf0318", + "Service_Plans_Included_Friendly_Names": "Exchange Foundation" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center", + "String_Id": "DYNAMICS_365_CONTACT_CENTER", + "GUID": "dfb1700c-013e-4132-8bce-0d319c43a95d", + "Service_Plan_Name": "SHAREPOINTWAC", + "Service_Plan_Id": "e95bec33-7c88-4a70-8e19-b10bd9d0c014", + "Service_Plans_Included_Friendly_Names": "Office for the Web" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center", + "String_Id": "DYNAMICS_365_CONTACT_CENTER", + "GUID": "dfb1700c-013e-4132-8bce-0d319c43a95d", + "Service_Plan_Name": "SHAREPOINTENTERPRISE", + "Service_Plan_Id": "5dbe027f-2339-4123-9542-606e4d348a72", + "Service_Plans_Included_Friendly_Names": "SharePoint (Plan 2)" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center", + "String_Id": "DYNAMICS_365_CONTACT_CENTER", + "GUID": "dfb1700c-013e-4132-8bce-0d319c43a95d", + "Service_Plan_Name": "POWERAPPS_DYN_APPS", + "Service_Plan_Id": "874fc546-6efe-4d22-90b8-5c4e7aa59f4b", + "Service_Plans_Included_Friendly_Names": "Power Apps for Dynamics 365" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center", + "String_Id": "DYNAMICS_365_CONTACT_CENTER", + "GUID": "dfb1700c-013e-4132-8bce-0d319c43a95d", + "Service_Plan_Name": "FLOW_DYN_APPS", + "Service_Plan_Id": "7e6d7d78-73de-46ba-83b1-6d25117334ba", + "Service_Plans_Included_Friendly_Names": "Power Automate for Dynamics 365" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Add-on for Customer Service Enterprise", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_ADD_ON_FOR_CUSTOMER_SERVICE_ENTERPRISE", + "GUID": "39a78eb6-3a8a-4e1e-878a-575a5c8984e7", + "Service_Plan_Name": "DYN365_CC", + "Service_Plan_Id": "2a9d72b3-1714-440f-babf-bf92bf9683d8", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 Contact Center" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Add-on for Customer Service Enterprise", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_ADD_ON_FOR_CUSTOMER_SERVICE_ENTERPRISE", + "GUID": "39a78eb6-3a8a-4e1e-878a-575a5c8984e7", + "Service_Plan_Name": "DYN365_CS_MESSAGING_TPS", + "Service_Plan_Id": "47c2b191-a5fb-4129-b690-00c474d2f623", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 Customer Service Digital Messaging add-on" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Add-on for Customer Service Enterprise", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_ADD_ON_FOR_CUSTOMER_SERVICE_ENTERPRISE", + "GUID": "39a78eb6-3a8a-4e1e-878a-575a5c8984e7", + "Service_Plan_Name": "DYN365_CS_VOICE", + "Service_Plan_Id": "f6ec6dfa-2402-468d-a455-89be11116d43", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 for Customer Service Voice Add-in" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Add-on for Customer Service Enterprise", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_ADD_ON_FOR_CUSTOMER_SERVICE_ENTERPRISE", + "GUID": "39a78eb6-3a8a-4e1e-878a-575a5c8984e7", + "Service_Plan_Name": "Power_Pages_Internal_User", + "Service_Plan_Id": "60bf28f9-2b70-4522-96f7-335f5e06c941", + "Service_Plans_Included_Friendly_Names": "Power Pages Internal User" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Add-on for Customer Service Enterprise", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_ADD_ON_FOR_CUSTOMER_SERVICE_ENTERPRISE", + "GUID": "39a78eb6-3a8a-4e1e-878a-575a5c8984e7", + "Service_Plan_Name": "POWER_VIRTUAL_AGENTS_D365_CS_VOICE", + "Service_Plan_Id": "a3dce1be-e9ca-453a-9483-e69a5b46ce98", + "Service_Plans_Included_Friendly_Names": "Power Virtual Agents for Customer Service Voice" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Add-on for Customer Service Enterprise", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_ADD_ON_FOR_CUSTOMER_SERVICE_ENTERPRISE", + "GUID": "39a78eb6-3a8a-4e1e-878a-575a5c8984e7", + "Service_Plan_Name": "EXCHANGE_S_FOUNDATION", + "Service_Plan_Id": "113feb6c-3fe4-4440-bddc-54d774bf0318", + "Service_Plans_Included_Friendly_Names": "Exchange Foundation" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Add-on for Customer Service Enterprise", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_ADD_ON_FOR_CUSTOMER_SERVICE_ENTERPRISE", + "GUID": "39a78eb6-3a8a-4e1e-878a-575a5c8984e7", + "Service_Plan_Name": "SHAREPOINTWAC", + "Service_Plan_Id": "e95bec33-7c88-4a70-8e19-b10bd9d0c014", + "Service_Plans_Included_Friendly_Names": "Office for the Web" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Add-on for Customer Service Enterprise", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_ADD_ON_FOR_CUSTOMER_SERVICE_ENTERPRISE", + "GUID": "39a78eb6-3a8a-4e1e-878a-575a5c8984e7", + "Service_Plan_Name": "SHAREPOINTENTERPRISE", + "Service_Plan_Id": "5dbe027f-2339-4123-9542-606e4d348a72", + "Service_Plans_Included_Friendly_Names": "SharePoint (Plan 2)" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Add-on for Customer Service Enterprise", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_ADD_ON_FOR_CUSTOMER_SERVICE_ENTERPRISE", + "GUID": "39a78eb6-3a8a-4e1e-878a-575a5c8984e7", + "Service_Plan_Name": "POWERAPPS_DYN_APPS", + "Service_Plan_Id": "874fc546-6efe-4d22-90b8-5c4e7aa59f4b", + "Service_Plans_Included_Friendly_Names": "Power Apps for Dynamics 365" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Add-on for Customer Service Enterprise", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_ADD_ON_FOR_CUSTOMER_SERVICE_ENTERPRISE", + "GUID": "39a78eb6-3a8a-4e1e-878a-575a5c8984e7", + "Service_Plan_Name": "FLOW_DYN_APPS", + "Service_Plan_Id": "7e6d7d78-73de-46ba-83b1-6d25117334ba", + "Service_Plans_Included_Friendly_Names": "Power Automate for Dynamics 365" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Digital", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_DIGITAL", + "GUID": "59d3d0bf-df39-4b8b-8601-ea6c09a7fd66", + "Service_Plan_Name": "DYN365_CC_DIGITAL", + "Service_Plan_Id": "0ef2b4e3-0a2b-450d-8c5f-a52203c40f50", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 Contact Center Digital" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Digital", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_DIGITAL", + "GUID": "59d3d0bf-df39-4b8b-8601-ea6c09a7fd66", + "Service_Plan_Name": "DYN365_CS_MESSAGING_TPS", + "Service_Plan_Id": "47c2b191-a5fb-4129-b690-00c474d2f623", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 Customer Service Digital Messaging add-on" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Digital", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_DIGITAL", + "GUID": "59d3d0bf-df39-4b8b-8601-ea6c09a7fd66", + "Service_Plan_Name": "DYN365_CS_MESSAGING", + "Service_Plan_Id": "43b076f2-1123-45ba-a339-2e170ee58c53", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 Customer Service Digital Messaging Application Integration" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Digital", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_DIGITAL", + "GUID": "59d3d0bf-df39-4b8b-8601-ea6c09a7fd66", + "Service_Plan_Name": "Power_Pages_Internal_User", + "Service_Plan_Id": "60bf28f9-2b70-4522-96f7-335f5e06c941", + "Service_Plans_Included_Friendly_Names": "Power Pages Internal User" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Digital", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_DIGITAL", + "GUID": "59d3d0bf-df39-4b8b-8601-ea6c09a7fd66", + "Service_Plan_Name": "EXCHANGE_S_FOUNDATION", + "Service_Plan_Id": "113feb6c-3fe4-4440-bddc-54d774bf0318", + "Service_Plans_Included_Friendly_Names": "Exchange Foundation" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Digital", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_DIGITAL", + "GUID": "59d3d0bf-df39-4b8b-8601-ea6c09a7fd66", + "Service_Plan_Name": "SHAREPOINTWAC", + "Service_Plan_Id": "e95bec33-7c88-4a70-8e19-b10bd9d0c014", + "Service_Plans_Included_Friendly_Names": "Office for the Web" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Digital", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_DIGITAL", + "GUID": "59d3d0bf-df39-4b8b-8601-ea6c09a7fd66", + "Service_Plan_Name": "SHAREPOINTENTERPRISE", + "Service_Plan_Id": "5dbe027f-2339-4123-9542-606e4d348a72", + "Service_Plans_Included_Friendly_Names": "SharePoint (Plan 2)" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Digital", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_DIGITAL", + "GUID": "59d3d0bf-df39-4b8b-8601-ea6c09a7fd66", + "Service_Plan_Name": "POWERAPPS_DYN_APPS", + "Service_Plan_Id": "874fc546-6efe-4d22-90b8-5c4e7aa59f4b", + "Service_Plans_Included_Friendly_Names": "Power Apps for Dynamics 365" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Digital", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_DIGITAL", + "GUID": "59d3d0bf-df39-4b8b-8601-ea6c09a7fd66", + "Service_Plan_Name": "FLOW_DYN_APPS", + "Service_Plan_Id": "7e6d7d78-73de-46ba-83b1-6d25117334ba", + "Service_Plans_Included_Friendly_Names": "Power Automate for Dynamics 365" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Digital Add-on for Customer Service Enterprise", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_DIGITAL_ADD_ON_FOR_CUSTOMER_SERVICE_ENTERPRISE", + "GUID": "7e6e6091-1680-4532-9370-6cd4598483ac", + "Service_Plan_Name": "DYN365_CC_DIGITAL", + "Service_Plan_Id": "0ef2b4e3-0a2b-450d-8c5f-a52203c40f50", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 Contact Center Digital" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Digital Add-on for Customer Service Enterprise", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_DIGITAL_ADD_ON_FOR_CUSTOMER_SERVICE_ENTERPRISE", + "GUID": "7e6e6091-1680-4532-9370-6cd4598483ac", + "Service_Plan_Name": "DYN365_CS_MESSAGING_TPS", + "Service_Plan_Id": "47c2b191-a5fb-4129-b690-00c474d2f623", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 Customer Service Digital Messaging add-on" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Digital Add-on for Customer Service Enterprise", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_DIGITAL_ADD_ON_FOR_CUSTOMER_SERVICE_ENTERPRISE", + "GUID": "7e6e6091-1680-4532-9370-6cd4598483ac", + "Service_Plan_Name": "DYN365_CS_MESSAGING", + "Service_Plan_Id": "43b076f2-1123-45ba-a339-2e170ee58c53", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 Customer Service Digital Messaging Application Integration" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Digital Add-on for Customer Service Enterprise", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_DIGITAL_ADD_ON_FOR_CUSTOMER_SERVICE_ENTERPRISE", + "GUID": "7e6e6091-1680-4532-9370-6cd4598483ac", + "Service_Plan_Name": "Power_Pages_Internal_User", + "Service_Plan_Id": "60bf28f9-2b70-4522-96f7-335f5e06c941", + "Service_Plans_Included_Friendly_Names": "Power Pages Internal User" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Digital Add-on for Customer Service Enterprise", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_DIGITAL_ADD_ON_FOR_CUSTOMER_SERVICE_ENTERPRISE", + "GUID": "7e6e6091-1680-4532-9370-6cd4598483ac", + "Service_Plan_Name": "EXCHANGE_S_FOUNDATION", + "Service_Plan_Id": "113feb6c-3fe4-4440-bddc-54d774bf0318", + "Service_Plans_Included_Friendly_Names": "Exchange Foundation" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Digital Add-on for Customer Service Enterprise", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_DIGITAL_ADD_ON_FOR_CUSTOMER_SERVICE_ENTERPRISE", + "GUID": "7e6e6091-1680-4532-9370-6cd4598483ac", + "Service_Plan_Name": "SHAREPOINTWAC", + "Service_Plan_Id": "e95bec33-7c88-4a70-8e19-b10bd9d0c014", + "Service_Plans_Included_Friendly_Names": "Office for the Web" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Digital Add-on for Customer Service Enterprise", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_DIGITAL_ADD_ON_FOR_CUSTOMER_SERVICE_ENTERPRISE", + "GUID": "7e6e6091-1680-4532-9370-6cd4598483ac", + "Service_Plan_Name": "SHAREPOINTENTERPRISE", + "Service_Plan_Id": "5dbe027f-2339-4123-9542-606e4d348a72", + "Service_Plans_Included_Friendly_Names": "SharePoint (Plan 2)" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Digital Add-on for Customer Service Enterprise", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_DIGITAL_ADD_ON_FOR_CUSTOMER_SERVICE_ENTERPRISE", + "GUID": "7e6e6091-1680-4532-9370-6cd4598483ac", + "Service_Plan_Name": "POWERAPPS_DYN_APPS", + "Service_Plan_Id": "874fc546-6efe-4d22-90b8-5c4e7aa59f4b", + "Service_Plans_Included_Friendly_Names": "Power Apps for Dynamics 365" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Digital Add-on for Customer Service Enterprise", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_DIGITAL_ADD_ON_FOR_CUSTOMER_SERVICE_ENTERPRISE", + "GUID": "7e6e6091-1680-4532-9370-6cd4598483ac", + "Service_Plan_Name": "FLOW_DYN_APPS", + "Service_Plan_Id": "7e6d7d78-73de-46ba-83b1-6d25117334ba", + "Service_Plans_Included_Friendly_Names": "Power Automate for Dynamics 365" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Voice", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_VOICE", + "GUID": "79e2368c-4568-48d5-a352-b0344afabcf8", + "Service_Plan_Name": "DYN365_CC_VOICE", + "Service_Plan_Id": "57517633-b4ad-4db8-8c1a-65f443424490", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 Contact Center Voice" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Voice", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_VOICE", + "GUID": "79e2368c-4568-48d5-a352-b0344afabcf8", + "Service_Plan_Name": "DYN365_CS_VOICE", + "Service_Plan_Id": "f6ec6dfa-2402-468d-a455-89be11116d43", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 for Customer Service Voice Add-in" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Voice", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_VOICE", + "GUID": "79e2368c-4568-48d5-a352-b0344afabcf8", + "Service_Plan_Name": "Power_Pages_Internal_User", + "Service_Plan_Id": "60bf28f9-2b70-4522-96f7-335f5e06c941", + "Service_Plans_Included_Friendly_Names": "Power Pages Internal User" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Voice", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_VOICE", + "GUID": "79e2368c-4568-48d5-a352-b0344afabcf8", + "Service_Plan_Name": "POWER_VIRTUAL_AGENTS_D365_CS_VOICE", + "Service_Plan_Id": "a3dce1be-e9ca-453a-9483-e69a5b46ce98", + "Service_Plans_Included_Friendly_Names": "Power Virtual Agents for Customer Service Voice" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Voice", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_VOICE", + "GUID": "79e2368c-4568-48d5-a352-b0344afabcf8", + "Service_Plan_Name": "EXCHANGE_S_FOUNDATION", + "Service_Plan_Id": "113feb6c-3fe4-4440-bddc-54d774bf0318", + "Service_Plans_Included_Friendly_Names": "Exchange Foundation" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Voice", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_VOICE", + "GUID": "79e2368c-4568-48d5-a352-b0344afabcf8", + "Service_Plan_Name": "SHAREPOINTWAC", + "Service_Plan_Id": "e95bec33-7c88-4a70-8e19-b10bd9d0c014", + "Service_Plans_Included_Friendly_Names": "Office for the Web" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Voice", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_VOICE", + "GUID": "79e2368c-4568-48d5-a352-b0344afabcf8", + "Service_Plan_Name": "SHAREPOINTENTERPRISE", + "Service_Plan_Id": "5dbe027f-2339-4123-9542-606e4d348a72", + "Service_Plans_Included_Friendly_Names": "SharePoint (Plan 2)" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Voice", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_VOICE", + "GUID": "79e2368c-4568-48d5-a352-b0344afabcf8", + "Service_Plan_Name": "POWERAPPS_DYN_APPS", + "Service_Plan_Id": "874fc546-6efe-4d22-90b8-5c4e7aa59f4b", + "Service_Plans_Included_Friendly_Names": "Power Apps for Dynamics 365" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Voice", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_VOICE", + "GUID": "79e2368c-4568-48d5-a352-b0344afabcf8", + "Service_Plan_Name": "FLOW_DYN_APPS", + "Service_Plan_Id": "7e6d7d78-73de-46ba-83b1-6d25117334ba", + "Service_Plans_Included_Friendly_Names": "Power Automate for Dynamics 365" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Voice Add-on for Customer Service Enterprise", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_VOICE_ADD_ON_FOR_CUSTOMER_SERVICE_ENTERPRISE", + "GUID": "73e8b747-20bf-463d-8ffd-274a7d65d0bc", + "Service_Plan_Name": "DYN365_CC_VOICE", + "Service_Plan_Id": "57517633-b4ad-4db8-8c1a-65f443424490", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 Contact Center Voice" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Voice Add-on for Customer Service Enterprise", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_VOICE_ADD_ON_FOR_CUSTOMER_SERVICE_ENTERPRISE", + "GUID": "73e8b747-20bf-463d-8ffd-274a7d65d0bc", + "Service_Plan_Name": "DYN365_CS_VOICE", + "Service_Plan_Id": "f6ec6dfa-2402-468d-a455-89be11116d43", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 for Customer Service Voice Add-in" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Voice Add-on for Customer Service Enterprise", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_VOICE_ADD_ON_FOR_CUSTOMER_SERVICE_ENTERPRISE", + "GUID": "73e8b747-20bf-463d-8ffd-274a7d65d0bc", + "Service_Plan_Name": "Power_Pages_Internal_User", + "Service_Plan_Id": "60bf28f9-2b70-4522-96f7-335f5e06c941", + "Service_Plans_Included_Friendly_Names": "Power Pages Internal User" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Voice Add-on for Customer Service Enterprise", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_VOICE_ADD_ON_FOR_CUSTOMER_SERVICE_ENTERPRISE", + "GUID": "73e8b747-20bf-463d-8ffd-274a7d65d0bc", + "Service_Plan_Name": "POWER_VIRTUAL_AGENTS_D365_CS_VOICE", + "Service_Plan_Id": "a3dce1be-e9ca-453a-9483-e69a5b46ce98", + "Service_Plans_Included_Friendly_Names": "Power Virtual Agents for Customer Service Voice" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Voice Add-on for Customer Service Enterprise", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_VOICE_ADD_ON_FOR_CUSTOMER_SERVICE_ENTERPRISE", + "GUID": "73e8b747-20bf-463d-8ffd-274a7d65d0bc", + "Service_Plan_Name": "EXCHANGE_S_FOUNDATION", + "Service_Plan_Id": "113feb6c-3fe4-4440-bddc-54d774bf0318", + "Service_Plans_Included_Friendly_Names": "Exchange Foundation" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Voice Add-on for Customer Service Enterprise", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_VOICE_ADD_ON_FOR_CUSTOMER_SERVICE_ENTERPRISE", + "GUID": "73e8b747-20bf-463d-8ffd-274a7d65d0bc", + "Service_Plan_Name": "SHAREPOINTWAC", + "Service_Plan_Id": "e95bec33-7c88-4a70-8e19-b10bd9d0c014", + "Service_Plans_Included_Friendly_Names": "Office for the Web" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Voice Add-on for Customer Service Enterprise", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_VOICE_ADD_ON_FOR_CUSTOMER_SERVICE_ENTERPRISE", + "GUID": "73e8b747-20bf-463d-8ffd-274a7d65d0bc", + "Service_Plan_Name": "SHAREPOINTENTERPRISE", + "Service_Plan_Id": "5dbe027f-2339-4123-9542-606e4d348a72", + "Service_Plans_Included_Friendly_Names": "SharePoint (Plan 2)" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Voice Add-on for Customer Service Enterprise", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_VOICE_ADD_ON_FOR_CUSTOMER_SERVICE_ENTERPRISE", + "GUID": "73e8b747-20bf-463d-8ffd-274a7d65d0bc", + "Service_Plan_Name": "POWERAPPS_DYN_APPS", + "Service_Plan_Id": "874fc546-6efe-4d22-90b8-5c4e7aa59f4b", + "Service_Plans_Included_Friendly_Names": "Power Apps for Dynamics 365" + }, + { + "Product_Display_Name": "Dynamics 365 Contact Center Voice Add-on for Customer Service Enterprise", + "String_Id": "DYNAMICS_365_CONTACT_CENTER_VOICE_ADD_ON_FOR_CUSTOMER_SERVICE_ENTERPRISE", + "GUID": "73e8b747-20bf-463d-8ffd-274a7d65d0bc", + "Service_Plan_Name": "FLOW_DYN_APPS", + "Service_Plan_Id": "7e6d7d78-73de-46ba-83b1-6d25117334ba", + "Service_Plans_Included_Friendly_Names": "Power Automate for Dynamics 365" + }, { "Product_Display_Name": "Dynamics 365 for Customer Service Chat", "String_Id": "DYN365_CS_CHAT", @@ -3215,6 +3839,78 @@ "Service_Plan_Id": "3089c02b-e533-4b73-96a5-01fa648c3c3c", "Service_Plans_Included_Friendly_Names": "PowerApps for Dynamics 365 for Government" }, + { + "Product_Display_Name": "Dynamics 365 for Project Service Automation for Government", + "String_Id": "D365_ENTERPRISE_PROJECT_SERVICE_AUTOMATION_GOV", + "GUID": "6c827f0a-42cb-4cff-b1cd-f4104c16ede3", + "Service_Plan_Name": "DYN365_ENTERPRISE_PROJECT_SERVICE_AUTOMATION_GOV", + "Service_Plan_Id": "1d8c8e0e-4308-4db5-8a41-b129dbdaea20", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 for Project Service Automation for Government" + }, + { + "Product_Display_Name": "Dynamics 365 for Project Service Automation for Government", + "String_Id": "D365_ENTERPRISE_PROJECT_SERVICE_AUTOMATION_GOV", + "GUID": "6c827f0a-42cb-4cff-b1cd-f4104c16ede3", + "Service_Plan_Name": "Forms_Pro_PS_GCC", + "Service_Plan_Id": "e98256c5-17d0-4987-becc-e991c52d55c6", + "Service_Plans_Included_Friendly_Names": "Microsoft Dynamics 365 Customer Voice for Project Service Automation for GCC" + }, + { + "Product_Display_Name": "Dynamics 365 for Project Service Automation for Government", + "String_Id": "D365_ENTERPRISE_PROJECT_SERVICE_AUTOMATION_GOV", + "GUID": "6c827f0a-42cb-4cff-b1cd-f4104c16ede3", + "Service_Plan_Name": "EXCHANGE_S_FOUNDATION_GOV", + "Service_Plan_Id": "922ba911-5694-4e99-a794-73aed9bfeec8", + "Service_Plans_Included_Friendly_Names": "Exchange Foundation for Government" + }, + { + "Product_Display_Name": "Dynamics 365 for Project Service Automation for Government", + "String_Id": "D365_ENTERPRISE_PROJECT_SERVICE_AUTOMATION_GOV", + "GUID": "6c827f0a-42cb-4cff-b1cd-f4104c16ede3", + "Service_Plan_Name": "SHAREPOINTWAC_GOV", + "Service_Plan_Id": "8f9f0f3b-ca90-406c-a842-95579171f8ec", + "Service_Plans_Included_Friendly_Names": "Office for the Web for Government" + }, + { + "Product_Display_Name": "Dynamics 365 for Project Service Automation for Government", + "String_Id": "D365_ENTERPRISE_PROJECT_SERVICE_AUTOMATION_GOV", + "GUID": "6c827f0a-42cb-4cff-b1cd-f4104c16ede3", + "Service_Plan_Name": "PROJECT_CLIENT_SUBSCRIPTION_GOV", + "Service_Plan_Id": "45c6831b-ad74-4c7f-bd03-7c2b3fa39067", + "Service_Plans_Included_Friendly_Names": "Project Online Desktop Client" + }, + { + "Product_Display_Name": "Dynamics 365 for Project Service Automation for Government", + "String_Id": "D365_ENTERPRISE_PROJECT_SERVICE_AUTOMATION_GOV", + "GUID": "6c827f0a-42cb-4cff-b1cd-f4104c16ede3", + "Service_Plan_Name": "SHAREPOINT_PROJECT_GOV", + "Service_Plan_Id": "e57afa78-1f19-4542-ba13-b32cd4d8f472", + "Service_Plans_Included_Friendly_Names": "Project Online Service for Government" + }, + { + "Product_Display_Name": "Dynamics 365 for Project Service Automation for Government", + "String_Id": "D365_ENTERPRISE_PROJECT_SERVICE_AUTOMATION_GOV", + "GUID": "6c827f0a-42cb-4cff-b1cd-f4104c16ede3", + "Service_Plan_Name": "SHAREPOINTENTERPRISE_GOV", + "Service_Plan_Id": "153f85dd-d912-4762-af6c-d6e0fb4f6692", + "Service_Plans_Included_Friendly_Names": "SharePoint Plan 2G" + }, + { + "Product_Display_Name": "Dynamics 365 for Project Service Automation for Government", + "String_Id": "D365_ENTERPRISE_PROJECT_SERVICE_AUTOMATION_GOV", + "GUID": "6c827f0a-42cb-4cff-b1cd-f4104c16ede3", + "Service_Plan_Name": "FLOW_DYN_APPS_GOV", + "Service_Plan_Id": "2c6af4f1-e7b6-4d59-bbc8-eaa884f42d69", + "Service_Plans_Included_Friendly_Names": "Power Automate for Dynamics 365 for Government" + }, + { + "Product_Display_Name": "Dynamics 365 for Project Service Automation for Government", + "String_Id": "D365_ENTERPRISE_PROJECT_SERVICE_AUTOMATION_GOV", + "GUID": "6c827f0a-42cb-4cff-b1cd-f4104c16ede3", + "Service_Plan_Name": "POWERAPPS_DYN_APPS_GOV", + "Service_Plan_Id": "3089c02b-e533-4b73-96a5-01fa648c3c3c", + "Service_Plans_Included_Friendly_Names": "PowerApps for Dynamics 365 for Government" + }, { "Product_Display_Name": "Dynamics 365 for Sales and Customer Service Enterprise Edition", "String_Id": "DYN365_ENTERPRISE_SALES_CUSTOMERSERVICE", @@ -3927,6 +4623,110 @@ "Service_Plan_Id": "874fc546-6efe-4d22-90b8-5c4e7aa59f4b", "Service_Plans_Included_Friendly_Names": "POWERAPPS FOR DYNAMICS 365" }, + { + "Product_Display_Name": "Dynamics 365 Supply Chain Management Premium", + "String_Id": "Dynamics_365_Supply_Chain_Management_Premium", + "GUID": "9467fd84-2758-4287-b1fa-6a908c441b8a", + "Service_Plan_Name": "Power_Pages_Internal_User", + "Service_Plan_Id": "60bf28f9-2b70-4522-96f7-335f5e06c941", + "Service_Plans_Included_Friendly_Names": "Power Pages Internal User" + }, + { + "Product_Display_Name": "Dynamics 365 Supply Chain Management Premium", + "String_Id": "Dynamics_365_Supply_Chain_Management_Premium", + "GUID": "9467fd84-2758-4287-b1fa-6a908c441b8a", + "Service_Plan_Name": "DYN365_CDS_SUPPLYCHAINMANAGEMENT", + "Service_Plan_Id": "b6a8b974-2956-4e14-ae81-f0384c363528", + "Service_Plans_Included_Friendly_Names": "Common Data Service for Dynamics 365 Supply Chain Management" + }, + { + "Product_Display_Name": "Dynamics 365 Supply Chain Management Premium", + "String_Id": "Dynamics_365_Supply_Chain_Management_Premium", + "GUID": "9467fd84-2758-4287-b1fa-6a908c441b8a", + "Service_Plan_Name": "FLOW_FOR_IOM_USL", + "Service_Plan_Id": "9e6d1620-dce9-4655-8933-af8fa5bccc9c", + "Service_Plans_Included_Friendly_Names": "Data Integration for IOM with Power Automate USL" + }, + { + "Product_Display_Name": "Dynamics 365 Supply Chain Management Premium", + "String_Id": "Dynamics_365_Supply_Chain_Management_Premium", + "GUID": "9467fd84-2758-4287-b1fa-6a908c441b8a", + "Service_Plan_Name": "CDS_FOR_IOM", + "Service_Plan_Id": "2bb89402-51e9-4c5a-be33-e954a9dd1ba6", + "Service_Plans_Included_Friendly_Names": "Dataverse for IOM" + }, + { + "Product_Display_Name": "Dynamics 365 Supply Chain Management Premium", + "String_Id": "Dynamics_365_Supply_Chain_Management_Premium", + "GUID": "9467fd84-2758-4287-b1fa-6a908c441b8a", + "Service_Plan_Name": "D365_DemandPlanning", + "Service_Plan_Id": "e8b616eb-1a6d-42b4-84c7-b63870791349", + "Service_Plans_Included_Friendly_Names": "DO NOT USE - Dynamics 365 Supply Chain Management Premium" + }, + { + "Product_Display_Name": "Dynamics 365 Supply Chain Management Premium", + "String_Id": "Dynamics_365_Supply_Chain_Management_Premium", + "GUID": "9467fd84-2758-4287-b1fa-6a908c441b8a", + "Service_Plan_Name": "DYN365_REGULATORY_SERVICE", + "Service_Plan_Id": "c7657ae3-c0b0-4eed-8c1d-6a7967bd9c65", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 for Finance and Operations, Enterprise edition - Regulatory Service" + }, + { + "Product_Display_Name": "Dynamics 365 Supply Chain Management Premium", + "String_Id": "Dynamics_365_Supply_Chain_Management_Premium", + "GUID": "9467fd84-2758-4287-b1fa-6a908c441b8a", + "Service_Plan_Name": "D365_SCM", + "Service_Plan_Id": "1224eae4-0d91-474a-8a52-27ec96a63fe7", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 for Supply Chain Management" + }, + { + "Product_Display_Name": "Dynamics 365 Supply Chain Management Premium", + "String_Id": "Dynamics_365_Supply_Chain_Management_Premium", + "GUID": "9467fd84-2758-4287-b1fa-6a908c441b8a", + "Service_Plan_Name": "DYN365_IOM", + "Service_Plan_Id": "616cf6e2-f52f-4738-b463-10003061fcd3", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 Intelligent Order Management" + }, + { + "Product_Display_Name": "Dynamics 365 Supply Chain Management Premium", + "String_Id": "Dynamics_365_Supply_Chain_Management_Premium", + "GUID": "9467fd84-2758-4287-b1fa-6a908c441b8a", + "Service_Plan_Name": "DYN365_IOM_USER", + "Service_Plan_Id": "81375e2f-5ef7-4773-96aa-e3279f50bd21", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 Intelligent Order Management USL" + }, + { + "Product_Display_Name": "Dynamics 365 Supply Chain Management Premium", + "String_Id": "Dynamics_365_Supply_Chain_Management_Premium", + "GUID": "9467fd84-2758-4287-b1fa-6a908c441b8a", + "Service_Plan_Name": "D365_SCM_Premium", + "Service_Plan_Id": "0363c8e5-c30d-4d7c-a621-7b6cab5e0482", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 Supply Chain Management Premium" + }, + { + "Product_Display_Name": "Dynamics 365 Supply Chain Management Premium", + "String_Id": "Dynamics_365_Supply_Chain_Management_Premium", + "GUID": "9467fd84-2758-4287-b1fa-6a908c441b8a", + "Service_Plan_Name": "POWERAPPS_DYN_APPS", + "Service_Plan_Id": "874fc546-6efe-4d22-90b8-5c4e7aa59f4b", + "Service_Plans_Included_Friendly_Names": "Power Apps for Dynamics 365" + }, + { + "Product_Display_Name": "Dynamics 365 Supply Chain Management Premium", + "String_Id": "Dynamics_365_Supply_Chain_Management_Premium", + "GUID": "9467fd84-2758-4287-b1fa-6a908c441b8a", + "Service_Plan_Name": "EXCHANGE_S_FOUNDATION", + "Service_Plan_Id": "113feb6c-3fe4-4440-bddc-54d774bf0318", + "Service_Plans_Included_Friendly_Names": "Exchange Foundation" + }, + { + "Product_Display_Name": "Dynamics 365 Supply Chain Management Premium", + "String_Id": "Dynamics_365_Supply_Chain_Management_Premium", + "GUID": "9467fd84-2758-4287-b1fa-6a908c441b8a", + "Service_Plan_Name": "FLOW_DYN_APPS", + "Service_Plan_Id": "7e6d7d78-73de-46ba-83b1-6d25117334ba", + "Service_Plans_Included_Friendly_Names": "Power Automate for Dynamics 365" + }, { "Product_Display_Name": "Dynamics 365 for Talent", "String_Id": "SKU_Dynamics_365_for_HCM_Trial", @@ -5263,6 +6063,22 @@ "Service_Plan_Id": "882e1d05-acd1-4ccb-8708-6ee03664b117", "Service_Plans_Included_Friendly_Names": "Mobile Device Management for Office 365" }, + { + "Product_Display_Name": "Exchange Online (Plan 2) for GCC", + "String_Id": "EXCHANGEENTERPRISE_GOV", + "GUID": "7be8dc28-4da4-4e6d-b9b9-c60f2806df8a", + "Service_Plan_Name": "EXCHANGE_S_ENTERPRISE_GOV", + "Service_Plan_Id": "8c3069c0-ccdb-44be-ab77-986203a67df2", + "Service_Plans_Included_Friendly_Names": "Exchange Online (Plan 2) for Government" + }, + { + "Product_Display_Name": "Exchange Online (Plan 2) for GCC", + "String_Id": "EXCHANGEENTERPRISE_GOV", + "GUID": "7be8dc28-4da4-4e6d-b9b9-c60f2806df8a", + "Service_Plan_Name": "INTUNE_O365", + "Service_Plan_Id": "882e1d05-acd1-4ccb-8708-6ee03664b117", + "Service_Plans_Included_Friendly_Names": "Mobile Device Management for Office 365" + }, { "Product_Display_Name": "Exchange Online (Plan 2)", "String_Id": "EXCHANGEENTERPRISE", @@ -5367,6 +6183,30 @@ "Service_Plan_Id": "326e2b78-9d27-42c9-8509-46c827743a17", "Service_Plans_Included_Friendly_Names": "Exchange Online Protection" }, + { + "Product_Display_Name": "Flow Plan 1 for Government", + "String_Id": "FLOW_P1_GOV", + "GUID": "2b3b0c87-36af-4d15-8124-04a691cc2546", + "Service_Plan_Name": "EXCHANGE_S_FOUNDATION_GOV", + "Service_Plan_Id": "922ba911-5694-4e99-a794-73aed9bfeec8", + "Service_Plans_Included_Friendly_Names": "Exchange Foundation for Government" + }, + { + "Product_Display_Name": "Flow Plan 1 for Government", + "String_Id": "FLOW_P1_GOV", + "GUID": "2b3b0c87-36af-4d15-8124-04a691cc2546", + "Service_Plan_Name": "DYN365_CDS_P1_GOV", + "Service_Plan_Id": "ce361df2-f2a5-4713-953f-4050ba09aad8", + "Service_Plans_Included_Friendly_Names": "Common Data Service for Government" + }, + { + "Product_Display_Name": "Flow Plan 1 for Government", + "String_Id": "FLOW_P1_GOV", + "GUID": "2b3b0c87-36af-4d15-8124-04a691cc2546", + "Service_Plan_Name": "FLOW_P1_GOV", + "Service_Plan_Id": "774da41c-a8b3-47c1-8322-b9c1ab68be9f", + "Service_Plans_Included_Friendly_Names": "Power Automate (Plan 1) for Government" + }, { "Product_Display_Name": "Intune", "String_Id": "INTUNE_A", @@ -28839,6 +29679,22 @@ "Service_Plan_Id": "346d83bf-6fe6-42ca-b424-b9300d2e21bf", "Service_Plans_Included_Friendly_Names": "Microsoft 365 Domestic Calling Plan (240 min)" }, + { + "Product_Display_Name": "Microsoft Teams Domestic Calling Plan for GCC", + "String_Id": "MCOPSTN_1_GOV", + "GUID": "923f58ab-fca1-46a1-92f9-89fda21238a8", + "Service_Plan_Name": "MCOPSTN1_GOV", + "Service_Plan_Id": "3c8a8792-7866-409b-bb61-1b20ace0368b", + "Service_Plans_Included_Friendly_Names": "Domestic Calling Plan for Government" + }, + { + "Product_Display_Name": "Microsoft Teams Domestic Calling Plan for GCC", + "String_Id": "MCOPSTN_1_GOV", + "GUID": "923f58ab-fca1-46a1-92f9-89fda21238a8", + "Service_Plan_Name": "EXCHANGE_S_FOUNDATION_GOV", + "Service_Plan_Id": "922ba911-5694-4e99-a794-73aed9bfeec8", + "Service_Plans_Included_Friendly_Names": "Exchange Foundation for Government" + }, { "Product_Display_Name": "Microsoft Teams Essentials", "String_Id": "Teams_Ess", @@ -40407,6 +41263,22 @@ "Service_Plan_Id": "18e74ca2-b5f0-4802-9a8b-00d2ff1e8322", "Service_Plans_Included_Friendly_Names": "Power Pages Authenticated Users per site monthly capacity GCCH" }, + { + "Product_Display_Name": "Power Pages authenticated users T1 100 users/per site/month capacity pack CN_CN", + "String_Id": "Power Pages authenticated users T1_CN_CN", + "GUID": "9a3c2a19-06c0-41b1-b2ea-13528d7b2e17", + "Service_Plan_Name": "DV_PowerPages_Authenticated_User", + "Service_Plan_Id": "7aae746a-3463-4737-b295-3c1a16c31438", + "Service_Plans_Included_Friendly_Names": "Dataverse for Power Pages Authenticated users per site" + }, + { + "Product_Display_Name": "Power Pages authenticated users T1 100 users/per site/month capacity pack CN_CN", + "String_Id": "Power Pages authenticated users T1_CN_CN", + "GUID": "9a3c2a19-06c0-41b1-b2ea-13528d7b2e17", + "Service_Plan_Name": "PowerPages_Authenticated_User_CN", + "Service_Plan_Id": "967d9574-a076-4bb7-ab89-f41f64bc142e", + "Service_Plans_Included_Friendly_Names": "Power Pages Authenticated Users per site monthly capacity China" + }, { "Product_Display_Name": "Power Pages authenticated users T1 100 users/per site/month capacity pack_GCC", "String_Id": "Power_Pages_authenticated_users_T1_100_users/per_site/month_capacity_pack_GCC", From df2c31e7bd91916b6393f7b34cc901c3ca7cdfe9 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 30 May 2025 09:17:56 -0400 Subject: [PATCH 21/25] fix domain card grid size for search box --- src/components/CippCards/CippDomainCards.jsx | 32 ++++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/components/CippCards/CippDomainCards.jsx b/src/components/CippCards/CippDomainCards.jsx index 93bcc3cd7104..3aa81e3019fe 100644 --- a/src/components/CippCards/CippDomainCards.jsx +++ b/src/components/CippCards/CippDomainCards.jsx @@ -152,7 +152,7 @@ function DomainResultCard({ title, data, isFetching, info, type }) { ? { children: ( - + {info} @@ -182,7 +182,7 @@ function DomainResultCard({ title, data, isFetching, info, type }) { ? { children: ( //4 headers, "Record" and then under it. - (<> + <> Record: @@ -237,7 +237,7 @@ function DomainResultCard({ title, data, isFetching, info, type }) { value: email, }))} /> - ) + ), } : type === "SPF" @@ -361,7 +361,7 @@ function DomainResultCard({ title, data, isFetching, info, type }) { } isFetching={isFetching} > - + {info} setVisible(false)} {...offCanvasData} /> @@ -483,7 +483,7 @@ export const CippDomainCards = ({ domain: propDomain = "", fullwidth = false }) return (
- + - + - + @@ -562,7 +562,7 @@ export const CippDomainCards = ({ domain: propDomain = "", fullwidth = false }) {domain && ( <> - + - + - + - + - + - + - + - + {enableHttps && ( - + Date: Fri, 30 May 2025 11:17:57 -0400 Subject: [PATCH 22/25] fix scripted alert exclusions --- src/pages/tenant/administration/alert-configuration/alert.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/tenant/administration/alert-configuration/alert.jsx b/src/pages/tenant/administration/alert-configuration/alert.jsx index 050a30ef4612..be33d8615a31 100644 --- a/src/pages/tenant/administration/alert-configuration/alert.jsx +++ b/src/pages/tenant/administration/alert-configuration/alert.jsx @@ -561,7 +561,7 @@ const AlertWizard = () => { From 0de30ce06ef6507a1e765fa3b01a5c203fdef2be Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 30 May 2025 12:36:11 -0400 Subject: [PATCH 23/25] json view improvements implements #4096 --- src/components/CippFormPages/CippJSONView.jsx | 344 +++++++++++++++--- .../tenant/conditional/list-policies/index.js | 4 +- 2 files changed, 306 insertions(+), 42 deletions(-) diff --git a/src/components/CippFormPages/CippJSONView.jsx b/src/components/CippFormPages/CippJSONView.jsx index 0866cdc13b2c..93bf975518ec 100644 --- a/src/components/CippFormPages/CippJSONView.jsx +++ b/src/components/CippFormPages/CippJSONView.jsx @@ -6,6 +6,9 @@ import { IconButton, Typography, Button, + Tooltip, + CircularProgress, + Stack, } from "@mui/material"; import { Grid } from "@mui/system"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; @@ -17,6 +20,8 @@ import { getCippTranslation } from "../../utils/get-cipp-translation"; import { getCippFormatting } from "../../utils/get-cipp-formatting"; import { CippCodeBlock } from "../CippComponents/CippCodeBlock"; import intuneCollection from "/src/data/intuneCollection.json"; +import { ApiPostCall } from "../../api/ApiCall"; +import { useSettings } from "../../hooks/use-settings"; const cleanObject = (obj) => { if (Array.isArray(obj)) { @@ -37,7 +42,29 @@ const cleanObject = (obj) => { } }; -const renderListItems = (data, onItemClick) => { +// Function to check if a string is a GUID +const isGuid = (str) => { + if (typeof str !== "string") return false; + const guidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; + return guidRegex.test(str); +}; + +// Function to recursively scan an object for GUIDs +const findGuids = (obj, guidsSet = new Set()) => { + if (!obj) return guidsSet; + + if (typeof obj === "string" && isGuid(obj)) { + guidsSet.add(obj); + } else if (Array.isArray(obj)) { + obj.forEach((item) => findGuids(item, guidsSet)); + } else if (typeof obj === "object") { + Object.values(obj).forEach((value) => findGuids(value, guidsSet)); + } + + return guidsSet; +}; + +const renderListItems = (data, onItemClick, guidMapping = {}, isLoadingGuids = false) => { return Object.entries(data).map(([key, value]) => { if (Array.isArray(value)) { return ( @@ -64,6 +91,31 @@ const renderListItems = (data, onItemClick) => { } /> ); + } else if (typeof value === "string" && isGuid(value) && guidMapping[value]) { + return ( + +
{guidMapping[value]}
+ + } + /> + ); + } else if (typeof value === "string" && isGuid(value) && isLoadingGuids) { + return ( + + + {getCippFormatting(value, key)} + + } + /> + ); } else { return ( { + if (data && Array.isArray(data.value)) { + const newMapping = {}; + data.value.forEach((item) => { + if (item.id && (item.displayName || item.userPrincipalName || item.mail)) { + // Prefer displayName, fallback to UPN or mail if available + newMapping[item.id] = item.displayName || item.userPrincipalName || item.mail; + } + }); + setGuidMapping((prevMapping) => ({ ...prevMapping, ...newMapping })); + setPendingGuids([]); + setIsLoadingGuids(false); + } + }, + }); + + // Function to handle resolving GUIDs - used in both useEffect and handleItemClick + const resolveGuids = (objectToScan) => { + const guidsSet = findGuids(objectToScan); + + if (guidsSet.size === 0) return; + + const guidsArray = Array.from(guidsSet); + const notResolvedGuids = guidsArray.filter((guid) => !guidMapping[guid]); + + if (notResolvedGuids.length === 0) return; + + // Merge with any pending GUIDs to avoid duplicate requests + const allPendingGuids = [...new Set([...pendingGuids, ...notResolvedGuids])]; + setPendingGuids(allPendingGuids); + setIsLoadingGuids(true); + + // Implement throttling - only send a new request every 2 seconds + const now = Date.now(); + if (now - lastRequestTime < 2000) { + return; + } + + setLastRequestTime(now); + + // Only send a maximum of 1000 GUIDs per request + const batchSize = 1000; + const guidsToSend = allPendingGuids.slice(0, batchSize); + + if (guidsToSend.length > 0) { + directoryObjectsMutation.mutate({ + url: "/api/ListDirectoryObjects", + data: { + tenantFilter: tenantFilter, + ids: guidsToSend, + $select: "id,displayName,userPrincipalName,mail", + }, + }); + } else { + setIsLoadingGuids(false); + } + }; const renderIntuneItems = (data) => { const items = []; @@ -96,11 +214,31 @@ function CippJsonView({ if (data.omaSettings) { data.omaSettings.forEach((omaSetting, index) => { + // Check if value is a GUID that we've resolved + const value = + typeof omaSetting.value === "string" && + isGuid(omaSetting.value) && + guidMapping[omaSetting.value] ? ( + +
+ {guidMapping[omaSetting.value]} + + (GUID) + +
+
+ ) : ( + omaSetting.value + ); + items.push( ); }); @@ -144,13 +282,50 @@ function CippJsonView({ } else if (settingInstance?.simpleSettingValue?.value) { const label = intuneObj?.displayName || settingInstance.settingDefinitionId; const value = settingInstance.simpleSettingValue.value; - items.push(); + // Check if value is a GUID that we've resolved + const displayValue = + typeof value === "string" && isGuid(value) && guidMapping[value] ? ( + +
+ {guidMapping[value]} + + (GUID) + +
+
+ ) : ( + value + ); + + items.push( + + ); } else if (settingInstance?.choiceSettingValue?.value) { const label = intuneObj?.displayName || settingInstance.settingDefinitionId; - const optionValue = - intuneObj?.options?.find( - (option) => option.id === settingInstance.choiceSettingValue.value - )?.displayName || settingInstance.choiceSettingValue.value; + const rawValue = settingInstance.choiceSettingValue.value; + let optionValue = + intuneObj?.options?.find((option) => option.id === rawValue)?.displayName || rawValue; + + // Check if optionValue is a GUID that we've resolved + if (typeof optionValue === "string" && isGuid(optionValue) && guidMapping[optionValue]) { + optionValue = ( + +
+ {guidMapping[optionValue]} + + (GUID) + +
+
+ ); + } + items.push( ); @@ -175,13 +350,49 @@ function CippJsonView({ ); } else { Object.entries(data).forEach(([key, value]) => { - items.push( - - ); + // Check if value is a GUID that we've resolved + if (typeof value === "string" && isGuid(value) && guidMapping[value]) { + items.push( + +
+ {guidMapping[value]} + + (GUID) + +
+ + } + /> + ); + } else if (typeof value === "string" && isGuid(value) && isLoadingGuids) { + items.push( + + + {getCippFormatting(value, key)} + + } + /> + ); + } else { + items.push( + + ); + } }); } @@ -206,7 +417,41 @@ function CippJsonView({ Object.entries(cleanedObj).filter(([key]) => !blacklist.includes(key)) ); setDrilldownData([filteredObj]); - }, [object]); + + // Using the centralized resolveGuids function to handle GUID resolution + resolveGuids(cleanedObj); + }, [object, tenantFilter]); + + // Effect to reprocess any pending GUIDs when the guidMapping changes or throttling window passes + useEffect(() => { + if (pendingGuids.length > 0 && !isLoadingGuids) { + const now = Date.now(); + if (now - lastRequestTime >= 2000) { + // Only send a maximum of 1000 GUIDs per request + const batchSize = 1000; + const guidsToSend = pendingGuids.slice(0, batchSize); + + setLastRequestTime(now); + setIsLoadingGuids(true); + + directoryObjectsMutation.mutate({ + url: "/api/ListDirectoryObjects", + data: { + tenantFilter: tenantFilter, + ids: guidsToSend, + $select: "id,displayName,userPrincipalName,mail", + }, + }); + } + } + }, [ + guidMapping, + pendingGuids, + lastRequestTime, + isLoadingGuids, + directoryObjectsMutation, + tenantFilter, + ]); const toggleView = () => setViewJson(!viewJson); @@ -214,6 +459,9 @@ function CippJsonView({ const updatedData = drilldownData.slice(0, level + 1); updatedData[level + 1] = itemData; setDrilldownData(updatedData); + + // Use the centralized resolveGuids function to handle GUID resolution for drill-down data + resolveGuids(itemData); }; return ( @@ -226,9 +474,16 @@ function CippJsonView({ expandIcon={} sx={{ display: "flex", alignItems: "center" }} > - - Policy Details - + + + Policy Details + + {isLoadingGuids && ( + + Resolving object identifiers... + + )} + @@ -238,29 +493,36 @@ function CippJsonView({ ) : ( - {drilldownData?.map((data, index) => ( - 4, and add spacing between the top and bottom items - paddingTop: index === 0 ? 0 : 2, - borderTop: index >= 4 && type !== "intune" ? "1px solid lightgrey" : "none", - borderRight: index < drilldownData.length - 1 ? "1px solid lightgrey" : "none", - overflowWrap: "anywhere", - whiteSpace: "pre-line", - paddingRight: 2, - }} - > - {type !== "intune" && ( - - {renderListItems(data, (itemData) => handleItemClick(itemData, index))} - - )} - {type === "intune" && {renderIntuneItems(data)}} - - ))} + {drilldownData + ?.filter((data) => data !== null && data !== undefined) + .map((data, index) => ( + 4, and add spacing between the top and bottom items + paddingTop: index === 0 ? 0 : 2, + borderTop: index >= 4 && type !== "intune" ? "1px solid lightgrey" : "none", + borderRight: index < drilldownData.length - 1 ? "1px solid lightgrey" : "none", + overflowWrap: "anywhere", + whiteSpace: "pre-line", + paddingRight: 2, + }} + > + {type !== "intune" && ( + + {renderListItems( + data, + (itemData) => handleItemClick(itemData, index), + guidMapping, + isLoadingGuids + )} + + )} + {type === "intune" && {renderIntuneItems(data)}} + + ))} )} diff --git a/src/pages/tenant/conditional/list-policies/index.js b/src/pages/tenant/conditional/list-policies/index.js index 8c021df61319..2c4a30d8b06f 100644 --- a/src/pages/tenant/conditional/list-policies/index.js +++ b/src/pages/tenant/conditional/list-policies/index.js @@ -84,7 +84,9 @@ const Page = () => { // Off-canvas configuration const offCanvas = { - children: (row) => , + children: (row) => ( + + ), size: "xl", }; From 7492d9a7ddc354c6acd65fbf331271003d72c5b5 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 30 May 2025 12:50:41 -0400 Subject: [PATCH 24/25] prevent loops on unresolved guids --- src/components/CippFormPages/CippJSONView.jsx | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/components/CippFormPages/CippJSONView.jsx b/src/components/CippFormPages/CippJSONView.jsx index 93bf975518ec..e590f3ae2ed6 100644 --- a/src/components/CippFormPages/CippJSONView.jsx +++ b/src/components/CippFormPages/CippJSONView.jsx @@ -137,6 +137,7 @@ function CippJsonView({ const [accordionOpen, setAccordionOpen] = useState(defaultOpen); const [drilldownData, setDrilldownData] = useState([]); const [guidMapping, setGuidMapping] = useState({}); + const [notFoundGuids, setNotFoundGuids] = useState(new Set()); const [isLoadingGuids, setIsLoadingGuids] = useState(false); const [pendingGuids, setPendingGuids] = useState([]); const [lastRequestTime, setLastRequestTime] = useState(0); @@ -148,12 +149,29 @@ function CippJsonView({ onResult: (data) => { if (data && Array.isArray(data.value)) { const newMapping = {}; + + // Process the returned results data.value.forEach((item) => { if (item.id && (item.displayName || item.userPrincipalName || item.mail)) { // Prefer displayName, fallback to UPN or mail if available newMapping[item.id] = item.displayName || item.userPrincipalName || item.mail; } }); + + // Find GUIDs that were sent but not returned in the response + const processedGuids = new Set(pendingGuids); + const returnedGuids = new Set(data.value.map((item) => item.id)); + const notReturned = [...processedGuids].filter((guid) => !returnedGuids.has(guid)); + + // Add them to the notFoundGuids set + if (notReturned.length > 0) { + setNotFoundGuids((prev) => { + const newSet = new Set(prev); + notReturned.forEach((guid) => newSet.add(guid)); + return newSet; + }); + } + setGuidMapping((prevMapping) => ({ ...prevMapping, ...newMapping })); setPendingGuids([]); setIsLoadingGuids(false); @@ -168,7 +186,10 @@ function CippJsonView({ if (guidsSet.size === 0) return; const guidsArray = Array.from(guidsSet); - const notResolvedGuids = guidsArray.filter((guid) => !guidMapping[guid]); + // Filter out GUIDs that are already resolved or known to not be resolvable + const notResolvedGuids = guidsArray.filter( + (guid) => !guidMapping[guid] && !notFoundGuids.has(guid) + ); if (notResolvedGuids.length === 0) return; @@ -446,6 +467,7 @@ function CippJsonView({ } }, [ guidMapping, + notFoundGuids, pendingGuids, lastRequestTime, isLoadingGuids, From 5b80306c001a9d56923d484608bc968e8cfefacb Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 30 May 2025 13:41:55 -0400 Subject: [PATCH 25/25] up version --- public/version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/version.json b/public/version.json index fb57e409303d..d18f79dee972 100644 --- a/public/version.json +++ b/public/version.json @@ -1,3 +1,3 @@ { - "version": "8.0.0" + "version": "8.0.1" }