From a4817308aa58c95a1907d96bbd15e22e99ba74aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Wed, 18 Jun 2025 21:03:12 +0200 Subject: [PATCH 001/112] Feat: Add button to create new room list in Room Lists page remove divider comment Enhance room list form with default values and reset functionality change shown stuff to reflect the backend too Refactor CippAddRoomListForm: Remove description field and divider for a cleaner layout Enhance Room Lists Page: Add delete functionality for room lists with confirmation prompt --- .../CippFormPages/CippAddRoomListForm.jsx | 54 +++++++++++++++++ .../resources/management/room-lists/add.jsx | 50 ++++++++++++++++ .../resources/management/room-lists/index.js | 58 ++++++++++++++----- 3 files changed, 146 insertions(+), 16 deletions(-) create mode 100644 src/components/CippFormPages/CippAddRoomListForm.jsx create mode 100644 src/pages/email/resources/management/room-lists/add.jsx diff --git a/src/components/CippFormPages/CippAddRoomListForm.jsx b/src/components/CippFormPages/CippAddRoomListForm.jsx new file mode 100644 index 000000000000..958244dd0b44 --- /dev/null +++ b/src/components/CippFormPages/CippAddRoomListForm.jsx @@ -0,0 +1,54 @@ +import { InputAdornment, Divider } from "@mui/material"; +import { Grid } from "@mui/system"; +import { CippFormComponent } from "../CippComponents/CippFormComponent"; +import { CippFormDomainSelector } from "../CippComponents/CippFormDomainSelector"; + +const CippAddRoomListForm = ({ formControl }) => { + + return ( + + + + + + + + @, + }} + /> + + + + + + + + + ); +}; + +export default CippAddRoomListForm; \ No newline at end of file diff --git a/src/pages/email/resources/management/room-lists/add.jsx b/src/pages/email/resources/management/room-lists/add.jsx new file mode 100644 index 000000000000..606259d728e0 --- /dev/null +++ b/src/pages/email/resources/management/room-lists/add.jsx @@ -0,0 +1,50 @@ +import { Box } from "@mui/material"; +import CippFormPage from "../../../../../components/CippFormPages/CippFormPage"; +import { Layout as DashboardLayout } from "/src/layouts/index.js"; +import { useForm } from "react-hook-form"; +import { useSettings } from "../../../../../hooks/use-settings"; +import CippAddRoomListForm from "../../../../../components/CippFormPages/CippAddRoomListForm"; + +const Page = () => { + const userSettingsDefaults = useSettings(); + const tenantDomain = userSettingsDefaults.currentTenant; + + const formControl = useForm({ + mode: "onChange", + defaultValues: { + displayName: "", + username: "", + primDomain: null, + }, + }); + + return ( + <> + { + return { + tenantFilter: tenantDomain, + displayName: values.displayName?.trim(), + username: values.username?.trim(), + primDomain: values.primDomain, + }; + }} + > + + + + + + ); +}; + +Page.getLayout = (page) => {page}; + +export default Page; \ No newline at end of file diff --git a/src/pages/email/resources/management/room-lists/index.js b/src/pages/email/resources/management/room-lists/index.js index e73dcc50490e..b9049767c4b9 100644 --- a/src/pages/email/resources/management/room-lists/index.js +++ b/src/pages/email/resources/management/room-lists/index.js @@ -1,6 +1,9 @@ import { Layout as DashboardLayout } from "/src/layouts/index.js"; import { CippTablePage } from "/src/components/CippComponents/CippTablePage.jsx"; -import { Visibility } from "@mui/icons-material"; +import { Visibility, ListAlt } from "@mui/icons-material"; +import { TrashIcon } from "@heroicons/react/24/outline"; +import { Button } from "@mui/material"; +import Link from "next/link"; const Page = () => { const pageTitle = "Room Lists"; @@ -9,32 +12,44 @@ const Page = () => { const actions = [ { label: "View included Rooms", - link: `/email/resources/management/room-lists/list/view?roomAddress=[emailAddress]`, + link: `/email/resources/management/room-lists/list/view?roomAddress=[PrimarySmtpAddress]`, color: "info", icon: , }, + { + label: "Delete Room List", + type: "POST", + url: "/api/ExecGroupsDelete", + icon: , + data: { + id: "Guid", + displayName: "DisplayName", + GroupType: "calculatedGroupType", + }, + confirmText: "Are you sure you want to delete this room list?", + multiPost: false, + }, ]; const offCanvas = { extendedInfoFields: [ - "id", - "emailAddress", - "displayName", - "phone", - "placeId", - "geoCoordinates", - "address.city", - "address.countryOrRegion", + "ExternalDirectoryObjectId", + "PrimarySmtpAddress", + "DisplayName", + "Phone", + "Identity", + "Notes", + "MailNickname", ], actions: actions, }; const simpleColumns = [ - "displayName", - "geoCoordinates", - "placeId", - "address.city", - "address.countryOrRegion", + "DisplayName", + "PrimarySmtpAddress", + "Identity", + "Phone", + "Notes", ]; return ( @@ -42,9 +57,20 @@ const Page = () => { title={pageTitle} apiUrl={apiUrl} actions={actions} - apiDataKey="ListRoomListsResults" + apiDataKey="" offCanvas={offCanvas} simpleColumns={simpleColumns} + cardButton={ + <> + + + } /> ); }; From f9e528a509eb817566dbfc2859a54919dae7141c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Wed, 18 Jun 2025 23:08:17 +0200 Subject: [PATCH 002/112] Add Edit functionality to Room Lists Page: Introduce an 'Edit Room List' button with a link to the edit page Change to results as api return progress --- .../resources/management/room-lists/index.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/pages/email/resources/management/room-lists/index.js b/src/pages/email/resources/management/room-lists/index.js index b9049767c4b9..4ee281072c98 100644 --- a/src/pages/email/resources/management/room-lists/index.js +++ b/src/pages/email/resources/management/room-lists/index.js @@ -1,6 +1,6 @@ import { Layout as DashboardLayout } from "/src/layouts/index.js"; import { CippTablePage } from "/src/components/CippComponents/CippTablePage.jsx"; -import { Visibility, ListAlt } from "@mui/icons-material"; +import { Visibility, ListAlt, Edit } from "@mui/icons-material"; import { TrashIcon } from "@heroicons/react/24/outline"; import { Button } from "@mui/material"; import Link from "next/link"; @@ -16,6 +16,14 @@ const Page = () => { color: "info", icon: , }, + { + label: "Edit Room List", + link: "/identity/administration/groups/edit?groupId=[PrimarySmtpAddress]&groupType=[groupType]", + multiPost: false, + icon: , + color: "success", + }, + { label: "Delete Room List", type: "POST", @@ -24,7 +32,7 @@ const Page = () => { data: { id: "Guid", displayName: "DisplayName", - GroupType: "calculatedGroupType", + GroupType: "groupType", }, confirmText: "Are you sure you want to delete this room list?", multiPost: false, @@ -33,7 +41,7 @@ const Page = () => { const offCanvas = { extendedInfoFields: [ - "ExternalDirectoryObjectId", + "Guid", "PrimarySmtpAddress", "DisplayName", "Phone", @@ -57,7 +65,7 @@ const Page = () => { title={pageTitle} apiUrl={apiUrl} actions={actions} - apiDataKey="" + apiDataKey="Results" offCanvas={offCanvas} simpleColumns={simpleColumns} cardButton={ From 66c134a1949c38f5369f29a097aa6719335b7448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Mon, 23 Jun 2025 22:42:38 +0200 Subject: [PATCH 003/112] standalone edit page --- .../resources/management/room-lists/edit.jsx | 287 ++++++++++++++++++ .../resources/management/room-lists/index.js | 4 +- 2 files changed, 289 insertions(+), 2 deletions(-) create mode 100644 src/pages/email/resources/management/room-lists/edit.jsx diff --git a/src/pages/email/resources/management/room-lists/edit.jsx b/src/pages/email/resources/management/room-lists/edit.jsx new file mode 100644 index 000000000000..b3bc5a78fb8b --- /dev/null +++ b/src/pages/email/resources/management/room-lists/edit.jsx @@ -0,0 +1,287 @@ +import { useEffect, useState } from "react"; +import { Box, Button, Divider, Typography } from "@mui/material"; +import { Grid } from "@mui/system"; +import { useForm } from "react-hook-form"; +import { Layout as DashboardLayout } from "/src/layouts/index.js"; +import CippFormPage from "/src/components/CippFormPages/CippFormPage"; +import CippFormComponent from "/src/components/CippComponents/CippFormComponent"; +import { CippFormUserSelector } from "/src/components/CippComponents/CippFormUserSelector"; +import { useRouter } from "next/router"; +import { ApiGetCall } from "../../../../../api/ApiCall"; +import { useSettings } from "../../../../../hooks/use-settings"; +import { CippDataTable } from "../../../../../components/CippTable/CippDataTable"; + +const EditRoomList = () => { + const router = useRouter(); + const { groupId } = router.query; + const [groupIdReady, setGroupIdReady] = useState(false); + const [showMembershipTable, setShowMembershipTable] = useState(false); + const [combinedData, setCombinedData] = useState([]); + const tenantFilter = useSettings().currentTenant; + + const groupInfo = ApiGetCall({ + url: `/api/ListRoomLists?groupID=${groupId}&tenantFilter=${tenantFilter}&members=true&owners=true`, + queryKey: `ListRoomLists-${groupId}`, + waiting: groupIdReady, + }); + + useEffect(() => { + if (groupId) { + setGroupIdReady(true); + groupInfo.refetch(); + } + }, [router.query, groupId, tenantFilter]); + + const formControl = useForm({ + mode: "onChange", + defaultValues: { + tenantFilter: tenantFilter, + AddMember: [], + RemoveMember: [], + AddOwner: [], + RemoveOwner: [], + }, + }); + + useEffect(() => { + if (groupInfo.isSuccess) { + const group = groupInfo.data?.groupInfo; + if (group) { + // Create combined data for the table + const owners = Array.isArray(groupInfo.data?.owners) ? groupInfo.data.owners : []; + const members = Array.isArray(groupInfo.data?.members) ? groupInfo.data.members : []; + + const combinedData = [ + ...owners.map((o) => ({ + type: "Owner", + userPrincipalName: o.userPrincipalName, + displayName: o.displayName, + })), + ...members.map((m) => ({ + type: "Room", + userPrincipalName: m.PrimarySmtpAddress || m.userPrincipalName || m.mail, + displayName: m.DisplayName || m.displayName, + })), + ]; + setCombinedData(combinedData); + + // Reset the form with all values + formControl.reset({ + tenantFilter: tenantFilter, + mail: group.PrimarySmtpAddress || group.mail, + mailNickname: group.Alias || group.mailNickname || "", + allowExternal: groupInfo?.data?.allowExternal, + displayName: group.DisplayName || group.displayName, + description: group.Description || group.description || "", + groupId: group.Guid || group.id, + groupType: "Room List", + // Initialize empty arrays for add/remove actions + AddMember: [], + RemoveMember: [], + AddOwner: [], + RemoveOwner: [], + }); + } + } + }, [groupInfo.isSuccess, router.query, groupInfo.isFetching]); + + return ( + <> + + + + } + > + {showMembershipTable ? ( + + + + ) : ( + + + + Room List Properties + + + + + + + + + + + + + + Add Members + + + + `${room.displayName || 'Unknown'} (${room.mail || room.userPrincipalName || 'No email'})`, + valueField: "mail", + addedField: { + roomType: "bookingType", + capacity: "capacity", + id: "id", + }, + queryKey: `rooms-${tenantFilter}`, + showRefresh: true, + dataFilter: (rooms) => { + // Get current member emails to filter out + const members = Array.isArray(groupInfo.data?.members) ? groupInfo.data.members : []; + const currentMemberEmails = members.map(m => m.mail || m.userPrincipalName).filter(Boolean); + + // Filter out rooms that are already members + // rooms here have been transformed to {label, value, addedFields} format + const filteredRooms = rooms.filter(room => { + const roomEmail = room.value; // email is in the value field + const isAlreadyMember = currentMemberEmails.includes(roomEmail); + return !isAlreadyMember; + }); + + return filteredRooms; + } + }} + /> + + + + + + + + + Remove Members + + + + ({ + label: `${m.DisplayName || m.displayName || 'Unknown'} (${m.PrimarySmtpAddress || m.userPrincipalName || m.mail})`, + value: m.PrimarySmtpAddress || m.userPrincipalName || m.mail, + addedFields: { id: m.ExternalDirectoryObjectId || m.id }, + })) + : [] + } + /> + + + + ({ + label: `${o.displayName} (${o.userPrincipalName})`, + value: o.userPrincipalName, + addedFields: { id: o.id }, + })) + : [] + } + /> + + + + + Room List Settings + + + + + + + + )} + + + ); +}; + +EditRoomList.getLayout = (page) => {page}; + +export default EditRoomList; \ No newline at end of file diff --git a/src/pages/email/resources/management/room-lists/index.js b/src/pages/email/resources/management/room-lists/index.js index 4ee281072c98..b9e1c1300770 100644 --- a/src/pages/email/resources/management/room-lists/index.js +++ b/src/pages/email/resources/management/room-lists/index.js @@ -12,13 +12,13 @@ const Page = () => { const actions = [ { label: "View included Rooms", - link: `/email/resources/management/room-lists/list/view?roomAddress=[PrimarySmtpAddress]`, + link: `/email/resources/management/room-lists/list/view?roomAddress=[Guid]`, color: "info", icon: , }, { label: "Edit Room List", - link: "/identity/administration/groups/edit?groupId=[PrimarySmtpAddress]&groupType=[groupType]", + link: "/email/resources/management/room-lists/edit?groupId=[PrimarySmtpAddress]", multiPost: false, icon: , color: "success", From f78bb978d2403ef5d4b7a4e4a4a1ef68cea6d3ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Mon, 23 Jun 2025 22:45:57 +0200 Subject: [PATCH 004/112] Enhance EditRoomList component to track original allowExternal value and conditionally format submission data revert more Refactor EditRoomList component to replace CippFormUserSelector with CippFormComponent, enhancing user selection with filtering for existing owners and improved API integration. --- .../resources/management/room-lists/edit.jsx | 46 +++++++++++++++++-- .../identity/administration/groups/edit.jsx | 2 +- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/pages/email/resources/management/room-lists/edit.jsx b/src/pages/email/resources/management/room-lists/edit.jsx index b3bc5a78fb8b..11ff8261e212 100644 --- a/src/pages/email/resources/management/room-lists/edit.jsx +++ b/src/pages/email/resources/management/room-lists/edit.jsx @@ -17,6 +17,7 @@ const EditRoomList = () => { const [groupIdReady, setGroupIdReady] = useState(false); const [showMembershipTable, setShowMembershipTable] = useState(false); const [combinedData, setCombinedData] = useState([]); + const [originalAllowExternal, setOriginalAllowExternal] = useState(null); const tenantFilter = useSettings().currentTenant; const groupInfo = ApiGetCall({ @@ -65,12 +66,16 @@ const EditRoomList = () => { ]; setCombinedData(combinedData); + // Store original allowExternal value for comparison + const allowExternalValue = groupInfo?.data?.allowExternal; + setOriginalAllowExternal(allowExternalValue); + // Reset the form with all values formControl.reset({ tenantFilter: tenantFilter, mail: group.PrimarySmtpAddress || group.mail, mailNickname: group.Alias || group.mailNickname || "", - allowExternal: groupInfo?.data?.allowExternal, + allowExternal: allowExternalValue, displayName: group.DisplayName || group.displayName, description: group.Description || group.description || "", groupId: group.Guid || group.id, @@ -94,6 +99,14 @@ const EditRoomList = () => { formPageType="Edit" backButtonTitle="Room Lists" postUrl="/api/EditRoomList" + customDataformatter={(values) => { + // Only include allowExternal if it has changed from the original value + const modifiedValues = { ...values }; + if (originalAllowExternal !== null && values.allowExternal === originalAllowExternal) { + delete modifiedValues.allowExternal; + } + return modifiedValues; + }} titleButton={ <> + + + ); +}; \ No newline at end of file diff --git a/src/pages/index.js b/src/pages/index.js index 4c3b21ba7ce6..ef5e04907619 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -1,6 +1,6 @@ import Head from "next/head"; import { useEffect, useState } from "react"; -import { Box, Container, Button, Card, CardContent } from "@mui/material"; +import { Box, Container, Button, Card, CardContent, Tooltip } from "@mui/material"; import { Grid } from "@mui/system"; import { CippInfoBar } from "../components/CippCards/CippInfoBar"; import { CippChartCard } from "../components/CippCards/CippChartCard"; @@ -14,10 +14,12 @@ import { CippUniversalSearch } from "../components/CippCards/CippUniversalSearch import { ApiGetCall } from "../api/ApiCall.jsx"; import { CippCopyToClipBoard } from "../components/CippComponents/CippCopyToClipboard.jsx"; import { ExecutiveReportButton } from "../components/ExecutiveReportButton.js"; +import { CippStandardsDialog } from "../components/CippCards/CippStandardsDialog.jsx"; const Page = () => { const { currentTenant } = useSettings(); const [domainVisible, setDomainVisible] = useState(false); + const [standardsDialogOpen, setStandardsDialogOpen] = useState(false); const organization = ApiGetCall({ url: "/api/ListOrg", @@ -254,13 +256,16 @@ const Page = () => { - + + setStandardsDialogOpen(true)} + /> + @@ -357,6 +362,13 @@ const Page = () => { + + setStandardsDialogOpen(false)} + standardsData={standards.data} + currentTenant={currentTenant} + /> ); }; From 89c351a59111ba1742974f746f7fadefd1fc02b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Thu, 26 Jun 2025 21:14:07 +0200 Subject: [PATCH 011/112] Enhance CippUserActions component by adding available licenses count --- src/components/CippComponents/CippUserActions.jsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/CippComponents/CippUserActions.jsx b/src/components/CippComponents/CippUserActions.jsx index 28f4f654b6b9..41a5676c3834 100644 --- a/src/components/CippComponents/CippUserActions.jsx +++ b/src/components/CippComponents/CippUserActions.jsx @@ -20,6 +20,7 @@ import { PhonelinkSetup, Shortcut, } from "@mui/icons-material"; +import { getCippLicenseTranslation } from "../../utils/get-cipp-license-translation"; import { useSettings } from "/src/hooks/use-settings.js"; export const CippUserActions = () => { @@ -62,19 +63,19 @@ export const CippUserActions = () => { type: "number", name: "lifetimeInMinutes", label: "Lifetime (Minutes)", - placeholder: "Leave blank for default" + placeholder: "Leave blank for default", }, { type: "switch", name: "isUsableOnce", - label: "One-time use only" + label: "One-time use only", }, { type: "datePicker", name: "startDateTime", label: "Start Date/Time (leave blank for immediate)", - dateTimeType: "datetime" - } + dateTimeType: "datetime", + }, ], confirmText: "Are you sure you want to create a Temporary Access Password?", multiPost: false, @@ -267,7 +268,8 @@ export const CippUserActions = () => { creatable: false, api: { url: "/api/ListLicenses", - labelField: "skuPartNumber", + labelField: (option) => + `${getCippLicenseTranslation([option])} (${option?.availableUnits} available)`, valueField: "skuId", queryKey: `licenses-${tenant}`, }, From 29f875ec7ec1958f3666a9e7adc39efc26fb35d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Thu, 26 Jun 2025 23:30:37 +0200 Subject: [PATCH 012/112] feat: Add 'Hide group mailbox from Outlook' option in group editing form --- .../identity/administration/groups/edit.jsx | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/pages/identity/administration/groups/edit.jsx b/src/pages/identity/administration/groups/edit.jsx index 4453541ba4d7..be55679d0f52 100644 --- a/src/pages/identity/administration/groups/edit.jsx +++ b/src/pages/identity/administration/groups/edit.jsx @@ -73,6 +73,7 @@ const EditGroup = () => { mailNickname: group.mailNickname || "", allowExternal: groupInfo?.data?.allowExternal, sendCopies: groupInfo?.data?.sendCopies, + hideFromOutlookClients: groupInfo?.data?.hideFromOutlookClients, displayName: group.displayName, description: group.description || "", membershipRules: group.membershipRule || "", @@ -110,6 +111,7 @@ const EditGroup = () => { setInitialValues({ allowExternal: groupInfo?.data?.allowExternal, sendCopies: groupInfo?.data?.sendCopies, + hideFromOutlookClients: groupInfo?.data?.hideFromOutlookClients, }); // Reset the form with all values @@ -132,6 +134,11 @@ const EditGroup = () => { delete cleanedData.sendCopies; } + // Only include hideFromOutlookClients if it has changed from the initial value + if (formData.hideFromOutlookClients === initialValues.hideFromOutlookClients) { + delete cleanedData.hideFromOutlookClients; + } + return cleanedData; }; @@ -359,6 +366,19 @@ const EditGroup = () => { /> )} + + {groupType === "Microsoft 365" && ( + + + + )} )} From 481ffe00927eeea03979068ebd7822d1171170a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Thu, 26 Jun 2025 23:41:43 +0200 Subject: [PATCH 013/112] simplify with a loop --- .../identity/administration/groups/edit.jsx | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/pages/identity/administration/groups/edit.jsx b/src/pages/identity/administration/groups/edit.jsx index be55679d0f52..7b7dfe12e18f 100644 --- a/src/pages/identity/administration/groups/edit.jsx +++ b/src/pages/identity/administration/groups/edit.jsx @@ -124,20 +124,14 @@ const EditGroup = () => { const customDataFormatter = (formData) => { const cleanedData = { ...formData }; - // Only include allowExternal if it has changed from the initial value - if (formData.allowExternal === initialValues.allowExternal) { - delete cleanedData.allowExternal; - } - - // Only include sendCopies if it has changed from the initial value - if (formData.sendCopies === initialValues.sendCopies) { - delete cleanedData.sendCopies; - } + // Properties that should only be sent if they've changed from initial values + const changeDetectionProperties = ["allowExternal", "sendCopies", "hideFromOutlookClients"]; - // Only include hideFromOutlookClients if it has changed from the initial value - if (formData.hideFromOutlookClients === initialValues.hideFromOutlookClients) { - delete cleanedData.hideFromOutlookClients; - } + changeDetectionProperties.forEach((property) => { + if (formData[property] === initialValues[property]) { + delete cleanedData[property]; + } + }); return cleanedData; }; From 9d3f1b104a108204a67da3091a5abe7abe1c52c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Fri, 27 Jun 2025 01:11:38 +0200 Subject: [PATCH 014/112] Feat: Add new standard for configuring SharePoint default sharing link type and permissions --- src/data/standards.json | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/data/standards.json b/src/data/standards.json index 6be68e4a46e0..c4f01631d6bf 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -3615,6 +3615,40 @@ "powershellEquivalent": "Set-SPOTenant -EmailAttestationRequired $true -EmailAttestationReAuthDays 15", "recommendedBy": ["CIS", "CIPP"] }, + { + "name": "standards.DefaultSharingLink", + "cat": "SharePoint Standards", + "tag": ["CIS M365 5.0 (7.2.7)", "CIS M365 5.0 (7.2.11)", "CISA (MS.SPO.1.4v1)"], + "helpText": "Configure the SharePoint default sharing link type and permission. This setting controls both the type of sharing link created by default and the permission level assigned to those links.", + "docsDescription": "Sets the default sharing link type (Direct or Internal) and permission (View) in SharePoint and OneDrive. Direct sharing means links only work for specific people, while Internal sharing means links work for anyone in the organization. Setting the view permission as the default ensures that users must deliberately select the edit permission when sharing a link, reducing the risk of unintentionally granting edit privileges.", + "executiveText": "Configures SharePoint default sharing links to implement the principle of least privilege for document sharing. This security measure reduces the risk of accidental data modification while maintaining collaboration functionality, requiring users to explicitly select Edit permissions when necessary. The sharing type setting controls whether links are restricted to specific recipients or available to the entire organization. This reduces the risk of accidental data exposure through link sharing.", + "addedComponent": [ + { + "type": "autoComplete", + "multiple": false, + "creatable": false, + "required": true, + "label": "Default Sharing Link Type", + "name": "standards.DefaultSharingLink.SharingLinkType", + "options": [ + { + "label": "Direct - Only the people the user specifies", + "value": "Direct" + }, + { + "label": "Internal - Only people in your organization", + "value": "Internal" + } + ] + } + ], + "label": "Set Default Sharing Link Settings", + "impact": "Low Impact", + "impactColour": "info", + "addedDate": "2025-06-13", + "powershellEquivalent": "Set-SPOTenant -DefaultSharingLinkType [Direct|Internal] -DefaultLinkPermission View", + "recommendedBy": ["CIS", "CIPP"] + }, { "name": "standards.DisableAddShortcutsToOneDrive", "cat": "SharePoint Standards", From 5fb2cf86a74b07ca6d4ce28cf76d44c173df3bc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Fri, 27 Jun 2025 01:25:32 +0200 Subject: [PATCH 015/112] Deprecate SPDirectSharing --- src/data/standards.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data/standards.json b/src/data/standards.json index c4f01631d6bf..dbf842106be4 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -3564,8 +3564,8 @@ { "name": "standards.SPDirectSharing", "cat": "SharePoint Standards", - "tag": ["CIS M365 5.0 (7.2.7)", "CISA (MS.SPO.1.4v1)"], - "helpText": "Ensure default link sharing is set to Direct in SharePoint and OneDrive", + "tag": [], + "helpText": "This standard has been deprecated in favor of the Default Sharing Link standard. ", "executiveText": "Configures SharePoint and OneDrive to share files directly with specific people rather than creating anonymous links, improving security by ensuring only intended recipients can access shared documents. This reduces the risk of accidental data exposure through link sharing.", "addedComponent": [], "label": "Default sharing to Direct users", From a6e4b89c5c90ffd65e28576389413f70737aa17c Mon Sep 17 00:00:00 2001 From: Zac Richards <107489668+Zacgoose@users.noreply.github.com> Date: Fri, 27 Jun 2025 22:29:25 +0800 Subject: [PATCH 016/112] Remove last of grid and unstable grid 2 imports little cleanup, no broken things this time --- src/components/CippComponents/CippAppPermissionBuilder.jsx | 1 - src/components/CippFormPages/CippJSONView.jsx | 1 - src/components/CippFormPages/CippSchedulerForm.jsx | 1 - src/components/CippWizard/CippIntunePolicy.jsx | 2 +- src/components/CippWizard/CippWizardAutopilotImport.jsx | 6 +----- src/pages/email/spamfilter/list-quarantine-policies/add.jsx | 5 +++-- src/pages/tenant/standards/bpa-report/builder.js | 1 - src/pages/tenant/standards/bpa-report/view.js | 1 - src/pages/tenant/standards/list-standards/index.js | 2 +- src/pages/tools/breachlookup/index.js | 4 ++-- src/sections/dashboard/components/stats/stats-2.js | 3 ++- src/sections/dashboard/invoices/invoices-stats.js | 3 ++- src/sections/dashboard/products/products-stats.js | 3 ++- 13 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/components/CippComponents/CippAppPermissionBuilder.jsx b/src/components/CippComponents/CippAppPermissionBuilder.jsx index 3e489d043304..46adfc153008 100644 --- a/src/components/CippComponents/CippAppPermissionBuilder.jsx +++ b/src/components/CippComponents/CippAppPermissionBuilder.jsx @@ -821,7 +821,6 @@ const CippAppPermissionBuilder = ({ data !== null && data !== undefined) .map((data, index) => ( { key={idx} > {param.Type === "System.Boolean" || diff --git a/src/components/CippWizard/CippIntunePolicy.jsx b/src/components/CippWizard/CippIntunePolicy.jsx index 2e1e5797c2f0..8d2197dc2b13 100644 --- a/src/components/CippWizard/CippIntunePolicy.jsx +++ b/src/components/CippWizard/CippIntunePolicy.jsx @@ -103,7 +103,7 @@ export const CippIntunePolicy = (props) => { return null; } return uniquePlaceholders.map((placeholder) => ( - + {selectedTenants.map((tenant, idx) => ( { diff --git a/src/pages/email/spamfilter/list-quarantine-policies/add.jsx b/src/pages/email/spamfilter/list-quarantine-policies/add.jsx index 3af31bce1e9e..bd2e36fc3764 100644 --- a/src/pages/email/spamfilter/list-quarantine-policies/add.jsx +++ b/src/pages/email/spamfilter/list-quarantine-policies/add.jsx @@ -1,5 +1,6 @@ -import React, { useEffect } from "react"; -import { Grid, Divider } from "@mui/material"; +import { useEffect } from "react"; +import { Divider } from "@mui/material"; +import { Grid } from "@mui/system"; import { useForm, useWatch } from "react-hook-form"; import { Layout as DashboardLayout } from "/src/layouts/index.js"; import CippFormPage from "/src/components/CippFormPages/CippFormPage"; diff --git a/src/pages/tenant/standards/bpa-report/builder.js b/src/pages/tenant/standards/bpa-report/builder.js index 4a0d2f36b62f..976d79b63c53 100644 --- a/src/pages/tenant/standards/bpa-report/builder.js +++ b/src/pages/tenant/standards/bpa-report/builder.js @@ -255,7 +255,6 @@ const Page = () => { {blockCards.map((block, index) => ( diff --git a/src/pages/tenant/standards/bpa-report/view.js b/src/pages/tenant/standards/bpa-report/view.js index 361e14780442..0af8c51a7011 100644 --- a/src/pages/tenant/standards/bpa-report/view.js +++ b/src/pages/tenant/standards/bpa-report/view.js @@ -157,7 +157,6 @@ const Page = () => { <> {blockCards.map((block, index) => ( diff --git a/src/pages/tenant/standards/list-standards/index.js b/src/pages/tenant/standards/list-standards/index.js index dcaa36640959..0c479d6fa838 100644 --- a/src/pages/tenant/standards/list-standards/index.js +++ b/src/pages/tenant/standards/list-standards/index.js @@ -130,7 +130,7 @@ const Page = () => {
{oldStandards.isSuccess && oldStandards.data.length !== 0 && ( - + { > - + @@ -120,7 +120,7 @@ const Page = () => { )} {getGeoIP.data?.map((breach, index) => ( - + {breach.Title}} diff --git a/src/sections/dashboard/components/stats/stats-2.js b/src/sections/dashboard/components/stats/stats-2.js index f58463294fb1..273abb3e1522 100644 --- a/src/sections/dashboard/components/stats/stats-2.js +++ b/src/sections/dashboard/components/stats/stats-2.js @@ -1,4 +1,5 @@ -import { Box, Typography, Unstable_Grid2 as Grid } from '@mui/material'; +import { Box, Typography } from '@mui/material'; +import { Grid } from "@mui/system"; const data = [ { diff --git a/src/sections/dashboard/invoices/invoices-stats.js b/src/sections/dashboard/invoices/invoices-stats.js index 78521a117f2f..895c96d6d883 100644 --- a/src/sections/dashboard/invoices/invoices-stats.js +++ b/src/sections/dashboard/invoices/invoices-stats.js @@ -1,6 +1,7 @@ import PropTypes from 'prop-types'; import numeral from 'numeral'; -import { Box, Card, CardContent, Stack, Typography, Unstable_Grid2 as Grid } from '@mui/material'; +import { Box, Card, CardContent, Stack, Typography } from '@mui/material'; +import { Grid } from "@mui/system"; import { alpha, useTheme } from '@mui/material/styles'; import { Chart } from '../../../components/chart'; diff --git a/src/sections/dashboard/products/products-stats.js b/src/sections/dashboard/products/products-stats.js index 8a8b415d1fa5..6e64f9ba23a5 100644 --- a/src/sections/dashboard/products/products-stats.js +++ b/src/sections/dashboard/products/products-stats.js @@ -2,7 +2,8 @@ import CheckCircleIcon from "@heroicons/react/24/outline/CheckCircleIcon"; import CurrencyDollarIcon from "@heroicons/react/24/outline/CurrencyDollarIcon"; import ShoppingCartIcon from "@heroicons/react/24/outline/ShoppingCartIcon"; import XCircleIcon from "@heroicons/react/24/outline/XCircleIcon"; -import { Card, Stack, Typography, Unstable_Grid2 as Grid } from "@mui/material"; +import { Card, Stack, Typography } from "@mui/material"; +import { Grid } from "@mui/system"; const stats = [ { From 30cb8d6dcc6707c9d84584a472e15e43802ba4ef Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 27 Jun 2025 16:25:12 -0400 Subject: [PATCH 017/112] fix trailing slash --- src/pages/_app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/_app.js b/src/pages/_app.js index 6a74f65f2e5d..0b47acc52be7 100644 --- a/src/pages/_app.js +++ b/src/pages/_app.js @@ -239,8 +239,8 @@ const App = (props) => { id: "documentation", icon: , name: "Check the Documentation", - href: `https://docs.cipp.app/user-documentation/${pathname}`, - onClick: () => window.open(`https://docs.cipp.app/user-documentation/${pathname}`, "_blank"), + href: `https://docs.cipp.app/user-documentation${pathname}`, + onClick: () => window.open(`https://docs.cipp.app/user-documentation${pathname}`, "_blank"), }, ]; From d85e2524eaf632cb06a7b98d7f6a64da3e8b30d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Sat, 28 Jun 2025 09:40:09 +0200 Subject: [PATCH 018/112] Move stuff into consts and add icons --- .../app-consent-requests/index.js | 102 ++++++++++-------- 1 file changed, 55 insertions(+), 47 deletions(-) diff --git a/src/pages/tenant/administration/app-consent-requests/index.js b/src/pages/tenant/administration/app-consent-requests/index.js index f382bb3d71cd..7bf802d6f361 100644 --- a/src/pages/tenant/administration/app-consent-requests/index.js +++ b/src/pages/tenant/administration/app-consent-requests/index.js @@ -3,21 +3,11 @@ import { Layout as DashboardLayout } from "/src/layouts/index.js"; import { CippTablePage } from "/src/components/CippComponents/CippTablePage.jsx"; import { Button, Accordion, AccordionSummary, AccordionDetails, Typography } from "@mui/material"; import { Grid } from "@mui/system"; -import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; +import { Visibility, CheckCircle, ExpandMore } from "@mui/icons-material"; import { useForm } from "react-hook-form"; import CippFormComponent from "/src/components/CippComponents/CippFormComponent"; import { useSettings } from "/src/hooks/use-settings"; -const simpleColumns = [ - "Tenant", - "CippStatus", - "appDisplayName", - "requestUser", - "requestReason", - "requestStatus", - "requestDate", -]; - const apiUrl = "/api/ListAppConsentRequests"; const pageTitle = "App Consent Requests"; @@ -29,6 +19,56 @@ const Page = () => { }, }); + const actions = [ + { + label: "Review in Entra", + link: `https://entra.microsoft.com/${tenantFilter}/#view/Microsoft_AAD_IAM/StartboardApplicationsMenuBlade/~/AccessRequests`, + color: "info", + icon: , + target: "_blank", + external: true, + }, + { + label: "Approve in Entra", + link: "[consentUrl]", + color: "info", + icon: , + target: "_blank", + external: true, + }, + ]; + + const simpleColumns = [ + "requestUser", // Requester + "appDisplayName", // Application Name + "appId", // Application ID + "requestReason", // Reason + "requestStatus", // Status + "reviewedBy", // Reviewed by + "reviewedJustification", // Reviewed Reason + ]; + + const filters = [ + { + filterName: "Pending requests", + value: [{ id: "requestStatus", value: "InProgress" }], + type: "column", + }, + ]; + + const offCanvas = { + extendedInfoFields: [ + "requestUser", // Requester + "appDisplayName", // Application Name + "appId", // Application ID + "requestReason", // Reason + "requestStatus", // Status + "reviewedBy", // Reviewed by + "reviewedJustification", // Reviewed Reason + ], + actions: actions, + }; + const [expanded, setExpanded] = useState(false); // Accordion state const [filterParams, setFilterParams] = useState({}); // Dynamic filter params @@ -49,7 +89,7 @@ const Page = () => { // FIXME: This tableFilter does nothing. It does not change the table data at all, like the code makes it seem like it should. -Bobby tableFilter={ setExpanded(!expanded)}> - }> + }> Filters @@ -86,45 +126,13 @@ const Page = () => { title={pageTitle} apiUrl={apiUrl} simpleColumns={simpleColumns} - filters={[ - // Filter for showing only pending requests - { - filterName: "Pending requests", - value: [{ id: "requestStatus", value: "InProgress" }], - type: "column", - }, - ]} + filters={filters} queryKey={`AppConsentRequests-${JSON.stringify(filterParams)}-${tenantFilter}`} apiData={{ ...filterParams, }} - offCanvas={{ - extendedInfoFields: [ - "requestUser", // Requester - "appDisplayName", // Application Name - "appId", // Application ID - "requestReason", // Reason - "requestStatus", // Status - "reviewedBy", // Reviewed by - "reviewedJustification", // Reviewed Reason - ], - }} - actions={[ - { - label: "Review in Entra", - link: `https://entra.microsoft.com/${tenantFilter}/#view/Microsoft_AAD_IAM/StartboardApplicationsMenuBlade/~/AccessRequests`, - color: "info", - target: "_blank", - external: true, - }, - { - label: "Approve in Entra", - link: "[consentUrl]", - color: "info", - target: "_blank", - external: true, - }, - ]} + offCanvas={offCanvas} + actions={actions} /> ); }; From 76adbd1920e949efb54595540ded1de76ae519a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Sat, 28 Jun 2025 11:14:15 +0200 Subject: [PATCH 019/112] Fix: Fix tablefilter --- .../app-consent-requests/index.js | 121 ++++++++++++++---- 1 file changed, 96 insertions(+), 25 deletions(-) diff --git a/src/pages/tenant/administration/app-consent-requests/index.js b/src/pages/tenant/administration/app-consent-requests/index.js index 7bf802d6f361..266bed443ca0 100644 --- a/src/pages/tenant/administration/app-consent-requests/index.js +++ b/src/pages/tenant/administration/app-consent-requests/index.js @@ -1,9 +1,18 @@ import { useState } from "react"; import { Layout as DashboardLayout } from "/src/layouts/index.js"; import { CippTablePage } from "/src/components/CippComponents/CippTablePage.jsx"; -import { Button, Accordion, AccordionSummary, AccordionDetails, Typography } from "@mui/material"; +import { + Button, + Accordion, + AccordionSummary, + AccordionDetails, + Typography, + SvgIcon, + Stack, +} from "@mui/material"; import { Grid } from "@mui/system"; -import { Visibility, CheckCircle, ExpandMore } from "@mui/icons-material"; +import { Visibility, CheckCircle, ExpandMore, Security } from "@mui/icons-material"; +import { FunnelIcon, XMarkIcon } from "@heroicons/react/24/outline"; import { useForm } from "react-hook-form"; import CippFormComponent from "/src/components/CippComponents/CippFormComponent"; import { useSettings } from "/src/hooks/use-settings"; @@ -19,6 +28,44 @@ const Page = () => { }, }); + const [expanded, setExpanded] = useState(false); // Accordion state + const [filterEnabled, setFilterEnabled] = useState(false); // State for filter toggle + const [requestStatus, setRequestStatus] = useState(null); // State for request status filter + const [requestStatusLabel, setRequestStatusLabel] = useState(null); // State for displaying filter label + + const onSubmit = (data) => { + // Handle the case where requestStatus could be an object {label, value} or a string + const statusValue = + typeof data.requestStatus === "object" && data.requestStatus?.value + ? data.requestStatus.value + : data.requestStatus; + const statusLabel = + typeof data.requestStatus === "object" && data.requestStatus?.label + ? data.requestStatus.label + : data.requestStatus; + + // Check if any filter is applied + const hasFilter = statusValue !== "All"; + setFilterEnabled(hasFilter); + + // Set request status filter if not "All" + setRequestStatus(hasFilter ? statusValue : null); + setRequestStatusLabel(hasFilter ? statusLabel : null); + + // Close the accordion after applying filters + setExpanded(false); + }; + + const clearFilters = () => { + formControl.reset({ + requestStatus: "All", + }); + setFilterEnabled(false); + setRequestStatus(null); + setRequestStatusLabel(null); + setExpanded(false); // Close the accordion when clearing filters + }; + const actions = [ { label: "Review in Entra", @@ -69,28 +116,28 @@ const Page = () => { actions: actions, }; - const [expanded, setExpanded] = useState(false); // Accordion state - const [filterParams, setFilterParams] = useState({}); // Dynamic filter params - - const onSubmit = (data) => { - // Handle filter application logic - const { requestStatus } = data; - const filters = {}; - - if (requestStatus !== "All") { - filters.requestStatus = requestStatus; - } - - setFilterParams(filters); - }; - return ( setExpanded(!expanded)}> }> - Filters + + + + + + App Consent Request Filters + {filterEnabled ? ( + + ({requestStatusLabel && <>Status: {requestStatusLabel}}) + + ) : ( + + (No filters applied) + + )} + +
@@ -112,11 +159,34 @@ const Page = () => { /> - {/* Submit Button */} + {/* Action Buttons */} - + + + +
@@ -127,9 +197,10 @@ const Page = () => { apiUrl={apiUrl} simpleColumns={simpleColumns} filters={filters} - queryKey={`AppConsentRequests-${JSON.stringify(filterParams)}-${tenantFilter}`} + queryKey={`AppConsentRequests-${requestStatus}-${filterEnabled}-${tenantFilter}`} apiData={{ - ...filterParams, + RequestStatus: requestStatus, // Pass request status filter from state + Filter: filterEnabled, // Pass filter toggle state }} offCanvas={offCanvas} actions={actions} From ed3f5d3b107ac5690f1b86ec7198eabd52e3fb04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Sat, 28 Jun 2025 12:47:54 +0200 Subject: [PATCH 020/112] Few more filters --- .../administration/app-consent-requests/index.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/pages/tenant/administration/app-consent-requests/index.js b/src/pages/tenant/administration/app-consent-requests/index.js index 266bed443ca0..7715393824dc 100644 --- a/src/pages/tenant/administration/app-consent-requests/index.js +++ b/src/pages/tenant/administration/app-consent-requests/index.js @@ -93,6 +93,7 @@ const Page = () => { "requestStatus", // Status "reviewedBy", // Reviewed by "reviewedJustification", // Reviewed Reason + "consentUrl", // Consent URL ]; const filters = [ @@ -101,6 +102,16 @@ const Page = () => { value: [{ id: "requestStatus", value: "InProgress" }], type: "column", }, + { + filterName: "Expired requests", + value: [{ id: "requestStatus", value: "Expired" }], + type: "column", + }, + { + filterName: "Completed requests", + value: [{ id: "requestStatus", value: "Completed" }], + type: "column", + }, ]; const offCanvas = { @@ -112,6 +123,7 @@ const Page = () => { "requestStatus", // Status "reviewedBy", // Reviewed by "reviewedJustification", // Reviewed Reason + "consentUrl", // Consent URL ], actions: actions, }; From 35563f200af87c3b334e55171c77644ff8596439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Sat, 28 Jun 2025 12:55:55 +0200 Subject: [PATCH 021/112] Update default filter settings for app consent requests --- .../administration/app-consent-requests/index.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/tenant/administration/app-consent-requests/index.js b/src/pages/tenant/administration/app-consent-requests/index.js index 7715393824dc..5aafe8c75958 100644 --- a/src/pages/tenant/administration/app-consent-requests/index.js +++ b/src/pages/tenant/administration/app-consent-requests/index.js @@ -24,14 +24,14 @@ const Page = () => { const tenantFilter = useSettings().currentTenant; const formControl = useForm({ defaultValues: { - requestStatus: "All", + requestStatus: "InProgress", }, }); - const [expanded, setExpanded] = useState(false); // Accordion state - const [filterEnabled, setFilterEnabled] = useState(false); // State for filter toggle - const [requestStatus, setRequestStatus] = useState(null); // State for request status filter - const [requestStatusLabel, setRequestStatusLabel] = useState(null); // State for displaying filter label + const [expanded, setExpanded] = useState(true); // Accordion state - start expanded since we have a default filter + const [filterEnabled, setFilterEnabled] = useState(true); // State for filter toggle - start with filter enabled + const [requestStatus, setRequestStatus] = useState("InProgress"); // State for request status filter - default to InProgress + const [requestStatusLabel, setRequestStatusLabel] = useState("Pending"); // State for displaying filter label - default label const onSubmit = (data) => { // Handle the case where requestStatus could be an object {label, value} or a string From eb58b2980a5502964bb3f071b7e439a700ec1199 Mon Sep 17 00:00:00 2001 From: ngms-psh Date: Sun, 29 Jun 2025 17:24:36 +0200 Subject: [PATCH 022/112] Change method to PATCH as used by the CIPP-API --- src/pages/endpoint/MEM/list-scripts/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/endpoint/MEM/list-scripts/index.jsx b/src/pages/endpoint/MEM/list-scripts/index.jsx index a5fecd62a878..7dfe8f1537a4 100644 --- a/src/pages/endpoint/MEM/list-scripts/index.jsx +++ b/src/pages/endpoint/MEM/list-scripts/index.jsx @@ -113,7 +113,7 @@ const Page = () => { }; const response = await fetch("/api/EditIntuneScript", { - method: "POST", + method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(patchData), }); From d768c12b0692a6b50666901bdd411ec9ea9dc9e4 Mon Sep 17 00:00:00 2001 From: ngms-psh Date: Sun, 29 Jun 2025 17:25:24 +0200 Subject: [PATCH 023/112] Dynamically set language by script type --- src/pages/endpoint/MEM/list-scripts/index.jsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/pages/endpoint/MEM/list-scripts/index.jsx b/src/pages/endpoint/MEM/list-scripts/index.jsx index 7dfe8f1537a4..ffeff231e353 100644 --- a/src/pages/endpoint/MEM/list-scripts/index.jsx +++ b/src/pages/endpoint/MEM/list-scripts/index.jsx @@ -12,7 +12,7 @@ import { DialogActions, } from "@mui/material"; import { CippCodeBlock } from "/src/components/CippComponents/CippCodeBlock"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useMemo } from "react"; import { useDispatch } from "react-redux"; import { Close, Save } from "@mui/icons-material"; import { useSettings } from "../../../../hooks/use-settings"; @@ -31,6 +31,11 @@ const Page = () => { const dispatch = useDispatch(); + const language = useMemo(() => { + return currentScript?.scriptType?.toLowerCase() === ("macos" || "linux") ? "shell" : "powershell"; + }, [currentScript?.scriptType]); + + const tenantFilter = useSettings().currentTenant; const { isLoading: scriptIsLoading, @@ -240,7 +245,7 @@ const Page = () => { type="editor" code={codeContent} onChange={codeChange} - language="powershell" + language={language} /> )} From 053f05ddd1f739533e492cf9afc3367b6f756b6f Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sun, 29 Jun 2025 18:34:14 +0200 Subject: [PATCH 024/112] Fixes ugly filter bar. --- .../CippStandards/CippStandardDialog.jsx | 418 ++++++++---------- 1 file changed, 182 insertions(+), 236 deletions(-) diff --git a/src/components/CippStandards/CippStandardDialog.jsx b/src/components/CippStandards/CippStandardDialog.jsx index 553f1127b1a0..c16bcd0ae55b 100644 --- a/src/components/CippStandards/CippStandardDialog.jsx +++ b/src/components/CippStandards/CippStandardDialog.jsx @@ -935,260 +935,206 @@ const CippStandardDialog = ({ sx={{ mb: 3 }} /> - {/* View Toggle Controls */} - - - View Mode: - - { - if (newViewMode !== null) { - setViewMode(newViewMode); - } - }} - size="small" - > - - - Cards - - - - List - - - - - {/* Filter Controls Section */} + {/* Unified Controls Section */} {/* Clickable header bar */} - setFiltersExpanded(!filtersExpanded)} - sx={{ - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - mb: 2, - p: 1, + sx={{ + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + py: 0.75, + px: 1, borderRadius: 1, cursor: 'pointer', bgcolor: 'action.hover', + border: '1px solid', + borderColor: 'divider', '&:hover': { bgcolor: 'action.selected' } }} > - - Sort & Filter Options - - {filtersExpanded ? : } - - - {/* Compact summary when collapsed */} - {!filtersExpanded && ( - - - Sorted by {sortBy === 'addedDate' ? 'Date Added' : 'Name'} ({sortOrder === 'desc' ? 'Desc' : 'Asc'}) + + + + View, Sort & Filter Options - {activeFiltersCount > 0 && ( - <> - - • {activeFiltersCount} filter{activeFiltersCount !== 1 ? 's' : ''} - - - + {!filtersExpanded && ( + + ({viewMode === 'card' ? 'Card' : 'List'} • {sortBy === 'addedDate' ? 'Date' : 'Name'} {sortOrder === 'desc' ? '↓' : '↑'}{activeFiltersCount > 0 ? ` • ${activeFiltersCount} filter${activeFiltersCount !== 1 ? 's' : ''}` : ''}) + )} - )} + {filtersExpanded ? : } + - {/* Collapsible filter controls */} + {/* Single line controls when expanded */} - - {/* Sort Controls Card */} - - - SORT: - - - Sort By - - - - - Order - - - - - {/* Filter Controls Card */} - - - FILTER: - - - Categories - - - - - Impact - - - - - Recommended By - - - - - Compliance Tags - - - - {/* New Standards Toggle */} - setShowOnlyNew(e.target.checked)} - /> + + {/* View Mode */} + { + if (newViewMode !== null) { + setViewMode(newViewMode); } - label="New (30 days)" - sx={{ ml: 1 }} - /> + }} + > + + + Cards + + + + List + + + + {/* Sort Controls */} + + Sort By + + + + + Order + + + + {/* Filter Controls */} + + Categories + + + + + Impact + + + + + Recommended By + + + + + Compliance Tags + + + + {/* New Standards Toggle */} + setShowOnlyNew(e.target.checked)} + /> + } + label="New (30 days)" + sx={{ ml: 1 }} + /> - {/* Clear All Filters Button */} - {activeFiltersCount > 0 && ( - - )} - + {/* Clear Button */} + {activeFiltersCount > 0 && ( + + )} From 6ea53611d41ad16bbbdebfbedd8eb3d3d75b9ee1 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sun, 29 Jun 2025 18:37:54 +0200 Subject: [PATCH 025/112] visual changes --- .../CippStandards/CippStandardDialog.jsx | 1076 +++++++++-------- 1 file changed, 567 insertions(+), 509 deletions(-) diff --git a/src/components/CippStandards/CippStandardDialog.jsx b/src/components/CippStandards/CippStandardDialog.jsx index c16bcd0ae55b..1767bdf0ecf0 100644 --- a/src/components/CippStandards/CippStandardDialog.jsx +++ b/src/components/CippStandards/CippStandardDialog.jsx @@ -30,7 +30,16 @@ import { ListItemSecondaryAction, } from "@mui/material"; import { Grid } from "@mui/system"; -import { Add, Sort, Clear, FilterList, ExpandMore, ExpandLess, ViewModule, ViewList } from "@mui/icons-material"; +import { + Add, + Sort, + Clear, + FilterList, + ExpandMore, + ExpandLess, + ViewModule, + ViewList, +} from "@mui/icons-material"; import { useState, useCallback, useMemo, memo, useEffect } from "react"; import { debounce } from "lodash"; import { Virtuoso } from "react-virtuoso"; @@ -89,13 +98,13 @@ const StandardCard = memo( {isNewStandard(standard.addedDate) && ( @@ -104,169 +113,164 @@ const StandardCard = memo( size="small" color="success" sx={{ - position: 'absolute', + position: "absolute", top: -10, left: 12, zIndex: 1, - fontSize: '0.7rem', + fontSize: "0.7rem", height: 20, - fontWeight: 'bold' + fontWeight: "bold", }} /> )} - - - - {standard.label} - - {expanded && standard.helpText && ( - <> - - Description: - - theme.palette.primary.main, - textDecoration: "underline", - "&:hover": { - textDecoration: "none", + border: "2px solid", + borderColor: "success.main", + }), + }} + > + + + {standard.label} + + {expanded && standard.helpText && ( + <> + + Description: + + theme.palette.primary.main, + textDecoration: "underline", + "&:hover": { + textDecoration: "none", + }, }, - }, - color: "text.secondary", - fontSize: "0.875rem", - lineHeight: 1.43, - mb: 2, - }} - > - ( - - {children} - - ), - // Convert paragraphs to spans to avoid unwanted spacing - p: ({ children }) => {children}, + color: "text.secondary", + fontSize: "0.875rem", + lineHeight: 1.43, + mb: 2, }} > - {standard.helpText} - - - - )} - - Category: - - - {expanded && - standard.tag?.filter((tag) => !tag.toLowerCase().includes("impact")).length > 0 && ( + ( + + {children} + + ), + // Convert paragraphs to spans to avoid unwanted spacing + p: ({ children }) => {children}, + }} + > + {standard.helpText} + + + + )} + + Category: + + + {expanded && + standard.tag?.filter((tag) => !tag.toLowerCase().includes("impact")).length > 0 && ( + <> + + Tags: + + + {standard.tag + .filter((tag) => !tag.toLowerCase().includes("impact")) + .map((tag, idx) => ( + + ))} + + + )} + + Impact: + + + {expanded && standard.recommendedBy?.length > 0 && ( <> - Tags: + Recommended By: + + + {standard.recommendedBy.join(", ")} - - {standard.tag - .filter((tag) => !tag.toLowerCase().includes("impact")) - .map((tag, idx) => ( - - ))} - )} - - Impact: - - - {expanded && standard.recommendedBy?.length > 0 && ( - <> - - Recommended By: - - - {standard.recommendedBy.join(", ")} - - - )} - {expanded && standard.addedDate?.length > 0 && ( - <> - - Date Added: - - - - {standard.addedDate} + {expanded && standard.addedDate?.length > 0 && ( + <> + + Date Added: - - - )} - - - - {standard.multiple ? ( - handleAddClick(standard.name)} - > - - - ) : ( - - } - label="Add this standard to the template" - /> - )} - - + + + {standard.addedDate} + + + + )} + + + + {standard.multiple ? ( + handleAddClick(standard.name)} + > + + + ) : ( + + } + label="Add this standard to the template" + /> + )} + + ); @@ -327,14 +331,14 @@ const VirtualizedStandardGrid = memo(({ items, renderItem }) => { defaultItemHeight={320} // Provide estimated row height for better virtualization itemContent={(index) => ( - {rows[index].map(renderItem)} @@ -348,189 +352,191 @@ const VirtualizedStandardGrid = memo(({ items, renderItem }) => { VirtualizedStandardGrid.displayName = "VirtualizedStandardGrid"; // Compact List View component for standards -const CompactStandardList = memo(({ items, selectedStandards, handleToggleSingleStandard, handleAddClick, isButtonDisabled }) => { - return ( - - {items.map(({ standard, category }) => { - const isSelected = !!selectedStandards[standard.name]; - - const isNewStandard = (dateAdded) => { - if (!dateAdded) return false; - const currentDate = new Date(); - const addedDate = new Date(dateAdded); - return differenceInDays(currentDate, addedDate) <= 30; - }; - - const handleToggle = () => { - handleToggleSingleStandard(standard.name); - }; - - return ( - - - - {standard.label} - - {isNewStandard(standard.addedDate) && ( +const CompactStandardList = memo( + ({ items, selectedStandards, handleToggleSingleStandard, handleAddClick, isButtonDisabled }) => { + return ( + + {items.map(({ standard, category }) => { + const isSelected = !!selectedStandards[standard.name]; + + const isNewStandard = (dateAdded) => { + if (!dateAdded) return false; + const currentDate = new Date(); + const addedDate = new Date(dateAdded); + return differenceInDays(currentDate, addedDate) <= 30; + }; + + const handleToggle = () => { + handleToggleSingleStandard(standard.name); + }; + + return ( + + + + {standard.label} + + {isNewStandard(standard.addedDate) && ( + + )} + - )} - - - - } - secondary={ - - {standard.helpText && ( - theme.palette.primary.main, - textDecoration: "underline", - "&:hover": { - textDecoration: "none", + + } + secondary={ + + {standard.helpText && ( + theme.palette.primary.main, + textDecoration: "underline", + "&:hover": { + textDecoration: "none", + }, }, - }, - color: "text.secondary", - fontSize: "0.875rem", - lineHeight: 1.43, - }} - > - ( - - {children} - - ), - p: ({ children }) => ( - - {children} - - ), + color: "text.secondary", + fontSize: "0.875rem", + lineHeight: 1.43, }} > - {standard.helpText} - - - )} - - {standard.tag?.filter((tag) => !tag.toLowerCase().includes("impact")).length > 0 && ( - - {standard.tag - .filter((tag) => !tag.toLowerCase().includes("impact")) - .slice(0, 3) // Show only first 3 tags to save space - .map((tag, idx) => ( - - ))} - {standard.tag.filter((tag) => !tag.toLowerCase().includes("impact")).length > 3 && ( - - +{standard.tag.filter((tag) => !tag.toLowerCase().includes("impact")).length - 3} more - - )} + ( + + {children} + + ), + p: ({ children }) => ( + + {children} + + ), + }} + > + {standard.helpText} + )} - {standard.recommendedBy?.length > 0 && ( - - • Recommended by: {standard.recommendedBy.join(", ")} - - )} - {standard.addedDate && ( - - • Added: {standard.addedDate} - - )} + + {standard.tag?.filter((tag) => !tag.toLowerCase().includes("impact")).length > + 0 && ( + + {standard.tag + .filter((tag) => !tag.toLowerCase().includes("impact")) + .slice(0, 3) // Show only first 3 tags to save space + .map((tag, idx) => ( + + ))} + {standard.tag.filter((tag) => !tag.toLowerCase().includes("impact")) + .length > 3 && ( + + + + {standard.tag.filter((tag) => !tag.toLowerCase().includes("impact")) + .length - 3}{" "} + more + + )} + + )} + {standard.recommendedBy?.length > 0 && ( + + • Recommended by: {standard.recommendedBy.join(", ")} + + )} + {standard.addedDate && ( + + • Added: {standard.addedDate} + + )} + - - } - /> - - {standard.multiple ? ( - handleAddClick(standard.name)} - sx={{ mr: 1 }} - > - - - ) : ( - - } - label="" - sx={{ mr: 1 }} - /> - )} - - - ); - })} - - ); -}); + } + /> + + {standard.multiple ? ( + handleAddClick(standard.name)} + sx={{ mr: 1 }} + > + + + ) : ( + + } + label="" + sx={{ mr: 1 }} + /> + )} + + + ); + })} + + ); + } +); CompactStandardList.displayName = "CompactStandardList"; @@ -548,7 +554,7 @@ const CippStandardDialog = ({ const [localSearchQuery, setLocalSearchQuery] = useState(""); const [isInitialLoading, setIsInitialLoading] = useState(true); const [viewMode, setViewMode] = useState("card"); // "card" or "list" - + // Enhanced filtering and sorting state const [sortBy, setSortBy] = useState("addedDate"); // Default sort by date added const [sortOrder, setSortOrder] = useState("desc"); // desc to show newest first @@ -577,43 +583,44 @@ const CippStandardDialog = ({ const recommendedBySet = new Set(); const tagFrameworkSet = new Set(); - // Function to extract base framework from tag - const extractTagFramework = (tag) => { - // Compliance Frameworks - extract version dynamically - if (tag.startsWith('CIS M365')) { - const versionMatch = tag.match(/CIS M365 (\d+\.\d+)/); - return versionMatch ? `CIS M365 ${versionMatch[1]}` : 'CIS M365'; - } - if (tag.startsWith('CISA ')) return 'CISA'; - if (tag.startsWith('EIDSCA.')) return 'EIDSCA'; - if (tag.startsWith('Essential 8')) return 'Essential 8'; - if (tag.startsWith('NIST CSF')) { - const versionMatch = tag.match(/NIST CSF (\d+\.\d+)/); - return versionMatch ? `NIST CSF ${versionMatch[1]}` : 'NIST CSF'; - } - - // Microsoft Secure Score Categories - if (tag.startsWith('exo_')) return 'Secure Score - Exchange'; - if (tag.startsWith('mdo_')) return 'Secure Score - Defender'; - if (tag.startsWith('spo_')) return 'Secure Score - SharePoint'; - if (tag.startsWith('mip_')) return 'Secure Score - Purview'; - - // For any other tags, return null to exclude them - return null; - }; + // Function to extract base framework from tag + const extractTagFramework = (tag) => { + // Compliance Frameworks - extract version dynamically + if (tag.startsWith("CIS M365")) { + const versionMatch = tag.match(/CIS M365 (\d+\.\d+)/); + return versionMatch ? `CIS M365 ${versionMatch[1]}` : "CIS M365"; + } + if (tag.startsWith("CISA ")) return "CISA"; + if (tag.startsWith("EIDSCA.")) return "EIDSCA"; + if (tag.startsWith("Essential 8")) return "Essential 8"; + if (tag.startsWith("NIST CSF")) { + const versionMatch = tag.match(/NIST CSF (\d+\.\d+)/); + return versionMatch ? `NIST CSF ${versionMatch[1]}` : "NIST CSF"; + } + + // Microsoft Secure Score Categories + if (tag.startsWith("exo_")) return "Secure Score - Exchange"; + if (tag.startsWith("mdo_")) return "Secure Score - Defender"; + if (tag.startsWith("spo_")) return "Secure Score - SharePoint"; + if (tag.startsWith("mip_")) return "Secure Score - Purview"; + + // For any other tags, return null to exclude them + return null; + }; Object.keys(categories).forEach((category) => { categorySet.add(category); categories[category].forEach((standard) => { if (standard.impact) impactSet.add(standard.impact); if (standard.recommendedBy && Array.isArray(standard.recommendedBy)) { - standard.recommendedBy.forEach(rec => recommendedBySet.add(rec)); + standard.recommendedBy.forEach((rec) => recommendedBySet.add(rec)); } // Process tags to extract frameworks if (standard.tag && Array.isArray(standard.tag)) { - standard.tag.forEach(tag => { + standard.tag.forEach((tag) => { const framework = extractTagFramework(tag); - if (framework) { // Only add non-null frameworks + if (framework) { + // Only add non-null frameworks tagFrameworkSet.add(framework); } }); @@ -633,23 +640,23 @@ const CippStandardDialog = ({ const sortedTagFrameworks = Array.from(tagFrameworkSet).sort((a, b) => { // Define priority groups const getFrameworkPriority = (framework) => { - if (framework.startsWith('CIS M365')) return 1; - if (framework === 'CISA') return 2; - if (framework === 'EIDSCA') return 3; - if (framework === 'Essential 8') return 4; - if (framework.startsWith('NIST CSF')) return 5; - if (framework.startsWith('Secure Score -')) return 6; + if (framework.startsWith("CIS M365")) return 1; + if (framework === "CISA") return 2; + if (framework === "EIDSCA") return 3; + if (framework === "Essential 8") return 4; + if (framework.startsWith("NIST CSF")) return 5; + if (framework.startsWith("Secure Score -")) return 6; return 999; // Other tags go last }; - + const aPriority = getFrameworkPriority(a); const bPriority = getFrameworkPriority(b); - + // If different priorities, sort by priority if (aPriority !== bPriority) { return aPriority - bPriority; } - + // If same priority, sort alphabetically return a.localeCompare(b); }); @@ -663,113 +670,143 @@ const CippStandardDialog = ({ }, [categories]); // Enhanced filter function - const enhancedFilterStandards = useCallback((standardsList) => { - // Function to extract base framework from tag (same as in useMemo) + const enhancedFilterStandards = useCallback( + (standardsList) => { + // Function to extract base framework from tag (same as in useMemo) const extractTagFramework = (tag) => { // Compliance Frameworks - extract version dynamically - if (tag.startsWith('CIS M365')) { + if (tag.startsWith("CIS M365")) { const versionMatch = tag.match(/CIS M365 (\d+\.\d+)/); - return versionMatch ? `CIS M365 ${versionMatch[1]}` : 'CIS M365'; + return versionMatch ? `CIS M365 ${versionMatch[1]}` : "CIS M365"; } - if (tag.startsWith('CISA ')) return 'CISA'; - if (tag.startsWith('EIDSCA.')) return 'EIDSCA'; - if (tag.startsWith('Essential 8')) return 'Essential 8'; - if (tag.startsWith('NIST CSF')) { + if (tag.startsWith("CISA ")) return "CISA"; + if (tag.startsWith("EIDSCA.")) return "EIDSCA"; + if (tag.startsWith("Essential 8")) return "Essential 8"; + if (tag.startsWith("NIST CSF")) { const versionMatch = tag.match(/NIST CSF (\d+\.\d+)/); - return versionMatch ? `NIST CSF ${versionMatch[1]}` : 'NIST CSF'; + return versionMatch ? `NIST CSF ${versionMatch[1]}` : "NIST CSF"; } - + // Microsoft Secure Score Categories - if (tag.startsWith('exo_')) return 'Secure Score - Exchange'; - if (tag.startsWith('mdo_')) return 'Secure Score - Defender'; - if (tag.startsWith('spo_')) return 'Secure Score - SharePoint'; - if (tag.startsWith('mip_')) return 'Secure Score - Purview'; - + if (tag.startsWith("exo_")) return "Secure Score - Exchange"; + if (tag.startsWith("mdo_")) return "Secure Score - Defender"; + if (tag.startsWith("spo_")) return "Secure Score - SharePoint"; + if (tag.startsWith("mip_")) return "Secure Score - Purview"; + // For any other tags, return null to exclude them return null; }; - return standardsList.filter((standard) => { - // Original text search - const matchesSearch = !localSearchQuery || - standard.label.toLowerCase().includes(localSearchQuery.toLowerCase()) || - standard.helpText.toLowerCase().includes(localSearchQuery.toLowerCase()) || - (standard.tag && standard.tag.some((tag) => - tag.toLowerCase().includes(localSearchQuery.toLowerCase()) - )); - - // Category filter - const matchesCategory = selectedCategories.length === 0 || - selectedCategories.includes(standard.cat); - - // Impact filter - const matchesImpact = selectedImpacts.length === 0 || - selectedImpacts.includes(standard.impact); - - // Recommended by filter - const matchesRecommendedBy = selectedRecommendedBy.length === 0 || - (standard.recommendedBy && Array.isArray(standard.recommendedBy) && - standard.recommendedBy.some(rec => selectedRecommendedBy.includes(rec))); - - // Tag framework filter - const matchesTagFramework = selectedTagFrameworks.length === 0 || - (standard.tag && Array.isArray(standard.tag) && - standard.tag.some(tag => { - const framework = extractTagFramework(tag); - return framework && selectedTagFrameworks.includes(framework); - })); - - // New standards filter (last 30 days) - const isNewStandard = (dateAdded) => { - if (!dateAdded) return false; - const currentDate = new Date(); - const addedDate = new Date(dateAdded); - return differenceInDays(currentDate, addedDate) <= 30; - }; - const matchesNewFilter = !showOnlyNew || isNewStandard(standard.addedDate); + return standardsList.filter((standard) => { + // Original text search + const matchesSearch = + !localSearchQuery || + standard.label.toLowerCase().includes(localSearchQuery.toLowerCase()) || + standard.helpText.toLowerCase().includes(localSearchQuery.toLowerCase()) || + (standard.tag && + standard.tag.some((tag) => tag.toLowerCase().includes(localSearchQuery.toLowerCase()))); + + // Category filter + const matchesCategory = + selectedCategories.length === 0 || selectedCategories.includes(standard.cat); + + // Impact filter + const matchesImpact = + selectedImpacts.length === 0 || selectedImpacts.includes(standard.impact); + + // Recommended by filter + const matchesRecommendedBy = + selectedRecommendedBy.length === 0 || + (standard.recommendedBy && + Array.isArray(standard.recommendedBy) && + standard.recommendedBy.some((rec) => selectedRecommendedBy.includes(rec))); + + // Tag framework filter + const matchesTagFramework = + selectedTagFrameworks.length === 0 || + (standard.tag && + Array.isArray(standard.tag) && + standard.tag.some((tag) => { + const framework = extractTagFramework(tag); + return framework && selectedTagFrameworks.includes(framework); + })); + + // New standards filter (last 30 days) + const isNewStandard = (dateAdded) => { + if (!dateAdded) return false; + const currentDate = new Date(); + const addedDate = new Date(dateAdded); + return differenceInDays(currentDate, addedDate) <= 30; + }; + const matchesNewFilter = !showOnlyNew || isNewStandard(standard.addedDate); - return matchesSearch && matchesCategory && matchesImpact && matchesRecommendedBy && matchesTagFramework && matchesNewFilter; - }); - }, [localSearchQuery, selectedCategories, selectedImpacts, selectedRecommendedBy, selectedTagFrameworks, showOnlyNew]); + return ( + matchesSearch && + matchesCategory && + matchesImpact && + matchesRecommendedBy && + matchesTagFramework && + matchesNewFilter + ); + }); + }, + [ + localSearchQuery, + selectedCategories, + selectedImpacts, + selectedRecommendedBy, + selectedTagFrameworks, + showOnlyNew, + ] + ); // Enhanced sort function - const sortStandards = useCallback((standardsList) => { - return [...standardsList].sort((a, b) => { - let aValue, bValue; - - switch (sortBy) { - case "label": - aValue = a.label.toLowerCase(); - bValue = b.label.toLowerCase(); - break; - case "addedDate": - aValue = new Date(a.addedDate || "1900-01-01"); - bValue = new Date(b.addedDate || "1900-01-01"); - break; - case "category": - aValue = a.cat?.toLowerCase() || ""; - bValue = b.cat?.toLowerCase() || ""; - break; - case "impact": - // Sort by impact priority: High > Medium > Low - const impactOrder = { "High Impact": 3, "Medium Impact": 2, "Low Impact": 1 }; - aValue = impactOrder[a.impact] || 0; - bValue = impactOrder[b.impact] || 0; - break; - case "recommendedBy": - aValue = (a.recommendedBy && a.recommendedBy.length > 0) ? a.recommendedBy.join(", ").toLowerCase() : ""; - bValue = (b.recommendedBy && b.recommendedBy.length > 0) ? b.recommendedBy.join(", ").toLowerCase() : ""; - break; - default: - aValue = a.label.toLowerCase(); - bValue = b.label.toLowerCase(); - } + const sortStandards = useCallback( + (standardsList) => { + return [...standardsList].sort((a, b) => { + let aValue, bValue; + + switch (sortBy) { + case "label": + aValue = a.label.toLowerCase(); + bValue = b.label.toLowerCase(); + break; + case "addedDate": + aValue = new Date(a.addedDate || "1900-01-01"); + bValue = new Date(b.addedDate || "1900-01-01"); + break; + case "category": + aValue = a.cat?.toLowerCase() || ""; + bValue = b.cat?.toLowerCase() || ""; + break; + case "impact": + // Sort by impact priority: High > Medium > Low + const impactOrder = { "High Impact": 3, "Medium Impact": 2, "Low Impact": 1 }; + aValue = impactOrder[a.impact] || 0; + bValue = impactOrder[b.impact] || 0; + break; + case "recommendedBy": + aValue = + a.recommendedBy && a.recommendedBy.length > 0 + ? a.recommendedBy.join(", ").toLowerCase() + : ""; + bValue = + b.recommendedBy && b.recommendedBy.length > 0 + ? b.recommendedBy.join(", ").toLowerCase() + : ""; + break; + default: + aValue = a.label.toLowerCase(); + bValue = b.label.toLowerCase(); + } - if (aValue < bValue) return sortOrder === "asc" ? -1 : 1; - if (aValue > bValue) return sortOrder === "asc" ? 1 : -1; - return 0; - }); - }, [sortBy, sortOrder]); + if (aValue < bValue) return sortOrder === "asc" ? -1 : 1; + if (aValue > bValue) return sortOrder === "asc" ? 1 : -1; + return 0; + }); + }, + [sortBy, sortOrder] + ); // Optimize handleAddClick to be more performant const handleAddClick = useCallback( @@ -845,7 +882,7 @@ const CippStandardDialog = ({ Object.keys(categories).forEach((category) => { const categoryStandards = categories[category]; const filteredStandards = enhancedFilterStandards(categoryStandards); - + filteredStandards.forEach((standard) => { allItems.push({ standard, @@ -855,10 +892,12 @@ const CippStandardDialog = ({ }); // Apply sorting to the final combined array instead of per-category - const sortedAllItems = sortStandards(allItems.map(item => item.standard)).map(standard => { - const item = allItems.find(item => item.standard.name === standard.name); - return item; - }); + const sortedAllItems = sortStandards(allItems.map((item) => item.standard)).map( + (standard) => { + const item = allItems.find((item) => item.standard.name === standard.name); + return item; + } + ); setProcessedItems(sortedAllItems); setIsInitialLoading(false); @@ -897,7 +936,12 @@ const CippStandardDialog = ({ ); // Count active filters - const activeFiltersCount = selectedCategories.length + selectedImpacts.length + selectedRecommendedBy.length + selectedTagFrameworks.length + (showOnlyNew ? 1 : 0); + const activeFiltersCount = + selectedCategories.length + + selectedImpacts.length + + selectedRecommendedBy.length + + selectedTagFrameworks.length + + (showOnlyNew ? 1 : 0); // Don't render dialog contents until it's actually open (improves performance) return ( @@ -925,12 +969,12 @@ const CippStandardDialog = ({ {/* Search and Filter Controls */} {/* Search Box */} - @@ -941,49 +985,57 @@ const CippStandardDialog = ({ setFiltersExpanded(!filtersExpanded)} sx={{ - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', + display: "flex", + alignItems: "center", + justifyContent: "space-between", py: 0.75, px: 1, - borderRadius: 1, - cursor: 'pointer', - bgcolor: 'action.hover', - border: '1px solid', - borderColor: 'divider', - '&:hover': { - bgcolor: 'action.selected' - } + borderRadius: filtersExpanded ? "4px 4px 0 0" : 1, + cursor: "pointer", + bgcolor: "action.hover", + border: "1px solid", + borderColor: "divider", + borderBottom: filtersExpanded ? "none" : "none", + "&:hover": { + bgcolor: "action.selected", + }, }} > - - - + + + View, Sort & Filter Options {!filtersExpanded && ( - ({viewMode === 'card' ? 'Card' : 'List'} • {sortBy === 'addedDate' ? 'Date' : 'Name'} {sortOrder === 'desc' ? '↓' : '↑'}{activeFiltersCount > 0 ? ` • ${activeFiltersCount} filter${activeFiltersCount !== 1 ? 's' : ''}` : ''}) + ({viewMode === "card" ? "Card" : "List"} •{" "} + {sortBy === "addedDate" ? "Date" : "Name"} {sortOrder === "desc" ? "↓" : "↑"} + {activeFiltersCount > 0 + ? ` • ${activeFiltersCount} filter${activeFiltersCount !== 1 ? "s" : ""}` + : ""} + ) )} {filtersExpanded ? : } - + {/* Single line controls when expanded */} - + {/* View Mode */} Date Added - + Order { + const file = e.target.files[0]; + field.onChange(file); + if (other.onChange) { + other.onChange(file); + } + }} + /> + + )} + /> +
+ + {get(errors, convertedName, {})?.message} + + {helperText && ( + + {helperText} + + )} + + ); + default: return null; } From a0464cd854443267d0a122bdbf8e60b2d5bb9b68 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sat, 12 Jul 2025 01:51:01 -0400 Subject: [PATCH 091/112] Add template creation --- .../applications/app-registrations.js | 65 +++++++++++++++++-- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/src/pages/tenant/administration/applications/app-registrations.js b/src/pages/tenant/administration/applications/app-registrations.js index 0f0711c07d1f..7a2e0cc6eff2 100644 --- a/src/pages/tenant/administration/applications/app-registrations.js +++ b/src/pages/tenant/administration/applications/app-registrations.js @@ -4,7 +4,8 @@ import { TabbedLayout } from "/src/layouts/TabbedLayout"; import { CippTablePage } from "/src/components/CippComponents/CippTablePage.jsx"; import { CippFormComponent } from "/src/components/CippComponents/CippFormComponent.jsx"; import { CertificateCredentialRemovalForm } from "/src/components/CippComponents/CertificateCredentialRemovalForm.jsx"; -import { Launch, Delete, Edit, Key, Security, Block, CheckCircle } from "@mui/icons-material"; +import CippPermissionPreview from "/src/components/CippComponents/CippPermissionPreview.jsx"; +import { Launch, Delete, Edit, Key, Security, Block, CheckCircle, Save } from "@mui/icons-material"; import { usePermissions } from "/src/hooks/use-permissions.js"; import tabOptions from "./tabOptions"; @@ -88,6 +89,53 @@ const Page = () => { confirmText: "Are you sure you want to remove the selected certificate credentials?", condition: (row) => canWriteApplication && row?.keyCredentials?.length > 0, }, + { + icon: , + label: "Create Template from App Registration", + type: "POST", + color: "success", + multiPost: false, + url: "/api/ExecAppApprovalTemplate", + fields: [ + { + label: "Template Name", + name: "TemplateName", + type: "textField", + placeholder: "Enter a name for the template", + required: true, + validators: { + required: { value: true, message: "Template name is required" }, + }, + }, + ], + customDataformatter: (row, action, formData) => { + console.log("Creating template from app registration:", row, formData); + const propertiesToRemove = [ + "appId", + "id", + "createdDateTime", + "publisherDomain", + "servicePrincipalLockConfiguration", + "identifierUris", + "applicationIdUris", + ]; + + const cleanManifest = { ...row }; + propertiesToRemove.forEach((prop) => { + delete cleanManifest[prop]; + }); + + return { + Action: "Save", + TemplateName: formData.TemplateName, + AppType: "ApplicationManifest", + AppName: row.displayName || row.appId, + ApplicationManifest: cleanManifest, + }; + }, + confirmText: "Are you sure you want to create a template from this app registration?", + condition: () => canWriteApplication, + }, { icon: , label: "Delete App Registration", @@ -115,13 +163,20 @@ const Page = () => { "signInAudience", "disabledByMicrosoftStatus", "replyUrls", - "requiredResourceAccess", - "web", - "api", "passwordCredentials", "keyCredentials", ], actions: actions, + children: (row) => { + return ( + + ); + }, }; const simpleColumns = [ @@ -137,8 +192,6 @@ const Page = () => { const apiParams = { Endpoint: "applications", - $select: - "id,appId,displayName,createdDateTime,signInAudience,disabledByMicrosoftStatus,web,api,requiredResourceAccess,publisherDomain,replyUrls,passwordCredentials,keyCredentials", $count: true, $top: 999, }; From eb64e9a35062b0dcd6f291dd008841463b0b81d2 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sat, 12 Jul 2025 01:52:54 -0400 Subject: [PATCH 092/112] Update app-registrations.js --- .../tenant/administration/applications/app-registrations.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/tenant/administration/applications/app-registrations.js b/src/pages/tenant/administration/applications/app-registrations.js index 7a2e0cc6eff2..0143400a00c6 100644 --- a/src/pages/tenant/administration/applications/app-registrations.js +++ b/src/pages/tenant/administration/applications/app-registrations.js @@ -109,7 +109,6 @@ const Page = () => { }, ], customDataformatter: (row, action, formData) => { - console.log("Creating template from app registration:", row, formData); const propertiesToRemove = [ "appId", "id", @@ -118,6 +117,8 @@ const Page = () => { "servicePrincipalLockConfiguration", "identifierUris", "applicationIdUris", + "Tenant", + "CippStatus", ]; const cleanManifest = { ...row }; From 047941584b290ed1915c6debb8767fc4b4af92cf Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sat, 12 Jul 2025 01:53:57 -0400 Subject: [PATCH 093/112] Update app-registrations.js --- .../tenant/administration/applications/app-registrations.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/tenant/administration/applications/app-registrations.js b/src/pages/tenant/administration/applications/app-registrations.js index 0143400a00c6..354d83651f8d 100644 --- a/src/pages/tenant/administration/applications/app-registrations.js +++ b/src/pages/tenant/administration/applications/app-registrations.js @@ -113,6 +113,7 @@ const Page = () => { "appId", "id", "createdDateTime", + "deletedDateTime", "publisherDomain", "servicePrincipalLockConfiguration", "identifierUris", From 21d85b2ad3a6e1abd3e72fc036b41443ae11f0ac Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sat, 12 Jul 2025 01:58:54 -0400 Subject: [PATCH 094/112] Update app-registrations.js --- .../tenant/administration/applications/app-registrations.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/tenant/administration/applications/app-registrations.js b/src/pages/tenant/administration/applications/app-registrations.js index 354d83651f8d..c311e1b56420 100644 --- a/src/pages/tenant/administration/applications/app-registrations.js +++ b/src/pages/tenant/administration/applications/app-registrations.js @@ -136,7 +136,7 @@ const Page = () => { }; }, confirmText: "Are you sure you want to create a template from this app registration?", - condition: () => canWriteApplication, + condition: (row) => canWriteApplication && row.signInAudience === "AzureADMyOrg", }, { icon: , From 15a7b644f0934e14bcd7013a0ed22354efd6574c Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sat, 12 Jul 2025 12:32:03 +0200 Subject: [PATCH 095/112] fixes richText --- .../CippComponents/CippFormComponent.jsx | 72 ++++++++++++------- 1 file changed, 47 insertions(+), 25 deletions(-) diff --git a/src/components/CippComponents/CippFormComponent.jsx b/src/components/CippComponents/CippFormComponent.jsx index 3e7ba4393024..7ddc2965b0b0 100644 --- a/src/components/CippComponents/CippFormComponent.jsx +++ b/src/components/CippComponents/CippFormComponent.jsx @@ -330,7 +330,10 @@ export const CippFormComponent = (props) => { ); - case "richText": + case "richText": { + const editorInstanceRef = React.useRef(null); + const hasSetInitialValue = React.useRef(false); + return ( <>
@@ -338,29 +341,48 @@ export const CippFormComponent = (props) => { name={convertedName} control={formControl.control} rules={validators} - render={({ field }) => ( - <> - {label} - { - field.onChange(editor.getHTML()); - }} - label={label} - renderControls={() => ( - - - - - - - )} - /> - - )} + render={({ field }) => { + const { value, onChange, ref } = field; + + // Set content only once on first render + React.useEffect(() => { + if ( + editorInstanceRef.current && + !hasSetInitialValue.current && + typeof value === "string" + ) { + editorInstanceRef.current.commands.setContent(value || "", false); + hasSetInitialValue.current = true; + } + }, [value]); + + return ( + <> + {label} + { + editorInstanceRef.current = editor; + }} + onUpdate={({ editor }) => { + onChange(editor.getHTML()); + }} + label={label} + renderControls={() => ( + + + + + + + )} + /> + + ); + }} />
@@ -368,7 +390,7 @@ export const CippFormComponent = (props) => { ); - + } case "CSVReader": const remapData = (data, nameToCSVMapping) => { if (nameToCSVMapping && data) { From 2c29cb58adf5a9ed92d526a9a139640fc137a3ca Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sat, 12 Jul 2025 13:10:08 +0200 Subject: [PATCH 096/112] compliance stuff --- src/components/ExecutiveReportButton.js | 433 ++++++++++++------------ 1 file changed, 219 insertions(+), 214 deletions(-) diff --git a/src/components/ExecutiveReportButton.js b/src/components/ExecutiveReportButton.js index 40f6034aa67b..381841444922 100644 --- a/src/components/ExecutiveReportButton.js +++ b/src/components/ExecutiveReportButton.js @@ -35,7 +35,6 @@ const ExecutiveReportDocument = ({ month: "long", day: "numeric", }); - const brandColor = brandingSettings?.colour || "#F77F00"; // ENTERPRISE DESIGN SYSTEM - JOBS/RAMS/IVE PRINCIPLES @@ -1396,8 +1395,8 @@ const ExecutiveReportDocument = ({ { deviceData.filter( (device) => - device.complianceState === "Compliant" || - device.ComplianceState === "Compliant" + device.complianceState === "compliant" || + device.ComplianceState === "compliant" ).length } @@ -1408,8 +1407,8 @@ const ExecutiveReportDocument = ({ { deviceData.filter( (device) => - device.complianceState !== "Compliant" && - device.ComplianceState !== "Compliant" + device.complianceState !== "compliant" && + device.ComplianceState !== "compliant" ).length } @@ -1527,240 +1526,244 @@ const ExecutiveReportDocument = ({ )} {/* CONDITIONAL ACCESS POLICIES PAGE - Only show if data is available */} - {conditionalAccessData && Array.isArray(conditionalAccessData) && conditionalAccessData.length > 0 && ( - <> - {/* STATISTIC PAGE 5 - CHAPTER SPLITTER */} - - - - 277 - days - - average time to identify and{"\n"} - contain a data breach - - - - Early detection minimizes{"\n"} - business impact - - - - - - Conditional Access Policies - - Identity and access management security controls + {conditionalAccessData && + Array.isArray(conditionalAccessData) && + conditionalAccessData.length > 0 && ( + <> + {/* STATISTIC PAGE 5 - CHAPTER SPLITTER */} + + + + 277 + days + + average time to identify and{"\n"} + contain a data breach - {brandingSettings?.logo && ( - - )} - - - - - Access control policies help protect your business by ensuring only the right people - can access sensitive information under appropriate circumstances. These smart - security measures automatically evaluate each access request and apply additional - verification when needed, balancing security with employee productivity. + + Early detection minimizes{"\n"} + business impact - + + + + + Conditional Access Policies + + Identity and access management security controls + + + {brandingSettings?.logo && ( + + )} + - - How Access Controls Protect Your Business - - These policies work like intelligent security guards, making decisions based on who - is trying to access what, from where, and when. For example, accessing email from - the office might be seamless, but accessing it from an unusual location might - require additional verification. This approach protects your data while minimizing - disruption to daily work. - - + + + Access control policies help protect your business by ensuring only the right + people can access sensitive information under appropriate circumstances. These + smart security measures automatically evaluate each access request and apply + additional verification when needed, balancing security with employee + productivity. + + - - Current Policy Configuration + + How Access Controls Protect Your Business + + These policies work like intelligent security guards, making decisions based on + who is trying to access what, from where, and when. For example, accessing email + from the office might be seamless, but accessing it from an unusual location might + require additional verification. This approach protects your data while minimizing + disruption to daily work. + + - - - Policy Name - State - Applications - Controls - + + Current Policy Configuration - {conditionalAccessData.slice(0, 8).map((policy, index) => { - const getStateStyle = (state) => { - switch (state) { - case "enabled": - return styles.statusCompliant; - case "enabledForReportingButNotEnforced": - return styles.statusPartial; - case "disabled": - return styles.statusReview; - default: - return styles.statusText; - } - }; - - const getStateDisplay = (state) => { - switch (state) { - case "enabled": - return "Enabled"; - case "enabledForReportingButNotEnforced": - return "Report Only"; - case "disabled": - return "Disabled"; - default: - return state || "Unknown"; - } - }; - - const getControlsText = (policy) => { - const controls = []; - if (policy.builtInControls) { - if (policy.builtInControls.includes("mfa")) controls.push("MFA"); - if (policy.builtInControls.includes("block")) controls.push("Block"); - if (policy.builtInControls.includes("compliantDevice")) - controls.push("Compliant Device"); - } - return controls.length > 0 ? controls.join(", ") : "Custom"; - }; + + + Policy Name + State + Applications + Controls + - return ( - - - {policy.displayName || "N/A"} - - - - {getStateDisplay(policy.state)} + {conditionalAccessData.slice(0, 8).map((policy, index) => { + const getStateStyle = (state) => { + switch (state) { + case "enabled": + return styles.statusCompliant; + case "enabledForReportingButNotEnforced": + return styles.statusPartial; + case "disabled": + return styles.statusReview; + default: + return styles.statusText; + } + }; + + const getStateDisplay = (state) => { + switch (state) { + case "enabled": + return "Enabled"; + case "enabledForReportingButNotEnforced": + return "Report Only"; + case "disabled": + return "Disabled"; + default: + return state || "Unknown"; + } + }; + + const getControlsText = (policy) => { + const controls = []; + if (policy.builtInControls) { + if (policy.builtInControls.includes("mfa")) controls.push("MFA"); + if (policy.builtInControls.includes("block")) controls.push("Block"); + if (policy.builtInControls.includes("compliantDevice")) + controls.push("Compliant Device"); + } + return controls.length > 0 ? controls.join(", ") : "Custom"; + }; + + return ( + + + {policy.displayName || "N/A"} + + + + {getStateDisplay(policy.state)} + + + + {policy.includeApplications || "All"} + + + {getControlsText(policy)} - - {policy.includeApplications || "All"} - - - {getControlsText(policy)} - - - ); - })} + ); + })} + - - - Policy Overview + + Policy Overview - - - {conditionalAccessData.length} - Total Policies - - - - {conditionalAccessData.filter((policy) => policy.state === "enabled").length} - - Enabled - - - - { - conditionalAccessData.filter( - (policy) => policy.state === "enabledForReportingButNotEnforced" - ).length - } - - Report Only - - - - { - conditionalAccessData.filter( - (policy) => policy.builtInControls && policy.builtInControls.includes("mfa") - ).length - } - - MFA Policies + + + {conditionalAccessData.length} + Total Policies + + + + {conditionalAccessData.filter((policy) => policy.state === "enabled").length} + + Enabled + + + + { + conditionalAccessData.filter( + (policy) => policy.state === "enabledForReportingButNotEnforced" + ).length + } + + Report Only + + + + { + conditionalAccessData.filter( + (policy) => + policy.builtInControls && policy.builtInControls.includes("mfa") + ).length + } + + MFA Policies + - - - Policy Analysis + + Policy Analysis - - - - - Policy Coverage:{" "} - {conditionalAccessData.length} conditional access policies configured - - - - - - Enforcement Status:{" "} - {conditionalAccessData.filter((policy) => policy.state === "enabled").length}{" "} - policies actively enforced - - - - - - Testing Phase:{" "} - { - conditionalAccessData.filter( - (policy) => policy.state === "enabledForReportingButNotEnforced" - ).length - }{" "} - policies in report-only mode - - - - - - Security Controls: Multi-factor - authentication and access blocking implemented - + + + + + Policy Coverage:{" "} + {conditionalAccessData.length} conditional access policies configured + + + + + + Enforcement Status:{" "} + {conditionalAccessData.filter((policy) => policy.state === "enabled").length}{" "} + policies actively enforced + + + + + + Testing Phase:{" "} + { + conditionalAccessData.filter( + (policy) => policy.state === "enabledForReportingButNotEnforced" + ).length + }{" "} + policies in report-only mode + + + + + + Security Controls:{" "} + Multi-factor authentication and access blocking implemented + + - - - Access Control Recommendations - - {conditionalAccessData.filter( - (policy) => policy.state === "enabledForReportingButNotEnforced" - ).length > 0 - ? `Consider activating ${ - conditionalAccessData.filter( - (policy) => policy.state === "enabledForReportingButNotEnforced" - ).length - } policies currently in testing mode after ensuring they don't disrupt business operations. ` - : "Your access controls are properly configured. "} - Regularly review how these policies affect employee productivity and adjust as - needed. Consider additional location-based protections for enhanced security without - impacting daily operations. - - + + Access Control Recommendations + + {conditionalAccessData.filter( + (policy) => policy.state === "enabledForReportingButNotEnforced" + ).length > 0 + ? `Consider activating ${ + conditionalAccessData.filter( + (policy) => policy.state === "enabledForReportingButNotEnforced" + ).length + } policies currently in testing mode after ensuring they don't disrupt business operations. ` + : "Your access controls are properly configured. "} + Regularly review how these policies affect employee productivity and adjust as + needed. Consider additional location-based protections for enhanced security + without impacting daily operations. + + - - `Page ${pageNumber} of ${totalPages}`} - /> - - - - )} + + `Page ${pageNumber} of ${totalPages}`} + /> + + + + )} ); }; export const ExecutiveReportButton = (props) => { const { tenantName, tenantId, userStats, standardsData, organizationData, ...other } = props; - + console.log(props); const settings = useSettings(); const brandingSettings = settings.customBranding; @@ -1809,7 +1812,7 @@ export const ExecutiveReportButton = (props) => { deviceData.isFetching || conditionalAccessData.isFetching || standardsCompareData.isFetching; - + const hasAllDataFinished = (secureScore.isSuccess || secureScore.isError) && (licenseData.isSuccess || licenseData.isError) && @@ -1860,7 +1863,9 @@ export const ExecutiveReportButton = (props) => { secureScoreData={secureScore.isSuccess ? secureScore : null} licensingData={licenseData.isSuccess ? licenseData?.data : null} deviceData={deviceData.isSuccess ? deviceData?.data : null} - conditionalAccessData={conditionalAccessData.isSuccess ? conditionalAccessData?.data : null} + conditionalAccessData={ + conditionalAccessData.isSuccess ? conditionalAccessData?.data : null + } standardsCompareData={standardsCompareData.isSuccess ? standardsCompareData?.data : null} /> } From 288c8e41114e72737812599416a83bb806444fd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Thu, 10 Jul 2025 23:04:41 +0200 Subject: [PATCH 097/112] Add loading skeleton for contact edit and template pages --- .../administration/contacts-template/edit.jsx | 74 ++-- .../email/administration/contacts/edit.jsx | 363 +++++++++--------- 2 files changed, 226 insertions(+), 211 deletions(-) diff --git a/src/pages/email/administration/contacts-template/edit.jsx b/src/pages/email/administration/contacts-template/edit.jsx index 7b7d9a5840fe..100eebe46731 100644 --- a/src/pages/email/administration/contacts-template/edit.jsx +++ b/src/pages/email/administration/contacts-template/edit.jsx @@ -2,43 +2,45 @@ import { useEffect, useMemo, useCallback } from "react"; import { useForm } from "react-hook-form"; import { Layout as DashboardLayout } from "/src/layouts/index.js"; import CippFormPage from "/src/components/CippFormPages/CippFormPage"; +import CippFormSkeleton from "/src/components/CippFormPages/CippFormSkeleton"; import ContactFormLayout from "/src/components/CippFormPages/CippAddEditContact"; import { ApiGetCall } from "../../../../api/ApiCall"; import countryList from "/src/data/countryList.json"; import { useRouter } from "next/router"; -const countryLookup = new Map( - countryList.map(country => [country.Name, country.Code]) -); +const countryLookup = new Map(countryList.map((country) => [country.Name, country.Code])); const EditContactTemplate = () => { const router = useRouter(); const { id } = router.query; - + const contactTemplateInfo = ApiGetCall({ url: `/api/ListContactTemplates?id=${id}`, queryKey: `ListContactTemplates-${id}`, waiting: !!id, }); - const defaultFormValues = useMemo(() => ({ - displayName: "", - firstName: "", - lastName: "", - email: "", - hidefromGAL: false, - streetAddress: "", - postalCode: "", - city: "", - state: "", - country: "", - companyName: "", - mobilePhone: "", - businessPhone: "", - jobTitle: "", - website: "", - mailTip: "", - }), []); + const defaultFormValues = useMemo( + () => ({ + displayName: "", + firstName: "", + lastName: "", + email: "", + hidefromGAL: false, + streetAddress: "", + postalCode: "", + city: "", + state: "", + country: "", + companyName: "", + mobilePhone: "", + businessPhone: "", + jobTitle: "", + website: "", + mailTip: "", + }), + [] + ); const formControl = useForm({ mode: "onChange", @@ -52,12 +54,14 @@ const EditContactTemplate = () => { } // Handle both single object (when fetching by ID) and array responses - const contact = Array.isArray(contactTemplateInfo.data) ? contactTemplateInfo.data[0] : contactTemplateInfo.data; + const contact = Array.isArray(contactTemplateInfo.data) + ? contactTemplateInfo.data[0] + : contactTemplateInfo.data; const address = contact.addresses?.[0] || {}; const phones = contact.phones || []; - + // Use Map for O(1) phone lookup - const phoneMap = new Map(phones.map(p => [p.type, p.number])); + const phoneMap = new Map(phones.map((p) => [p.type, p.number])); return { ContactTemplateID: id || "", @@ -70,9 +74,7 @@ const EditContactTemplate = () => { postalCode: address.postalCode || "", city: address.city || "", state: address.state || "", - country: address.countryOrRegion - ? countryLookup.get(address.countryOrRegion) || "" - : "", + country: address.countryOrRegion ? countryLookup.get(address.countryOrRegion) || "" : "", companyName: contact.companyName || "", mobilePhone: phoneMap.get("mobile") || "", businessPhone: phoneMap.get("business") || "", @@ -114,9 +116,11 @@ const EditContactTemplate = () => { website: values.website, mailTip: values.mailTip, }; - },); - - const contactTemplate = Array.isArray(contactTemplateInfo.data) ? contactTemplateInfo.data[0] : contactTemplateInfo.data; + }); + + const contactTemplate = Array.isArray(contactTemplateInfo.data) + ? contactTemplateInfo.data[0] + : contactTemplateInfo.data; return ( { data={contactTemplate} customDataformatter={customDataFormatter} > - + {contactTemplateInfo.isLoading && } + {!contactTemplateInfo.isLoading && ( + + )} ); }; diff --git a/src/pages/email/administration/contacts/edit.jsx b/src/pages/email/administration/contacts/edit.jsx index bc8220ef7719..963b0be981d0 100644 --- a/src/pages/email/administration/contacts/edit.jsx +++ b/src/pages/email/administration/contacts/edit.jsx @@ -3,6 +3,7 @@ import { useRouter } from "next/router"; import { useForm } from "react-hook-form"; import { Layout as DashboardLayout } from "/src/layouts/index.js"; import CippFormPage from "/src/components/CippFormPages/CippFormPage"; +import CippFormSkeleton from "/src/components/CippFormPages/CippFormSkeleton"; import { useSettings } from "../../../../hooks/use-settings"; import { ApiGetCall } from "../../../../api/ApiCall"; import countryList from "/src/data/countryList.json"; @@ -10,39 +11,40 @@ import { Grid } from "@mui/system"; import CippFormComponent from "/src/components/CippComponents/CippFormComponent"; import { Divider } from "@mui/material"; -const countryLookup = new Map( - countryList.map(country => [country.Name, country.Code]) -); +const countryLookup = new Map(countryList.map((country) => [country.Name, country.Code])); const EditContact = () => { const tenantDomain = useSettings().currentTenant; const router = useRouter(); const { id } = router.query; - + const contactInfo = ApiGetCall({ url: `/api/ListContacts?tenantFilter=${tenantDomain}&id=${id}`, queryKey: `ListContacts-${id}`, waiting: !!id, }); - const defaultFormValues = useMemo(() => ({ - displayName: "", - firstName: "", - lastName: "", - email: "", - hidefromGAL: false, - streetAddress: "", - postalCode: "", - city: "", - state: "", - country: "", - companyName: "", - mobilePhone: "", - businessPhone: "", - jobTitle: "", - website: "", - mailTip: "", - }), []); + const defaultFormValues = useMemo( + () => ({ + displayName: "", + firstName: "", + lastName: "", + email: "", + hidefromGAL: false, + streetAddress: "", + postalCode: "", + city: "", + state: "", + country: "", + companyName: "", + mobilePhone: "", + businessPhone: "", + jobTitle: "", + website: "", + mailTip: "", + }), + [] + ); const formControl = useForm({ mode: "onChange", @@ -58,9 +60,9 @@ const EditContact = () => { const contact = contactInfo.data; const address = contact.addresses?.[0] || {}; const phones = contact.phones || []; - + // Use Map for O(1) phone lookup - const phoneMap = new Map(phones.map(p => [p.type, p.number])); + const phoneMap = new Map(phones.map((p) => [p.type, p.number])); return { displayName: contact.displayName || "", @@ -72,9 +74,7 @@ const EditContact = () => { postalCode: address.postalCode || "", city: address.city || "", state: address.state || "", - country: address.countryOrRegion - ? countryLookup.get(address.countryOrRegion) || "" - : "", + country: address.countryOrRegion ? countryLookup.get(address.countryOrRegion) || "" : "", companyName: contact.companyName || "", mobilePhone: phoneMap.get("mobile") || "", businessPhone: phoneMap.get("business") || "", @@ -96,30 +96,33 @@ const EditContact = () => { }, [resetForm]); // Memoize custom data formatter - const customDataFormatter = useCallback((values) => { - const contact = Array.isArray(contactInfo.data) ? contactInfo.data[0] : contactInfo.data; - return { - tenantID: tenantDomain, - ContactID: contact?.id, - DisplayName: values.displayName, - hidefromGAL: values.hidefromGAL, - email: values.email, - FirstName: values.firstName, - LastName: values.lastName, - Title: values.jobTitle, - StreetAddress: values.streetAddress, - PostalCode: values.postalCode, - City: values.city, - State: values.state, - CountryOrRegion: values.country?.value || values.country, - Company: values.companyName, - mobilePhone: values.mobilePhone, - phone: values.businessPhone, - website: values.website, - mailTip: values.mailTip, - }; - }, [tenantDomain, contactInfo.data]); - + const customDataFormatter = useCallback( + (values) => { + const contact = Array.isArray(contactInfo.data) ? contactInfo.data[0] : contactInfo.data; + return { + tenantID: tenantDomain, + ContactID: contact?.id, + DisplayName: values.displayName, + hidefromGAL: values.hidefromGAL, + email: values.email, + FirstName: values.firstName, + LastName: values.lastName, + Title: values.jobTitle, + StreetAddress: values.streetAddress, + PostalCode: values.postalCode, + City: values.city, + State: values.state, + CountryOrRegion: values.country?.value || values.country, + Company: values.companyName, + mobilePhone: values.mobilePhone, + phone: values.businessPhone, + website: values.website, + mailTip: values.mailTip, + }; + }, + [tenantDomain, contactInfo.data] + ); + const contact = Array.isArray(contactInfo.data) ? contactInfo.data[0] : contactInfo.data; return ( @@ -133,142 +136,150 @@ const EditContact = () => { data={contact} customDataformatter={customDataFormatter} > - - {/* Display Name */} - - - + {contactInfo.isLoading && } + {!contactInfo.isLoading && ( + + {/* Display Name */} + + + - {/* First Name and Last Name */} - - - - - - + {/* First Name and Last Name */} + + + + + + - + - {/* Email */} - - - + {/* Email */} + + + - {/* Hide from GAL */} - - - + {/* Hide from GAL */} + + + - + - {/* Company Information */} - - - - - - + {/* Company Information */} + + + + + + - + - {/* Address Information */} - - - - - - - - - - - ({ - label: Name, - value: Code, - }))} - formControl={formControl} - /> - + {/* Address Information */} + + + + + + + + + + + ({ + label: Name, + value: Code, + }))} + formControl={formControl} + /> + - + - {/* Phone Numbers */} - - - - - + {/* Phone Numbers */} + + + + + + - + )} ); }; From 6146b3b2373266f67b500f47e82932bd7fa5ecb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Fri, 11 Jul 2025 20:38:07 +0200 Subject: [PATCH 098/112] Update license files to newest from MS --- src/data/M365Licenses.json | 840 +++++++++++++++++++++++++++++-------- 1 file changed, 656 insertions(+), 184 deletions(-) diff --git a/src/data/M365Licenses.json b/src/data/M365Licenses.json index d206d18e2f67..7774dc82f0aa 100644 --- a/src/data/M365Licenses.json +++ b/src/data/M365Licenses.json @@ -167,6 +167,14 @@ "Service_Plan_Id": "f7e5b77d-f293-410a-bae8-f941f19fe680", "Service_Plans_Included_Friendly_Names": "OneDrive for Business (Clipchamp)" }, + { + "Product_Display_Name": "Clipchamp Premium Add-on", + "String_Id": "Clipchamp_Premium_Add_on", + "GUID": "4b2c20e4-939d-4bf4-9dd8-6870240cfe19", + "Service_Plan_Name": "CLIPCHAMP_PREMIUM", + "Service_Plan_Id": "430b908f-78e1-4812-b045-cf83320e7d5d", + "Service_Plans_Included_Friendly_Names": "Microsoft Clipchamp Premium" + }, { "Product_Display_Name": "Microsoft 365 Audio Conferencing", "String_Id": "MCOMEETADV", @@ -3351,6 +3359,62 @@ "Service_Plan_Id": "26fa8a18-2812-4b3d-96b4-864818ce26be", "Service_Plans_Included_Friendly_Names": "Power Automate for Dynamics 365 Mixed Reality" }, + { + "Product_Display_Name": "Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer", + "String_Id": "DYN365_HUMAN_RESOURCES_ATTACH", + "GUID": "83c489a4-94b6-4dcc-9fdc-ff9b107a4621", + "Service_Plan_Name": "Forms_Pro_Talent", + "Service_Plan_Id": "1c4ae475-5608-43fa-b3f7-d20e07cf24b4", + "Service_Plans_Included_Friendly_Names": "Microsoft Dynamics 365 Customer Voice for Talent" + }, + { + "Product_Display_Name": "Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer", + "String_Id": "DYN365_HUMAN_RESOURCES_ATTACH", + "GUID": "83c489a4-94b6-4dcc-9fdc-ff9b107a4621", + "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 Human Resources Attach to Qualifying Dynamics 365 Base Offer", + "String_Id": "DYN365_HUMAN_RESOURCES_ATTACH", + "GUID": "83c489a4-94b6-4dcc-9fdc-ff9b107a4621", + "Service_Plan_Name": "D365_HR_SELF_SERVICE_OPS", + "Service_Plan_Id": "835b837b-63c1-410e-bf6b-bdef201ad129", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 Human Resource Self Service" + }, + { + "Product_Display_Name": "Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer", + "String_Id": "DYN365_HUMAN_RESOURCES_ATTACH", + "GUID": "83c489a4-94b6-4dcc-9fdc-ff9b107a4621", + "Service_Plan_Name": "D365_HR_OPS", + "Service_Plan_Id": "8b21a5dc-5485-49ed-a2d4-0e772c830f6d", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 Human Resources" + }, + { + "Product_Display_Name": "Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer", + "String_Id": "DYN365_HUMAN_RESOURCES_ATTACH", + "GUID": "83c489a4-94b6-4dcc-9fdc-ff9b107a4621", + "Service_Plan_Name": "D365_HR_Attach", + "Service_Plan_Id": "3219525a-4064-45ec-9c35-a33ea6b39a49", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 Human Resources Attach" + }, + { + "Product_Display_Name": "Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer", + "String_Id": "DYN365_HUMAN_RESOURCES_ATTACH", + "GUID": "83c489a4-94b6-4dcc-9fdc-ff9b107a4621", + "Service_Plan_Name": "D365_HR_ATTACH_OPS", + "Service_Plan_Id": "90d8cb62-e98a-4639-8342-8c7d2c8215ba", + "Service_Plans_Included_Friendly_Names": "Dynamics 365 Human Resources Attach License" + }, + { + "Product_Display_Name": "Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer", + "String_Id": "DYN365_HUMAN_RESOURCES_ATTACH", + "GUID": "83c489a4-94b6-4dcc-9fdc-ff9b107a4621", + "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 Hybrid Connector", "String_Id": "CRM_HYBRIDCONNECTOR", @@ -7744,15 +7808,15 @@ "Service_Plans_Included_Friendly_Names": "Power Automate for Office 365" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "AAD_BASIC_EDU", "Service_Plan_Id": "1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426", - "Service_Plans_Included_Friendly_Names": "Microsoft Entra ID Basic for Education" + "Service_Plans_Included_Friendly_Names": "Azure Active Directory Basic for Education" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "RMS_S_ENTERPRISE", @@ -7760,7 +7824,7 @@ "Service_Plans_Included_Friendly_Names": "Azure Rights Management" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "CDS_O365_P3", @@ -7768,7 +7832,7 @@ "Service_Plans_Included_Friendly_Names": "Common Data Service for Teams" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "LOCKBOX_ENTERPRISE", @@ -7776,7 +7840,15 @@ "Service_Plans_Included_Friendly_Names": "Customer Lockbox" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", + "String_Id": "M365EDU_A5_FACULTY", + "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", + "Service_Plan_Name": "CustomerLockboxA_Enterprise", + "Service_Plan_Id": "3ec18638-bd4c-4d3b-8905-479ed636b83e", + "Service_Plans_Included_Friendly_Names": "Customer Lockbox (A)" + }, + { + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "MIP_S_Exchange", @@ -7784,7 +7856,15 @@ "Service_Plans_Included_Friendly_Names": "Data Classification in Microsoft 365" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", + "String_Id": "M365EDU_A5_FACULTY", + "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", + "Service_Plan_Name": "COMMON_DEFENDER_PLATFORM_FOR_OFFICE", + "Service_Plan_Id": "a312bdeb-1e21-40d0-84b1-0e73f128144f", + "Service_Plans_Included_Friendly_Names": "Defender Platform for Office 365" + }, + { + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "EducationAnalyticsP1", @@ -7792,7 +7872,7 @@ "Service_Plans_Included_Friendly_Names": "Education Analytics" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "EXCHANGE_S_ENTERPRISE", @@ -7800,7 +7880,15 @@ "Service_Plans_Included_Friendly_Names": "Exchange Online (Plan 2)" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", + "String_Id": "M365EDU_A5_FACULTY", + "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", + "Service_Plan_Name": "GRAPH_CONNECTORS_SEARCH_INDEX", + "Service_Plan_Id": "a6520331-d7d4-4276-95f5-15c0933bc757", + "Service_Plans_Included_Friendly_Names": "Graph Connectors Search with Index" + }, + { + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "INFORMATION_BARRIERS", @@ -7808,7 +7896,7 @@ "Service_Plans_Included_Friendly_Names": "Information Barriers" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "Content_Explorer", @@ -7816,7 +7904,7 @@ "Service_Plans_Included_Friendly_Names": "Information Protection and Governance Analytics - Premium" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "ContentExplorer_Standard", @@ -7824,7 +7912,7 @@ "Service_Plans_Included_Friendly_Names": "Information Protection and Governance Analytics – Standard" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "MIP_S_CLP2", @@ -7832,7 +7920,7 @@ "Service_Plans_Included_Friendly_Names": "Information Protection for Office 365 - Premium" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "MIP_S_CLP1", @@ -7840,7 +7928,7 @@ "Service_Plans_Included_Friendly_Names": "Information Protection for Office 365 - Standard" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "M365_ADVANCED_AUDITING", @@ -7848,15 +7936,15 @@ "Service_Plans_Included_Friendly_Names": "Microsoft 365 Advanced Auditing" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "OFFICESUBSCRIPTION", "Service_Plan_Id": "43de0ff5-c92c-492b-9116-175376d08c38", - "Service_Plans_Included_Friendly_Names": "Microsoft 365 Apps for Enterprise" + "Service_Plans_Included_Friendly_Names": "Microsoft 365 Apps for enterprise" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "MCOMEETADV", @@ -7864,7 +7952,15 @@ "Service_Plans_Included_Friendly_Names": "Microsoft 365 Audio Conferencing" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", + "String_Id": "M365EDU_A5_FACULTY", + "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", + "Service_Plan_Name": "M365_AUDIT_PLATFORM", + "Service_Plan_Id": "f6de4823-28fa-440b-b886-4783fa86ddba", + "Service_Plans_Included_Friendly_Names": "Microsoft 365 Audit Platform" + }, + { + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "MICROSOFT_COMMUNICATION_COMPLIANCE", @@ -7872,7 +7968,7 @@ "Service_Plans_Included_Friendly_Names": "Microsoft 365 Communication Compliance" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "MTP", @@ -7880,7 +7976,15 @@ "Service_Plans_Included_Friendly_Names": "Microsoft 365 Defender" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", + "String_Id": "M365EDU_A5_FACULTY", + "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", + "Service_Plan_Name": "M365_LIGHTHOUSE_CUSTOMER_PLAN1", + "Service_Plan_Id": "6f23d6a9-adbf-481c-8538-b4c095654487", + "Service_Plans_Included_Friendly_Names": "Microsoft 365 Lighthouse (Plan 1)" + }, + { + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "MCOEV", @@ -7888,7 +7992,7 @@ "Service_Plans_Included_Friendly_Names": "Microsoft 365 Phone System" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "MICROSOFTBOOKINGS", @@ -7896,7 +8000,15 @@ "Service_Plans_Included_Friendly_Names": "Microsoft Bookings" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", + "String_Id": "M365EDU_A5_FACULTY", + "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", + "Service_Plan_Name": "CLIPCHAMP", + "Service_Plan_Id": "a1ace008-72f3-4ea0-8dac-33b3a23a2472", + "Service_Plans_Included_Friendly_Names": "Microsoft Clipchamp" + }, + { + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "COMMUNICATIONS_DLP", @@ -7904,7 +8016,7 @@ "Service_Plans_Included_Friendly_Names": "Microsoft Communications DLP" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "CUSTOMER_KEY", @@ -7912,15 +8024,7 @@ "Service_Plans_Included_Friendly_Names": "Microsoft Customer Key" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", - "String_Id": "M365EDU_A5_FACULTY", - "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", - "Service_Plan_Name": "DATA_INVESTIGATIONS", - "Service_Plan_Id": "46129a58-a698-46f0-aa5b-17f6586297d9", - "Service_Plans_Included_Friendly_Names": "Microsoft Data Investigations" - }, - { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "ATP_ENTERPRISE", @@ -7928,7 +8032,7 @@ "Service_Plans_Included_Friendly_Names": "Microsoft Defender for Office 365 (Plan 1)" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "THREAT_INTELLIGENCE", @@ -7936,7 +8040,7 @@ "Service_Plans_Included_Friendly_Names": "Microsoft Defender for Office 365 (Plan 2)" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "EXCEL_PREMIUM", @@ -7944,7 +8048,7 @@ "Service_Plans_Included_Friendly_Names": "Microsoft Excel Advanced Analytics" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "OFFICE_FORMS_PLAN_3", @@ -7952,7 +8056,7 @@ "Service_Plans_Included_Friendly_Names": "Microsoft Forms (Plan 3)" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "INFO_GOVERNANCE", @@ -7960,7 +8064,7 @@ "Service_Plans_Included_Friendly_Names": "Microsoft Information Governance" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "INSIDER_RISK", @@ -7968,7 +8072,15 @@ "Service_Plans_Included_Friendly_Names": "Microsoft Insider Risk Management" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", + "String_Id": "M365EDU_A5_FACULTY", + "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", + "Service_Plan_Name": "INSIDER_RISK_MANAGEMENT", + "Service_Plan_Id": "9d0c4ee5-e4a1-4625-ab39-d82b619b1a34", + "Service_Plans_Included_Friendly_Names": "Microsoft Insider Risk Management - Exchange" + }, + { + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "KAIZALA_STANDALONE", @@ -7976,7 +8088,15 @@ "Service_Plans_Included_Friendly_Names": "Microsoft Kaizala Pro" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", + "String_Id": "M365EDU_A5_FACULTY", + "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", + "Service_Plan_Name": "MICROSOFT_LOOP", + "Service_Plan_Id": "c4b8c31a-fb44-4c65-9837-a21f55fcabda", + "Service_Plans_Included_Friendly_Names": "Microsoft Loop" + }, + { + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "ML_CLASSIFICATION", @@ -7984,7 +8104,7 @@ "Service_Plans_Included_Friendly_Names": "Microsoft ML-Based Classification" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "EXCHANGE_ANALYTICS", @@ -7992,7 +8112,7 @@ "Service_Plans_Included_Friendly_Names": "Microsoft MyAnalytics (Full)" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "PROJECTWORKMANAGEMENT", @@ -8000,7 +8120,7 @@ "Service_Plans_Included_Friendly_Names": "Microsoft Planner" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "RECORDS_MANAGEMENT", @@ -8008,7 +8128,7 @@ "Service_Plans_Included_Friendly_Names": "Microsoft Records Management" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "MICROSOFT_SEARCH", @@ -8016,7 +8136,7 @@ "Service_Plans_Included_Friendly_Names": "Microsoft Search" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "Deskless", @@ -8024,7 +8144,7 @@ "Service_Plans_Included_Friendly_Names": "Microsoft StaffHub" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "STREAM_O365_E5", @@ -8032,7 +8152,7 @@ "Service_Plans_Included_Friendly_Names": "Microsoft Stream for Office 365 E5" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "TEAMS1", @@ -8040,7 +8160,7 @@ "Service_Plans_Included_Friendly_Names": "Microsoft Teams" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "MINECRAFT_EDUCATION_EDITION", @@ -8048,7 +8168,7 @@ "Service_Plans_Included_Friendly_Names": "Minecraft Education Edition" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "INTUNE_O365", @@ -8056,7 +8176,7 @@ "Service_Plans_Included_Friendly_Names": "Mobile Device Management for Office 365" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "Nucleus", @@ -8064,7 +8184,7 @@ "Service_Plans_Included_Friendly_Names": "Nucleus" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "EQUIVIO_ANALYTICS", @@ -8072,7 +8192,7 @@ "Service_Plans_Included_Friendly_Names": "Office 365 Advanced eDiscovery" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "ADALLOM_S_O365", @@ -8080,7 +8200,7 @@ "Service_Plans_Included_Friendly_Names": "Office 365 Cloud App Security" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "PAM_ENTERPRISE", @@ -8088,7 +8208,7 @@ "Service_Plans_Included_Friendly_Names": "Office 365 Privileged Access Management" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "SAFEDOCS", @@ -8096,7 +8216,7 @@ "Service_Plans_Included_Friendly_Names": "Office 365 SafeDocs" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "SHAREPOINTWAC_EDU", @@ -8104,7 +8224,7 @@ "Service_Plans_Included_Friendly_Names": "Office for the Web for Education" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "POWERAPPS_O365_P3", @@ -8112,7 +8232,7 @@ "Service_Plans_Included_Friendly_Names": "Power Apps for Office 365 (Plan 3)" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "BI_AZURE_P2", @@ -8120,7 +8240,7 @@ "Service_Plans_Included_Friendly_Names": "Power BI Pro" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "PREMIUM_ENCRYPTION", @@ -8128,7 +8248,7 @@ "Service_Plans_Included_Friendly_Names": "Premium Encryption in Office 365" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "PROJECT_O365_P3", @@ -8136,23 +8256,39 @@ "Service_Plans_Included_Friendly_Names": "Project for Office (Plan E5)" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", + "String_Id": "M365EDU_A5_FACULTY", + "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", + "Service_Plan_Name": "PURVIEW_DISCOVERY", + "Service_Plan_Id": "c948ea65-2053-4a5a-8a62-9eaaaf11b522", + "Service_Plans_Included_Friendly_Names": "Purview Discovery" + }, + { + "Product_Display_Name": "Microsoft 365 A5 for faculty", + "String_Id": "M365EDU_A5_FACULTY", + "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", + "Service_Plan_Name": "Bing_Chat_Enterprise", + "Service_Plan_Id": "0d0c0d31-fae7-41f2-b909-eaf4d7f26dba", + "Service_Plans_Included_Friendly_Names": "RETIRED - Commercial data protection for Microsoft Copilot" + }, + { + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "COMMUNICATIONS_COMPLIANCE", "Service_Plan_Id": "41fcdd7d-4733-4863-9cf4-c65b83ce2df4", - "Service_Plans_Included_Friendly_Names": "Microsoft Communications Compliance" + "Service_Plans_Included_Friendly_Names": "RETIRED - Microsoft Communications Compliance" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", - "Service_Plan_Name": "INSIDER_RISK_MANAGEMENT", - "Service_Plan_Id": "9d0c4ee5-e4a1-4625-ab39-d82b619b1a34", - "Service_Plans_Included_Friendly_Names": "Microsoft Insider Risk Management" + "Service_Plan_Name": "DATA_INVESTIGATIONS", + "Service_Plan_Id": "46129a58-a698-46f0-aa5b-17f6586297d9", + "Service_Plans_Included_Friendly_Names": "Retired - Microsoft Data Investigations" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "SCHOOL_DATA_SYNC_P2", @@ -8160,7 +8296,7 @@ "Service_Plans_Included_Friendly_Names": "School Data Sync (Plan 2)" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "SHAREPOINTENTERPRISE_EDU", @@ -8168,7 +8304,7 @@ "Service_Plans_Included_Friendly_Names": "SharePoint (Plan 2) for Education" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "MCOSTANDARD", @@ -8176,7 +8312,7 @@ "Service_Plans_Included_Friendly_Names": "Skype for Business Online (Plan 2)" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "SWAY", @@ -8184,7 +8320,7 @@ "Service_Plans_Included_Friendly_Names": "Sway" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "BPOS_S_TODO_3", @@ -8192,7 +8328,7 @@ "Service_Plans_Included_Friendly_Names": "To-Do (Plan 3)" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "VIVA_LEARNING_SEEDED", @@ -8200,7 +8336,7 @@ "Service_Plans_Included_Friendly_Names": "Viva Learning Seeded" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "WHITEBOARD_PLAN3", @@ -8208,7 +8344,7 @@ "Service_Plans_Included_Friendly_Names": "Whiteboard (Plan 3)" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "YAMMER_EDU", @@ -8216,7 +8352,7 @@ "Service_Plans_Included_Friendly_Names": "Yammer for Academic" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "WINDEFATP", @@ -8224,7 +8360,7 @@ "Service_Plans_Included_Friendly_Names": "Microsoft Defender for Endpoint" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "MICROSOFTENDPOINTDLP", @@ -8232,7 +8368,7 @@ "Service_Plans_Included_Friendly_Names": "Microsoft Endpoint DLP" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "UNIVERSAL_PRINT_01", @@ -8240,7 +8376,7 @@ "Service_Plans_Included_Friendly_Names": "Universal Print" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "Virtualization Rights for Windows 10 (E3/E5+VDA)", @@ -8248,7 +8384,7 @@ "Service_Plans_Included_Friendly_Names": "Windows 10/11 Enterprise" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "WINDOWSUPDATEFORBUSINESS_DEPLOYMENTSERVICE", @@ -8256,23 +8392,7 @@ "Service_Plans_Included_Friendly_Names": "Windows Update for Business Deployment Service" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", - "String_Id": "M365EDU_A5_FACULTY", - "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", - "Service_Plan_Name": "AAD_PREMIUM", - "Service_Plan_Id": "41781fb2-bc02-4b7c-bd55-b576c07bb09d", - "Service_Plans_Included_Friendly_Names": "Microsoft Entra ID P1" - }, - { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", - "String_Id": "M365EDU_A5_FACULTY", - "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", - "Service_Plan_Name": "AAD_PREMIUM_P2", - "Service_Plan_Id": "eec0eb4f-6444-4f95-aba0-50c24d67f998", - "Service_Plans_Included_Friendly_Names": "Microsoft Entra ID P2" - }, - { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "RMS_S_PREMIUM", @@ -8280,7 +8400,7 @@ "Service_Plans_Included_Friendly_Names": "Azure Information Protection Premium P1" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "RMS_S_PREMIUM2", @@ -8288,7 +8408,7 @@ "Service_Plans_Included_Friendly_Names": "Azure Information Protection Premium P2" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "DYN365_CDS_O365_P3", @@ -8296,7 +8416,15 @@ "Service_Plans_Included_Friendly_Names": "Common Data Service" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", + "String_Id": "M365EDU_A5_FACULTY", + "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", + "Service_Plan_Name": "Intune_ServiceNow", + "Service_Plan_Id": "3eeb8536-fecf-41bf-a3f8-d6f17a9f3efc", + "Service_Plans_Included_Friendly_Names": "Intune ServiceNow Integration" + }, + { + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "MFA_PREMIUM", @@ -8304,7 +8432,7 @@ "Service_Plans_Included_Friendly_Names": "Microsoft Azure Multi-Factor Authentication" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "ADALLOM_S_STANDALONE", @@ -8312,7 +8440,7 @@ "Service_Plans_Included_Friendly_Names": "Microsoft Defender for Cloud Apps" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "ATA", @@ -8320,23 +8448,39 @@ "Service_Plans_Included_Friendly_Names": "Microsoft Defender for Identity" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", + "String_Id": "M365EDU_A5_FACULTY", + "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", + "Service_Plan_Name": "AAD_PREMIUM", + "Service_Plan_Id": "41781fb2-bc02-4b7c-bd55-b576c07bb09d", + "Service_Plans_Included_Friendly_Names": "Microsoft Entra ID P1" + }, + { + "Product_Display_Name": "Microsoft 365 A5 for faculty", + "String_Id": "M365EDU_A5_FACULTY", + "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", + "Service_Plan_Name": "AAD_PREMIUM_P2", + "Service_Plan_Id": "eec0eb4f-6444-4f95-aba0-50c24d67f998", + "Service_Plans_Included_Friendly_Names": "Microsoft Entra ID P2" + }, + { + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "INTUNE_A", "Service_Plan_Id": "c1ec4a95-1f05-45b3-a911-aa3fa01094f5", - "Service_Plans_Included_Friendly_Names": "Microsoft Intune" + "Service_Plans_Included_Friendly_Names": "Microsoft Intune Plan 1" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "INTUNE_EDU", "Service_Plan_Id": "da24caf9-af8e-485c-b7c8-e73336da2693", - "Service_Plans_Included_Friendly_Names": "Microsoft Intune for Education" + "Service_Plans_Included_Friendly_Names": "Microsoft Intune Plan 1 for Education" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "FLOW_O365_P3", @@ -8344,20 +8488,28 @@ "Service_Plans_Included_Friendly_Names": "Power Automate for Office 365" }, { - "Product_Display_Name": "Microsoft 365 A5 for Faculty", + "Product_Display_Name": "Microsoft 365 A5 for faculty", "String_Id": "M365EDU_A5_FACULTY", "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", "Service_Plan_Name": "POWER_VIRTUAL_AGENTS_O365_P3", "Service_Plan_Id": "ded3d325-1bdc-453e-8432-5bac26d7a014", "Service_Plans_Included_Friendly_Names": "Power Virtual Agents for Office 365" }, + { + "Product_Display_Name": "Microsoft 365 A5 for faculty", + "String_Id": "M365EDU_A5_FACULTY", + "GUID": "e97c048c-37a4-45fb-ab50-922fbf07a370", + "Service_Plan_Name": "REMOTE_HELP", + "Service_Plan_Id": "a4c6cf29-1168-4076-ba5c-e8fe0e62b17e", + "Service_Plans_Included_Friendly_Names": "Remote help" + }, { "Product_Display_Name": "Microsoft 365 A5 for Students", "String_Id": "M365EDU_A5_STUDENT", "GUID": "46c119d4-0379-4a9d-85e4-97c66d3f909e", "Service_Plan_Name": "AAD_BASIC_EDU", "Service_Plan_Id": "1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426", - "Service_Plans_Included_Friendly_Names": "Microsoft Entra ID Basic for Education" + "Service_Plans_Included_Friendly_Names": "Azure Active Directory Basic for Education" }, { "Product_Display_Name": "Microsoft 365 A5 for Students", @@ -8383,6 +8535,14 @@ "Service_Plan_Id": "9f431833-0334-42de-a7dc-70aa40db46db", "Service_Plans_Included_Friendly_Names": "Customer Lockbox" }, + { + "Product_Display_Name": "Microsoft 365 A5 for Students", + "String_Id": "M365EDU_A5_STUDENT", + "GUID": "46c119d4-0379-4a9d-85e4-97c66d3f909e", + "Service_Plan_Name": "CustomerLockboxA_Enterprise", + "Service_Plan_Id": "3ec18638-bd4c-4d3b-8905-479ed636b83e", + "Service_Plans_Included_Friendly_Names": "Customer Lockbox (A)" + }, { "Product_Display_Name": "Microsoft 365 A5 for Students", "String_Id": "M365EDU_A5_STUDENT", @@ -8391,6 +8551,14 @@ "Service_Plan_Id": "cd31b152-6326-4d1b-ae1b-997b625182e6", "Service_Plans_Included_Friendly_Names": "Data Classification in Microsoft 365" }, + { + "Product_Display_Name": "Microsoft 365 A5 for Students", + "String_Id": "M365EDU_A5_STUDENT", + "GUID": "46c119d4-0379-4a9d-85e4-97c66d3f909e", + "Service_Plan_Name": "COMMON_DEFENDER_PLATFORM_FOR_OFFICE", + "Service_Plan_Id": "a312bdeb-1e21-40d0-84b1-0e73f128144f", + "Service_Plans_Included_Friendly_Names": "Defender Platform for Office 365" + }, { "Product_Display_Name": "Microsoft 365 A5 for Students", "String_Id": "M365EDU_A5_STUDENT", @@ -8407,6 +8575,14 @@ "Service_Plan_Id": "efb87545-963c-4e0d-99df-69c6916d9eb0", "Service_Plans_Included_Friendly_Names": "Exchange Online (Plan 2)" }, + { + "Product_Display_Name": "Microsoft 365 A5 for Students", + "String_Id": "M365EDU_A5_STUDENT", + "GUID": "46c119d4-0379-4a9d-85e4-97c66d3f909e", + "Service_Plan_Name": "GRAPH_CONNECTORS_SEARCH_INDEX", + "Service_Plan_Id": "a6520331-d7d4-4276-95f5-15c0933bc757", + "Service_Plans_Included_Friendly_Names": "Graph Connectors Search with Index" + }, { "Product_Display_Name": "Microsoft 365 A5 for Students", "String_Id": "M365EDU_A5_STUDENT", @@ -8461,7 +8637,7 @@ "GUID": "46c119d4-0379-4a9d-85e4-97c66d3f909e", "Service_Plan_Name": "OFFICESUBSCRIPTION", "Service_Plan_Id": "43de0ff5-c92c-492b-9116-175376d08c38", - "Service_Plans_Included_Friendly_Names": "Microsoft 365 Apps for Enterprise" + "Service_Plans_Included_Friendly_Names": "Microsoft 365 Apps for enterprise" }, { "Product_Display_Name": "Microsoft 365 A5 for Students", @@ -8471,6 +8647,14 @@ "Service_Plan_Id": "3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40", "Service_Plans_Included_Friendly_Names": "Microsoft 365 Audio Conferencing" }, + { + "Product_Display_Name": "Microsoft 365 A5 for Students", + "String_Id": "M365EDU_A5_STUDENT", + "GUID": "46c119d4-0379-4a9d-85e4-97c66d3f909e", + "Service_Plan_Name": "M365_AUDIT_PLATFORM", + "Service_Plan_Id": "f6de4823-28fa-440b-b886-4783fa86ddba", + "Service_Plans_Included_Friendly_Names": "Microsoft 365 Audit Platform" + }, { "Product_Display_Name": "Microsoft 365 A5 for Students", "String_Id": "M365EDU_A5_STUDENT", @@ -8487,6 +8671,14 @@ "Service_Plan_Id": "bf28f719-7844-4079-9c78-c1307898e192", "Service_Plans_Included_Friendly_Names": "Microsoft 365 Defender" }, + { + "Product_Display_Name": "Microsoft 365 A5 for Students", + "String_Id": "M365EDU_A5_STUDENT", + "GUID": "46c119d4-0379-4a9d-85e4-97c66d3f909e", + "Service_Plan_Name": "M365_LIGHTHOUSE_CUSTOMER_PLAN1", + "Service_Plan_Id": "6f23d6a9-adbf-481c-8538-b4c095654487", + "Service_Plans_Included_Friendly_Names": "Microsoft 365 Lighthouse (Plan 1)" + }, { "Product_Display_Name": "Microsoft 365 A5 for Students", "String_Id": "M365EDU_A5_STUDENT", @@ -8503,6 +8695,14 @@ "Service_Plan_Id": "199a5c09-e0ca-4e37-8f7c-b05d533e1ea2", "Service_Plans_Included_Friendly_Names": "Microsoft Bookings" }, + { + "Product_Display_Name": "Microsoft 365 A5 for Students", + "String_Id": "M365EDU_A5_STUDENT", + "GUID": "46c119d4-0379-4a9d-85e4-97c66d3f909e", + "Service_Plan_Name": "CLIPCHAMP", + "Service_Plan_Id": "a1ace008-72f3-4ea0-8dac-33b3a23a2472", + "Service_Plans_Included_Friendly_Names": "Microsoft Clipchamp" + }, { "Product_Display_Name": "Microsoft 365 A5 for Students", "String_Id": "M365EDU_A5_STUDENT", @@ -8519,14 +8719,6 @@ "Service_Plan_Id": "6db1f1db-2b46-403f-be40-e39395f08dbb", "Service_Plans_Included_Friendly_Names": "Microsoft Customer Key" }, - { - "Product_Display_Name": "Microsoft 365 A5 for Students", - "String_Id": "M365EDU_A5_STUDENT", - "GUID": "46c119d4-0379-4a9d-85e4-97c66d3f909e", - "Service_Plan_Name": "DATA_INVESTIGATIONS", - "Service_Plan_Id": "46129a58-a698-46f0-aa5b-17f6586297d9", - "Service_Plans_Included_Friendly_Names": "Microsoft Data Investigations" - }, { "Product_Display_Name": "Microsoft 365 A5 for Students", "String_Id": "M365EDU_A5_STUDENT", @@ -8575,6 +8767,14 @@ "Service_Plan_Id": "d587c7a3-bda9-4f99-8776-9bcf59c84f75", "Service_Plans_Included_Friendly_Names": "Microsoft Insider Risk Management" }, + { + "Product_Display_Name": "Microsoft 365 A5 for Students", + "String_Id": "M365EDU_A5_STUDENT", + "GUID": "46c119d4-0379-4a9d-85e4-97c66d3f909e", + "Service_Plan_Name": "INSIDER_RISK_MANAGEMENT", + "Service_Plan_Id": "9d0c4ee5-e4a1-4625-ab39-d82b619b1a34", + "Service_Plans_Included_Friendly_Names": "Microsoft Insider Risk Management - Exchange" + }, { "Product_Display_Name": "Microsoft 365 A5 for Students", "String_Id": "M365EDU_A5_STUDENT", @@ -8583,6 +8783,14 @@ "Service_Plan_Id": "0898bdbb-73b0-471a-81e5-20f1fe4dd66e", "Service_Plans_Included_Friendly_Names": "Microsoft Kaizala Pro" }, + { + "Product_Display_Name": "Microsoft 365 A5 for Students", + "String_Id": "M365EDU_A5_STUDENT", + "GUID": "46c119d4-0379-4a9d-85e4-97c66d3f909e", + "Service_Plan_Name": "MICROSOFT_LOOP", + "Service_Plan_Id": "c4b8c31a-fb44-4c65-9837-a21f55fcabda", + "Service_Plans_Included_Friendly_Names": "Microsoft Loop" + }, { "Product_Display_Name": "Microsoft 365 A5 for Students", "String_Id": "M365EDU_A5_STUDENT", @@ -8735,6 +8943,22 @@ "Service_Plan_Id": "b21a6b06-1988-436e-a07b-51ec6d9f52ad", "Service_Plans_Included_Friendly_Names": "Project for Office (Plan E5)" }, + { + "Product_Display_Name": "Microsoft 365 A5 for Students", + "String_Id": "M365EDU_A5_STUDENT", + "GUID": "46c119d4-0379-4a9d-85e4-97c66d3f909e", + "Service_Plan_Name": "PURVIEW_DISCOVERY", + "Service_Plan_Id": "c948ea65-2053-4a5a-8a62-9eaaaf11b522", + "Service_Plans_Included_Friendly_Names": "Purview Discovery" + }, + { + "Product_Display_Name": "Microsoft 365 A5 for Students", + "String_Id": "M365EDU_A5_STUDENT", + "GUID": "46c119d4-0379-4a9d-85e4-97c66d3f909e", + "Service_Plan_Name": "Bing_Chat_Enterprise", + "Service_Plan_Id": "0d0c0d31-fae7-41f2-b909-eaf4d7f26dba", + "Service_Plans_Included_Friendly_Names": "RETIRED - Commercial data protection for Microsoft Copilot" + }, { "Product_Display_Name": "Microsoft 365 A5 for Students", "String_Id": "M365EDU_A5_STUDENT", @@ -8747,9 +8971,9 @@ "Product_Display_Name": "Microsoft 365 A5 for Students", "String_Id": "M365EDU_A5_STUDENT", "GUID": "46c119d4-0379-4a9d-85e4-97c66d3f909e", - "Service_Plan_Name": "INSIDER_RISK_MANAGEMENT", - "Service_Plan_Id": "9d0c4ee5-e4a1-4625-ab39-d82b619b1a34", - "Service_Plans_Included_Friendly_Names": "RETIRED - Microsoft Insider Risk Management" + "Service_Plan_Name": "DATA_INVESTIGATIONS", + "Service_Plan_Id": "46129a58-a698-46f0-aa5b-17f6586297d9", + "Service_Plans_Included_Friendly_Names": "Retired - Microsoft Data Investigations" }, { "Product_Display_Name": "Microsoft 365 A5 for Students", @@ -8847,22 +9071,6 @@ "Service_Plan_Id": "7bf960f6-2cd9-443a-8046-5dbff9558365", "Service_Plans_Included_Friendly_Names": "Windows Update for Business Deployment Service" }, - { - "Product_Display_Name": "Microsoft 365 A5 for Students", - "String_Id": "M365EDU_A5_STUDENT", - "GUID": "46c119d4-0379-4a9d-85e4-97c66d3f909e", - "Service_Plan_Name": "AAD_PREMIUM", - "Service_Plan_Id": "41781fb2-bc02-4b7c-bd55-b576c07bb09d", - "Service_Plans_Included_Friendly_Names": "Microsoft Entra ID P1" - }, - { - "Product_Display_Name": "Microsoft 365 A5 for Students", - "String_Id": "M365EDU_A5_STUDENT", - "GUID": "46c119d4-0379-4a9d-85e4-97c66d3f909e", - "Service_Plan_Name": "AAD_PREMIUM_P2", - "Service_Plan_Id": "eec0eb4f-6444-4f95-aba0-50c24d67f998", - "Service_Plans_Included_Friendly_Names": "Microsoft Entra ID P2" - }, { "Product_Display_Name": "Microsoft 365 A5 for Students", "String_Id": "M365EDU_A5_STUDENT", @@ -8887,6 +9095,14 @@ "Service_Plan_Id": "28b0fa46-c39a-4188-89e2-58e979a6b014", "Service_Plans_Included_Friendly_Names": "Common Data Service" }, + { + "Product_Display_Name": "Microsoft 365 A5 for Students", + "String_Id": "M365EDU_A5_STUDENT", + "GUID": "46c119d4-0379-4a9d-85e4-97c66d3f909e", + "Service_Plan_Name": "Intune_ServiceNow", + "Service_Plan_Id": "3eeb8536-fecf-41bf-a3f8-d6f17a9f3efc", + "Service_Plans_Included_Friendly_Names": "Intune ServiceNow Integration" + }, { "Product_Display_Name": "Microsoft 365 A5 for Students", "String_Id": "M365EDU_A5_STUDENT", @@ -8911,13 +9127,29 @@ "Service_Plan_Id": "14ab5db5-e6c4-4b20-b4bc-13e36fd2227f", "Service_Plans_Included_Friendly_Names": "Microsoft Defender for Identity" }, + { + "Product_Display_Name": "Microsoft 365 A5 for Students", + "String_Id": "M365EDU_A5_STUDENT", + "GUID": "46c119d4-0379-4a9d-85e4-97c66d3f909e", + "Service_Plan_Name": "AAD_PREMIUM", + "Service_Plan_Id": "41781fb2-bc02-4b7c-bd55-b576c07bb09d", + "Service_Plans_Included_Friendly_Names": "Microsoft Entra ID P1" + }, + { + "Product_Display_Name": "Microsoft 365 A5 for Students", + "String_Id": "M365EDU_A5_STUDENT", + "GUID": "46c119d4-0379-4a9d-85e4-97c66d3f909e", + "Service_Plan_Name": "AAD_PREMIUM_P2", + "Service_Plan_Id": "eec0eb4f-6444-4f95-aba0-50c24d67f998", + "Service_Plans_Included_Friendly_Names": "Microsoft Entra ID P2" + }, { "Product_Display_Name": "Microsoft 365 A5 for Students", "String_Id": "M365EDU_A5_STUDENT", "GUID": "46c119d4-0379-4a9d-85e4-97c66d3f909e", "Service_Plan_Name": "INTUNE_A", "Service_Plan_Id": "c1ec4a95-1f05-45b3-a911-aa3fa01094f5", - "Service_Plans_Included_Friendly_Names": "Microsoft Intune" + "Service_Plans_Included_Friendly_Names": "Microsoft Intune Plan 1" }, { "Product_Display_Name": "Microsoft 365 A5 for Students", @@ -8925,7 +9157,7 @@ "GUID": "46c119d4-0379-4a9d-85e4-97c66d3f909e", "Service_Plan_Name": "INTUNE_EDU", "Service_Plan_Id": "da24caf9-af8e-485c-b7c8-e73336da2693", - "Service_Plans_Included_Friendly_Names": "Microsoft Intune for Education" + "Service_Plans_Included_Friendly_Names": "Microsoft Intune Plan 1 for Education" }, { "Product_Display_Name": "Microsoft 365 A5 for Students", @@ -8943,6 +9175,14 @@ "Service_Plan_Id": "ded3d325-1bdc-453e-8432-5bac26d7a014", "Service_Plans_Included_Friendly_Names": "Power Virtual Agents for Office 365" }, + { + "Product_Display_Name": "Microsoft 365 A5 for Students", + "String_Id": "M365EDU_A5_STUDENT", + "GUID": "46c119d4-0379-4a9d-85e4-97c66d3f909e", + "Service_Plan_Name": "REMOTE_HELP", + "Service_Plan_Id": "a4c6cf29-1168-4076-ba5c-e8fe0e62b17e", + "Service_Plans_Included_Friendly_Names": "Remote help" + }, { "Product_Display_Name": "Microsoft 365 A5 for students use benefit", "String_Id": "M365EDU_A5_STUUSEBNFT", @@ -29071,6 +29311,38 @@ "Service_Plan_Id": "e866a266-3cff-43a3-acca-0c90a7e00c8b", "Service_Plans_Included_Friendly_Names": "Entra Identity Governance" }, + { + "Product_Display_Name": "Microsoft Entra Suite Add-on for Microsoft Entra ID P2", + "String_Id": "Microsoft_Entra_Suite_Step_Up_for_Microsoft_Entra_ID_P2", + "GUID": "2ef3064c-c95c-426c-96dd-9ffeaa2f2c37", + "Service_Plan_Name": "Entra_Premium_Internet_Access", + "Service_Plan_Id": "8d23cb83-ab07-418f-8517-d7aca77307dc", + "Service_Plans_Included_Friendly_Names": "Microsoft Entra Internet Access" + }, + { + "Product_Display_Name": "Microsoft Entra Suite Add-on for Microsoft Entra ID P2", + "String_Id": "Microsoft_Entra_Suite_Step_Up_for_Microsoft_Entra_ID_P2", + "GUID": "2ef3064c-c95c-426c-96dd-9ffeaa2f2c37", + "Service_Plan_Name": "Entra_Premium_Private_Access", + "Service_Plan_Id": "f057aab1-b184-49b2-85c0-881b02a405c5", + "Service_Plans_Included_Friendly_Names": "Microsoft Entra Private Access" + }, + { + "Product_Display_Name": "Microsoft Entra Suite Add-on for Microsoft Entra ID P2", + "String_Id": "Microsoft_Entra_Suite_Step_Up_for_Microsoft_Entra_ID_P2", + "GUID": "2ef3064c-c95c-426c-96dd-9ffeaa2f2c37", + "Service_Plan_Name": "Verifiable_Credentials_Service_Request", + "Service_Plan_Id": "aae826b7-14cd-4691-8178-2b312f7072ea", + "Service_Plans_Included_Friendly_Names": "Verifiable Credentials Service Request" + }, + { + "Product_Display_Name": "Microsoft Entra Suite Add-on for Microsoft Entra ID P2", + "String_Id": "Microsoft_Entra_Suite_Step_Up_for_Microsoft_Entra_ID_P2", + "GUID": "2ef3064c-c95c-426c-96dd-9ffeaa2f2c37", + "Service_Plan_Name": "Entra_Identity_Governance", + "Service_Plan_Id": "e866a266-3cff-43a3-acca-0c90a7e00c8b", + "Service_Plans_Included_Friendly_Names": "Entra Identity Governance" + }, { "Product_Display_Name": "Microsoft Fabric (Free)", "String_Id": "POWER_BI_STANDARD", @@ -29087,6 +29359,14 @@ "Service_Plan_Id": "2049e525-b859-401b-b2a0-e0a31c4b1fe4", "Service_Plans_Included_Friendly_Names": "Power BI (free)" }, + { + "Product_Display_Name": "Microsoft Fabric (Free)", + "String_Id": "POWER_BI_STANDARD", + "GUID": "a403ebcc-fae0-4ca2-8c8c-7a907fd6c235", + "Service_Plan_Name": "PURVIEW_DISCOVERY", + "Service_Plan_Id": "c948ea65-2053-4a5a-8a62-9eaaaf11b522", + "Service_Plans_Included_Friendly_Names": "Purview Discovery" + }, { "Product_Display_Name": "Microsoft Fabric (Free) for faculty", "String_Id": "POWER_BI_STANDARD_FACULTY", @@ -29127,6 +29407,14 @@ "Service_Plan_Id": "d736def0-1fde-43f0-a5be-e3f8b2de6e41", "Service_Plans_Included_Friendly_Names": "MS IMAGINE ACADEMY" }, + { + "Product_Display_Name": "Microsoft Intune Advanced Analytics", + "String_Id": "Microsoft_Intune_Advanced_Analytics", + "GUID": "5e36d0d4-e9e5-4052-aba0-0257465c9b86", + "Service_Plan_Name": "Intune_AdvancedEA", + "Service_Plan_Id": "2a4baa0e-5e99-4c38-b1f2-6864960f1bd1", + "Service_Plans_Included_Friendly_Names": "Microsoft Intune Advanced Analytics" + }, { "Product_Display_Name": "Microsoft Intune Device", "String_Id": "INTUNE_A_D", @@ -29575,6 +29863,38 @@ "Service_Plan_Id": "113feb6c-3fe4-4440-bddc-54d774bf0318", "Service_Plans_Included_Friendly_Names": "Exchange Foundation" }, + { + "Product_Display_Name": "Microsoft Sustainability Manager Premium USL Plus", + "String_Id": "MICROSOFT_SUSTAINABILITY_MANAGER_PREMIUM_USL_ADDON", + "GUID": "9d576ffb-dd32-4c33-91ee-91625b61424a", + "Service_Plan_Name": "MCS_BIZAPPS_CLOUD_FOR_SUSTAINABILITY_USL_PLUS", + "Service_Plan_Id": "beaf5b5c-d11c-4417-b5cb-cd9f9e6719b0", + "Service_Plans_Included_Friendly_Names": "MCS - BizApps Cloud for Sustainability USL Plus" + }, + { + "Product_Display_Name": "Microsoft Sustainability Manager Premium USL Plus", + "String_Id": "MICROSOFT_SUSTAINABILITY_MANAGER_PREMIUM_USL_ADDON", + "GUID": "9d576ffb-dd32-4c33-91ee-91625b61424a", + "Service_Plan_Name": "POWER_APPS_FOR_MCS_USL_PLUS", + "Service_Plan_Id": "c5502fe7-406d-442a-827f-4948b821ba08", + "Service_Plans_Included_Friendly_Names": "Power Apps for Cloud for Sustainability USL Plus" + }, + { + "Product_Display_Name": "Microsoft Sustainability Manager Premium USL Plus", + "String_Id": "MICROSOFT_SUSTAINABILITY_MANAGER_PREMIUM_USL_ADDON", + "GUID": "9d576ffb-dd32-4c33-91ee-91625b61424a", + "Service_Plan_Name": "POWER_AUTOMATE_FOR_MCS_USL_PLUS", + "Service_Plan_Id": "1c22bb50-96fb-49e5-baa6-195cab19eee2", + "Service_Plans_Included_Friendly_Names": "Power Automate for Cloud for Sustainability USL Plus" + }, + { + "Product_Display_Name": "Microsoft Sustainability Manager Premium USL Plus", + "String_Id": "MICROSOFT_SUSTAINABILITY_MANAGER_PREMIUM_USL_ADDON", + "GUID": "9d576ffb-dd32-4c33-91ee-91625b61424a", + "Service_Plan_Name": "EXCHANGE_S_FOUNDATION", + "Service_Plan_Id": "113feb6c-3fe4-4440-bddc-54d774bf0318", + "Service_Plans_Included_Friendly_Names": "Exchange Foundation" + }, { "Product_Display_Name": "Microsoft Sustainability Manager USL Essentials", "String_Id": "Microsoft_Cloud_for_Sustainability_USL", @@ -30477,7 +30797,7 @@ "GUID": "c25e2b36-e161-4946-bef2-69239729f690", "Service_Plan_Name": "AAD_BASIC_EDU", "Service_Plan_Id": "1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426", - "Service_Plans_Included_Friendly_Names": "Microsoft Entra ID Basic for Education" + "Service_Plans_Included_Friendly_Names": "Azure Active Directory Basic for Education" }, { "Product_Display_Name": "Microsoft Teams Rooms Pro for EDU", @@ -30519,6 +30839,14 @@ "Service_Plan_Id": "0feaeb32-d00e-4d66-bd5a-43b5b83db82c", "Service_Plans_Included_Friendly_Names": "Skype for Business Online (Plan 2)" }, + { + "Product_Display_Name": "Microsoft Teams Rooms Pro for EDU", + "String_Id": "Microsoft_Teams_Rooms_Pro_FAC", + "GUID": "c25e2b36-e161-4946-bef2-69239729f690", + "Service_Plan_Name": "Teams_Rooms_Pro", + "Service_Plan_Id": "0374d34c-6be4-4dbb-b3f0-26105db0b28a", + "Service_Plans_Included_Friendly_Names": "Teams Rooms Pro" + }, { "Product_Display_Name": "Microsoft Teams Rooms Pro for EDU", "String_Id": "Microsoft_Teams_Rooms_Pro_FAC", @@ -30567,6 +30895,14 @@ "Service_Plan_Id": "c1ec4a95-1f05-45b3-a911-aa3fa01094f5", "Service_Plans_Included_Friendly_Names": "Microsoft Intune Plan 1" }, + { + "Product_Display_Name": "Microsoft Teams Rooms Pro for EDU", + "String_Id": "Microsoft_Teams_Rooms_Pro_FAC", + "GUID": "c25e2b36-e161-4946-bef2-69239729f690", + "Service_Plan_Name": "SPECIALTY_DEVICES", + "Service_Plan_Id": "cfce7ae3-4b41-4438-999c-c0e91f3b7fb9", + "Service_Plans_Included_Friendly_Names": "Specialty devices" + }, { "Product_Display_Name": "Microsoft Teams Rooms Pro for GCC", "String_Id": "Microsoft_Teams_Rooms_Pro_GCC", @@ -31325,15 +31661,15 @@ "GUID": "94763226-9b3c-4e75-a931-5c89701abe66", "Service_Plan_Name": "AAD_BASIC_EDU", "Service_Plan_Id": "1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426", - "Service_Plans_Included_Friendly_Names": "Microsoft Entra ID Basic for Education" + "Service_Plans_Included_Friendly_Names": "Azure Active Directory Basic for Education" }, { "Product_Display_Name": "Office 365 A1 for faculty", "String_Id": "STANDARDWOFFPACK_FACULTY", "GUID": "94763226-9b3c-4e75-a931-5c89701abe66", - "Service_Plan_Name": "DYN365_CDS_O365_P1", - "Service_Plan_Id": "40b010bb-0b69-4654-ac5e-ba161433f4b4", - "Service_Plans_Included_Friendly_Names": "Common Data Service - O365 P1" + "Service_Plan_Name": "RMS_S_ENTERPRISE", + "Service_Plan_Id": "bea4c11e-220a-4e6d-8eb8-8ea15d019f90", + "Service_Plans_Included_Friendly_Names": "Azure Rights Management" }, { "Product_Display_Name": "Office 365 A1 for faculty", @@ -31363,9 +31699,9 @@ "Product_Display_Name": "Office 365 A1 for faculty", "String_Id": "STANDARDWOFFPACK_FACULTY", "GUID": "94763226-9b3c-4e75-a931-5c89701abe66", - "Service_Plan_Name": "RMS_S_ENTERPRISE", - "Service_Plan_Id": "bea4c11e-220a-4e6d-8eb8-8ea15d019f90", - "Service_Plans_Included_Friendly_Names": "Microsoft Microsoft Entra Rights" + "Service_Plan_Name": "M365_LIGHTHOUSE_CUSTOMER_PLAN1", + "Service_Plan_Id": "6f23d6a9-adbf-481c-8538-b4c095654487", + "Service_Plans_Included_Friendly_Names": "Microsoft 365 Lighthouse (Plan 1)" }, { "Product_Display_Name": "Office 365 A1 for faculty", @@ -31381,7 +31717,7 @@ "GUID": "94763226-9b3c-4e75-a931-5c89701abe66", "Service_Plan_Name": "KAIZALA_O365_P2", "Service_Plan_Id": "54fc630f-5a40-48ee-8965-af0503c1386e", - "Service_Plans_Included_Friendly_Names": "Microsoft Kaizala Pro Plan 2" + "Service_Plans_Included_Friendly_Names": "Microsoft Kaizala Pro" }, { "Product_Display_Name": "Office 365 A1 for faculty", @@ -31459,25 +31795,17 @@ "Product_Display_Name": "Office 365 A1 for faculty", "String_Id": "STANDARDWOFFPACK_FACULTY", "GUID": "94763226-9b3c-4e75-a931-5c89701abe66", - "Service_Plan_Name": "POWERAPPS_O365_P2", - "Service_Plan_Id": "c68f8d98-5534-41c8-bf36-22fa496fa792", - "Service_Plans_Included_Friendly_Names": "Power Apps for Office 365" - }, - { - "Product_Display_Name": "Office 365 A1 for faculty", - "String_Id": "STANDARDWOFFPACK_FACULTY", - "GUID": "94763226-9b3c-4e75-a931-5c89701abe66", - "Service_Plan_Name": "FLOW_O365_P2", - "Service_Plan_Id": "76846ad7-7776-4c40-a281-a386362dd1b9", - "Service_Plans_Included_Friendly_Names": "Power Automate for Office 365" + "Service_Plan_Name": "PROJECT_O365_P1", + "Service_Plan_Id": "a55dfd10-0864-46d9-a3cd-da5991a3e0e2", + "Service_Plans_Included_Friendly_Names": "Project for Office (Plan E1)" }, { "Product_Display_Name": "Office 365 A1 for faculty", "String_Id": "STANDARDWOFFPACK_FACULTY", "GUID": "94763226-9b3c-4e75-a931-5c89701abe66", - "Service_Plan_Name": "PROJECT_O365_P1", - "Service_Plan_Id": "a55dfd10-0864-46d9-a3cd-da5991a3e0e2", - "Service_Plans_Included_Friendly_Names": "Project for Office (Plan E1)" + "Service_Plan_Name": "Bing_Chat_Enterprise", + "Service_Plan_Id": "0d0c0d31-fae7-41f2-b909-eaf4d7f26dba", + "Service_Plans_Included_Friendly_Names": "RETIRED - Commercial data protection for Microsoft Copilot" }, { "Product_Display_Name": "Office 365 A1 for faculty", @@ -31543,6 +31871,30 @@ "Service_Plan_Id": "2078e8df-cff6-4290-98cb-5408261a760a", "Service_Plans_Included_Friendly_Names": "Yammer for Academic" }, + { + "Product_Display_Name": "Office 365 A1 for faculty", + "String_Id": "STANDARDWOFFPACK_FACULTY", + "GUID": "94763226-9b3c-4e75-a931-5c89701abe66", + "Service_Plan_Name": "DYN365_CDS_O365_P1", + "Service_Plan_Id": "40b010bb-0b69-4654-ac5e-ba161433f4b4", + "Service_Plans_Included_Friendly_Names": "Common Data Service" + }, + { + "Product_Display_Name": "Office 365 A1 for faculty", + "String_Id": "STANDARDWOFFPACK_FACULTY", + "GUID": "94763226-9b3c-4e75-a931-5c89701abe66", + "Service_Plan_Name": "POWERAPPS_O365_P2", + "Service_Plan_Id": "c68f8d98-5534-41c8-bf36-22fa496fa792", + "Service_Plans_Included_Friendly_Names": "Power Apps for Office 365" + }, + { + "Product_Display_Name": "Office 365 A1 for faculty", + "String_Id": "STANDARDWOFFPACK_FACULTY", + "GUID": "94763226-9b3c-4e75-a931-5c89701abe66", + "Service_Plan_Name": "FLOW_O365_P2", + "Service_Plan_Id": "76846ad7-7776-4c40-a281-a386362dd1b9", + "Service_Plans_Included_Friendly_Names": "Power Automate for Office 365" + }, { "Product_Display_Name": "Office 365 A1 Plus for faculty", "String_Id": "STANDARDWOFFPACK_IW_FACULTY", @@ -31765,15 +32117,15 @@ "GUID": "314c4481-f395-4525-be8b-2ec4bb1e9d91", "Service_Plan_Name": "AAD_BASIC_EDU", "Service_Plan_Id": "1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426", - "Service_Plans_Included_Friendly_Names": "Microsoft Entra ID Basic for Education" + "Service_Plans_Included_Friendly_Names": "Azure Active Directory Basic for Education" }, { "Product_Display_Name": "Office 365 A1 for students", "String_Id": "STANDARDWOFFPACK_STUDENT", "GUID": "314c4481-f395-4525-be8b-2ec4bb1e9d91", - "Service_Plan_Name": "DYN365_CDS_O365_P1", - "Service_Plan_Id": "40b010bb-0b69-4654-ac5e-ba161433f4b4", - "Service_Plans_Included_Friendly_Names": "Common Data Service - O365 P1" + "Service_Plan_Name": "RMS_S_ENTERPRISE", + "Service_Plan_Id": "bea4c11e-220a-4e6d-8eb8-8ea15d019f90", + "Service_Plans_Included_Friendly_Names": "Azure Rights Management" }, { "Product_Display_Name": "Office 365 A1 for students", @@ -31803,9 +32155,9 @@ "Product_Display_Name": "Office 365 A1 for students", "String_Id": "STANDARDWOFFPACK_STUDENT", "GUID": "314c4481-f395-4525-be8b-2ec4bb1e9d91", - "Service_Plan_Name": "RMS_S_ENTERPRISE", - "Service_Plan_Id": "bea4c11e-220a-4e6d-8eb8-8ea15d019f90", - "Service_Plans_Included_Friendly_Names": "Microsoft Microsoft Entra Rights" + "Service_Plan_Name": "M365_LIGHTHOUSE_CUSTOMER_PLAN1", + "Service_Plan_Id": "6f23d6a9-adbf-481c-8538-b4c095654487", + "Service_Plans_Included_Friendly_Names": "Microsoft 365 Lighthouse (Plan 1)" }, { "Product_Display_Name": "Office 365 A1 for students", @@ -31821,7 +32173,7 @@ "GUID": "314c4481-f395-4525-be8b-2ec4bb1e9d91", "Service_Plan_Name": "KAIZALA_O365_P2", "Service_Plan_Id": "54fc630f-5a40-48ee-8965-af0503c1386e", - "Service_Plans_Included_Friendly_Names": "Microsoft Kaizala Pro Plan 2" + "Service_Plans_Included_Friendly_Names": "Microsoft Kaizala Pro" }, { "Product_Display_Name": "Office 365 A1 for students", @@ -31891,25 +32243,17 @@ "Product_Display_Name": "Office 365 A1 for students", "String_Id": "STANDARDWOFFPACK_STUDENT", "GUID": "314c4481-f395-4525-be8b-2ec4bb1e9d91", - "Service_Plan_Name": "POWERAPPS_O365_P2", - "Service_Plan_Id": "c68f8d98-5534-41c8-bf36-22fa496fa792", - "Service_Plans_Included_Friendly_Names": "Power Apps for Office 365" - }, - { - "Product_Display_Name": "Office 365 A1 for students", - "String_Id": "STANDARDWOFFPACK_STUDENT", - "GUID": "314c4481-f395-4525-be8b-2ec4bb1e9d91", - "Service_Plan_Name": "FLOW_O365_P2", - "Service_Plan_Id": "76846ad7-7776-4c40-a281-a386362dd1b9", - "Service_Plans_Included_Friendly_Names": "Power Automate for Office 365" + "Service_Plan_Name": "PROJECT_O365_P1", + "Service_Plan_Id": "a55dfd10-0864-46d9-a3cd-da5991a3e0e2", + "Service_Plans_Included_Friendly_Names": "Project for Office (Plan E1)" }, { "Product_Display_Name": "Office 365 A1 for students", "String_Id": "STANDARDWOFFPACK_STUDENT", "GUID": "314c4481-f395-4525-be8b-2ec4bb1e9d91", - "Service_Plan_Name": "PROJECT_O365_P1", - "Service_Plan_Id": "a55dfd10-0864-46d9-a3cd-da5991a3e0e2", - "Service_Plans_Included_Friendly_Names": "Project for Office (Plan E1)" + "Service_Plan_Name": "Bing_Chat_Enterprise", + "Service_Plan_Id": "0d0c0d31-fae7-41f2-b909-eaf4d7f26dba", + "Service_Plans_Included_Friendly_Names": "RETIRED - Commercial data protection for Microsoft Copilot" }, { "Product_Display_Name": "Office 365 A1 for students", @@ -31967,6 +32311,30 @@ "Service_Plan_Id": "2078e8df-cff6-4290-98cb-5408261a760a", "Service_Plans_Included_Friendly_Names": "Yammer for Academic" }, + { + "Product_Display_Name": "Office 365 A1 for students", + "String_Id": "STANDARDWOFFPACK_STUDENT", + "GUID": "314c4481-f395-4525-be8b-2ec4bb1e9d91", + "Service_Plan_Name": "DYN365_CDS_O365_P1", + "Service_Plan_Id": "40b010bb-0b69-4654-ac5e-ba161433f4b4", + "Service_Plans_Included_Friendly_Names": "Common Data Service" + }, + { + "Product_Display_Name": "Office 365 A1 for students", + "String_Id": "STANDARDWOFFPACK_STUDENT", + "GUID": "314c4481-f395-4525-be8b-2ec4bb1e9d91", + "Service_Plan_Name": "POWERAPPS_O365_P2", + "Service_Plan_Id": "c68f8d98-5534-41c8-bf36-22fa496fa792", + "Service_Plans_Included_Friendly_Names": "Power Apps for Office 365" + }, + { + "Product_Display_Name": "Office 365 A1 for students", + "String_Id": "STANDARDWOFFPACK_STUDENT", + "GUID": "314c4481-f395-4525-be8b-2ec4bb1e9d91", + "Service_Plan_Name": "FLOW_O365_P2", + "Service_Plan_Id": "76846ad7-7776-4c40-a281-a386362dd1b9", + "Service_Plans_Included_Friendly_Names": "Power Automate for Office 365" + }, { "Product_Display_Name": "Office 365 A1 Plus for students", "String_Id": "STANDARDWOFFPACK_IW_STUDENT", @@ -40483,9 +40851,9 @@ "Product_Display_Name": "Power Apps Premium", "String_Id": "POWERAPPS_PER_USER", "GUID": "b30411f5-fea1-4a59-9ad9-3db7c7ead579", - "Service_Plan_Name": "DYN365_CDS_P2", - "Service_Plan_Id": "6ea4c1ef-c259-46df-bce2-943342cd3cb2", - "Service_Plans_Included_Friendly_Names": "Common Data Service - P2" + "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": "Power Apps Premium", @@ -40495,6 +40863,30 @@ "Service_Plan_Id": "113feb6c-3fe4-4440-bddc-54d774bf0318", "Service_Plans_Included_Friendly_Names": "Exchange Foundation" }, + { + "Product_Display_Name": "Power Apps Premium", + "String_Id": "POWERAPPS_PER_USER", + "GUID": "b30411f5-fea1-4a59-9ad9-3db7c7ead579", + "Service_Plan_Name": "CDSAICAPACITY_PERUSER_NEW", + "Service_Plan_Id": "74d93933-6f22-436e-9441-66d205435abb", + "Service_Plans_Included_Friendly_Names": "AI Builder capacity Per User add-on" + }, + { + "Product_Display_Name": "Power Apps Premium", + "String_Id": "POWERAPPS_PER_USER", + "GUID": "b30411f5-fea1-4a59-9ad9-3db7c7ead579", + "Service_Plan_Name": "DYN365_CDS_P2", + "Service_Plan_Id": "6ea4c1ef-c259-46df-bce2-943342cd3cb2", + "Service_Plans_Included_Friendly_Names": "Common Data Service" + }, + { + "Product_Display_Name": "Power Apps Premium", + "String_Id": "POWERAPPS_PER_USER", + "GUID": "b30411f5-fea1-4a59-9ad9-3db7c7ead579", + "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" + }, { "Product_Display_Name": "Power Apps Premium", "String_Id": "POWERAPPS_PER_USER", @@ -41047,6 +41439,22 @@ "Service_Plan_Id": "0bf3c642-7bb5-4ccc-884e-59d09df0266c", "Service_Plans_Included_Friendly_Names": "Power BI Premium Per User" }, + { + "Product_Display_Name": "Power BI Premium Per User Add-On for Faculty", + "String_Id": "PBI_PREMIUM_PER_USER_ADDON_FACULTY", + "GUID": "c05b235f-be75-4029-8851-6a4170758eef", + "Service_Plan_Name": "BI_AZURE_P3", + "Service_Plan_Id": "0bf3c642-7bb5-4ccc-884e-59d09df0266c", + "Service_Plans_Included_Friendly_Names": "Power BI Premium Per User" + }, + { + "Product_Display_Name": "Power BI Premium Per User Add-On for Faculty", + "String_Id": "PBI_PREMIUM_PER_USER_ADDON_FACULTY", + "GUID": "c05b235f-be75-4029-8851-6a4170758eef", + "Service_Plan_Name": "PURVIEW_DISCOVERY", + "Service_Plan_Id": "c948ea65-2053-4a5a-8a62-9eaaaf11b522", + "Service_Plans_Included_Friendly_Names": "Purview Discovery" + }, { "Product_Display_Name": "Power BI Premium Per User Add-On for GCC", "String_Id": "PBI_PREMIUM_PER_USER_ADDON_CE_GCC", @@ -43415,6 +43823,70 @@ "Service_Plan_Id": "78b58230-ec7e-4309-913c-93a45cc4735b", "Service_Plans_Included_Friendly_Names": "Microsoft Teams Premium Webinar" }, + { + "Product_Display_Name": "Teams Premium for Faculty", + "String_Id": "Teams_Premium_for_Faculty", + "GUID": "960a972f-d017-4a17-8f64-b42c8035bc7d", + "Service_Plan_Name": "MICROSOFT_ECDN", + "Service_Plan_Id": "85704d55-2e73-47ee-93b4-4b8ea14db92b", + "Service_Plans_Included_Friendly_Names": "Microsoft eCDN" + }, + { + "Product_Display_Name": "Teams Premium for Faculty", + "String_Id": "Teams_Premium_for_Faculty", + "GUID": "960a972f-d017-4a17-8f64-b42c8035bc7d", + "Service_Plan_Name": "TEAMSPRO_MGMT", + "Service_Plan_Id": "0504111f-feb8-4a3c-992a-70280f9a2869", + "Service_Plans_Included_Friendly_Names": "Microsoft Teams Premium Intelligent" + }, + { + "Product_Display_Name": "Teams Premium for Faculty", + "String_Id": "Teams_Premium_for_Faculty", + "GUID": "960a972f-d017-4a17-8f64-b42c8035bc7d", + "Service_Plan_Name": "TEAMSPRO_CUST", + "Service_Plan_Id": "cc8c0802-a325-43df-8cba-995d0c6cb373", + "Service_Plans_Included_Friendly_Names": "Microsoft Teams Premium Personalized" + }, + { + "Product_Display_Name": "Teams Premium for Faculty", + "String_Id": "Teams_Premium_for_Faculty", + "GUID": "960a972f-d017-4a17-8f64-b42c8035bc7d", + "Service_Plan_Name": "TEAMSPRO_PROTECTION", + "Service_Plan_Id": "f8b44f54-18bb-46a3-9658-44ab58712968", + "Service_Plans_Included_Friendly_Names": "Microsoft Teams Premium Secure" + }, + { + "Product_Display_Name": "Teams Premium for Faculty", + "String_Id": "Teams_Premium_for_Faculty", + "GUID": "960a972f-d017-4a17-8f64-b42c8035bc7d", + "Service_Plan_Name": "TEAMSPRO_VIRTUALAPPT", + "Service_Plan_Id": "9104f592-f2a7-4f77-904c-ca5a5715883f", + "Service_Plans_Included_Friendly_Names": "Microsoft Teams Premium Virtual Appointment" + }, + { + "Product_Display_Name": "Teams Premium for Faculty", + "String_Id": "Teams_Premium_for_Faculty", + "GUID": "960a972f-d017-4a17-8f64-b42c8035bc7d", + "Service_Plan_Name": "MCO_VIRTUAL_APPT", + "Service_Plan_Id": "711413d0-b36e-4cd4-93db-0a50a4ab7ea3", + "Service_Plans_Included_Friendly_Names": "Microsoft Teams Premium Virtual Appointments" + }, + { + "Product_Display_Name": "Teams Premium for Faculty", + "String_Id": "Teams_Premium_for_Faculty", + "GUID": "960a972f-d017-4a17-8f64-b42c8035bc7d", + "Service_Plan_Name": "TEAMSPRO_WEBINAR", + "Service_Plan_Id": "78b58230-ec7e-4309-913c-93a45cc4735b", + "Service_Plans_Included_Friendly_Names": "Microsoft Teams Premium Webinar" + }, + { + "Product_Display_Name": "Teams Premium for Faculty", + "String_Id": "Teams_Premium_for_Faculty", + "GUID": "960a972f-d017-4a17-8f64-b42c8035bc7d", + "Service_Plan_Name": "QUEUES_APP", + "Service_Plan_Id": "ab2d4fb5-f80a-4bf1-a11d-7f1da254041b", + "Service_Plans_Included_Friendly_Names": "Queues app for Microsoft Teams" + }, { "Product_Display_Name": "Teams Rooms Premium", "String_Id": "MTR_PREM", From f4a6da515679744c0ee56e016ae9e95170cbdb51 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sat, 12 Jul 2025 15:07:18 +0200 Subject: [PATCH 099/112] add disable Ca --- src/components/CippWizard/CippCAForm.jsx | 7 +++ src/components/ExecutiveReportButton.js | 80 ++++++++++++++++++++---- src/data/standards.json | 5 ++ 3 files changed, 81 insertions(+), 11 deletions(-) diff --git a/src/components/CippWizard/CippCAForm.jsx b/src/components/CippWizard/CippCAForm.jsx index 7a6fdd98ca1b..d0ee1a657186 100644 --- a/src/components/CippWizard/CippCAForm.jsx +++ b/src/components/CippWizard/CippCAForm.jsx @@ -82,6 +82,13 @@ export const CippCAForm = (props) => { label="Overwrite Existing Policy" formControl={formControl} /> + + 0 ? standardDef.tag.slice(0, 2).join(", ") // Show first 2 tags : "No tags"; - processedStandards.push({ name: standardDef.label, description: @@ -904,7 +903,12 @@ const ExecutiveReportDocument = ({ {control.description} - {control.tags} + {(() => { + if (typeof control.tags === 'object') { + console.log('DEBUG: control.tags is an object:', control.tags, 'for control:', control.name); + } + return control.tags; + })()} {control.status} @@ -1268,7 +1272,13 @@ const ExecutiveReportDocument = ({ {licensingData.map((license, index) => ( - {license.License || license.license || "N/A"} + {(() => { + const licenseValue = license.License || license.license || "N/A"; + if (typeof licenseValue === 'object') { + console.log('DEBUG: license name is an object:', licenseValue, 'full license:', license); + } + return licenseValue; + })()} - {license.CountUsed || license.countUsed || "0"} + {(() => { + const countUsed = license.CountUsed || license.countUsed || "0"; + if (typeof countUsed === 'object') { + console.log('DEBUG: license.CountUsed is an object:', countUsed, 'full license:', license); + } + return countUsed; + })()} - {license.CountAvailable || license.countAvailable || "0"} + {(() => { + const countAvailable = license.CountAvailable || license.countAvailable || "0"; + if (typeof countAvailable === 'object') { + console.log('DEBUG: license.CountAvailable is an object:', countAvailable, 'full license:', license); + } + return countAvailable; + })()} - {license.TotalLicenses || license.totalLicenses || "0"} + {(() => { + const totalLicenses = license.TotalLicenses || license.totalLicenses || "0"; + if (typeof totalLicenses === 'object') { + console.log('DEBUG: license.TotalLicenses is an object:', totalLicenses, 'full license:', license); + } + return totalLicenses; + })()} ))} @@ -1450,10 +1478,22 @@ const ExecutiveReportDocument = ({ return ( - {device.deviceName || "N/A"} + {(() => { + const deviceName = device.deviceName || "N/A"; + if (typeof deviceName === 'object') { + console.log('DEBUG: device.deviceName is an object:', deviceName, 'full device:', device); + } + return deviceName; + })()} - {device.operatingSystem || "N/A"} + {(() => { + const operatingSystem = device.operatingSystem || "N/A"; + if (typeof operatingSystem === 'object') { + console.log('DEBUG: device.operatingSystem is an object:', operatingSystem, 'full device:', device); + } + return operatingSystem; + })()} - {device.complianceState || "Unknown"} + {(() => { + const complianceState = device.complianceState || "Unknown"; + if (typeof complianceState === 'object') { + console.log('DEBUG: device.complianceState is an object:', complianceState, 'full device:', device); + } + return complianceState; + })()} {lastSync} @@ -1632,7 +1678,13 @@ const ExecutiveReportDocument = ({ return ( - {policy.displayName || "N/A"} + {(() => { + const displayName = policy.displayName || "N/A"; + if (typeof displayName === 'object') { + console.log('DEBUG: policy.displayName is an object:', displayName, 'full policy:', policy); + } + return displayName; + })()} @@ -1640,7 +1692,13 @@ const ExecutiveReportDocument = ({ - {policy.includeApplications || "All"} + {(() => { + const includeApplications = policy.includeApplications || "All"; + if (typeof includeApplications === 'object') { + console.log('DEBUG: policy.includeApplications is an object:', includeApplications, 'full policy:', policy); + } + return includeApplications; + })()} {getControlsText(policy)} diff --git a/src/data/standards.json b/src/data/standards.json index d8782e8a245f..b6ea7d51dec7 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -4686,6 +4686,11 @@ "label": "Set to report only" } ] + }, + { + "type": "switch", + "name": "DisableSD", + "label": "Disable Security Defaults when deploying policy" } ] }, From 38312f6c622f9ad2d47ff35784abd5647b200da8 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sat, 12 Jul 2025 10:58:44 -0400 Subject: [PATCH 100/112] add support for details objects in api results --- .../CippComponents/CippApiResults.jsx | 70 +++++++++++++++++-- .../CippComponents/CippCopyToClipboard.jsx | 2 +- 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/src/components/CippComponents/CippApiResults.jsx b/src/components/CippComponents/CippApiResults.jsx index bc0d12572885..388b27984ced 100644 --- a/src/components/CippComponents/CippApiResults.jsx +++ b/src/components/CippComponents/CippApiResults.jsx @@ -1,4 +1,4 @@ -import { Close, Download, Help } from "@mui/icons-material"; +import { Close, Download, Help, ExpandMore, ExpandLess } from "@mui/icons-material"; import { Alert, CircularProgress, @@ -16,6 +16,7 @@ import { useEffect, useState, useMemo, useCallback } from "react"; import { getCippError } from "../../utils/get-cipp-error"; import { CippCopyToClipBoard } from "./CippCopyToClipboard"; import { CippDocsLookup } from "./CippDocsLookup"; +import { CippCodeBlock } from "./CippCodeBlock"; import React from "react"; import { CippTableDialog } from "./CippTableDialog"; import { EyeIcon } from "@heroicons/react/24/outline"; @@ -43,12 +44,14 @@ const extractAllResults = (data) => { const copyField = item.copyField || ""; const severity = typeof item.state === "string" ? item.state : getSeverity(item) ? "error" : "success"; + const details = item.details || null; if (text) { return { text, copyField, severity, + details, ...item, }; } @@ -123,6 +126,7 @@ export const CippApiResults = (props) => { const [errorVisible, setErrorVisible] = useState(false); const [fetchingVisible, setFetchingVisible] = useState(false); const [finalResults, setFinalResults] = useState([]); + const [showDetails, setShowDetails] = useState({}); const tableDialog = useDialog(); const pageTitle = `${document.title} - Results`; const correctResultObj = useMemo(() => { @@ -194,6 +198,10 @@ export const CippApiResults = (props) => { setFinalResults((prev) => prev.map((r) => (r.id === id ? { ...r, visible: false } : r))); }, []); + const toggleDetails = useCallback((id) => { + setShowDetails((prev) => ({ ...prev, [id]: !prev[id] })); + }, []); + const handleDownloadCsv = useCallback(() => { if (!finalResults?.length) return; @@ -272,7 +280,21 @@ export const CippApiResults = (props) => { { Get Help )} - + + + {resultObj.details && ( + + toggleDetails(resultObj.id)} + aria-label={showDetails[resultObj.id] ? "Hide Details" : "Show Details"} + > + {showDetails[resultObj.id] ? ( + + ) : ( + + )} + + + )} { } > - {resultObj.text} + + {resultObj.text} + {resultObj.details && ( + + + + + + )} + diff --git a/src/components/CippComponents/CippCopyToClipboard.jsx b/src/components/CippComponents/CippCopyToClipboard.jsx index 6d3603790df7..cdc18f224f8b 100644 --- a/src/components/CippComponents/CippCopyToClipboard.jsx +++ b/src/components/CippComponents/CippCopyToClipboard.jsx @@ -17,7 +17,7 @@ export const CippCopyToClipBoard = (props) => { return ( - + From 5ab9caace3a593043aee1626cac519e1695db2bb Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sat, 12 Jul 2025 22:43:48 +0200 Subject: [PATCH 101/112] new allignment score page --- src/components/ExecutiveReportButton.js | 93 ++++++++++++++----- src/layouts/config.js | 7 +- .../standards/tenant-alignment/index.js | 32 +++++++ 3 files changed, 110 insertions(+), 22 deletions(-) create mode 100644 src/pages/tenant/standards/tenant-alignment/index.js diff --git a/src/components/ExecutiveReportButton.js b/src/components/ExecutiveReportButton.js index 664b70aeb407..764a568e7959 100644 --- a/src/components/ExecutiveReportButton.js +++ b/src/components/ExecutiveReportButton.js @@ -904,8 +904,13 @@ const ExecutiveReportDocument = ({ {(() => { - if (typeof control.tags === 'object') { - console.log('DEBUG: control.tags is an object:', control.tags, 'for control:', control.name); + if (typeof control.tags === "object") { + console.log( + "DEBUG: control.tags is an object:", + control.tags, + "for control:", + control.name + ); } return control.tags; })()} @@ -1274,8 +1279,13 @@ const ExecutiveReportDocument = ({ {(() => { const licenseValue = license.License || license.license || "N/A"; - if (typeof licenseValue === 'object') { - console.log('DEBUG: license name is an object:', licenseValue, 'full license:', license); + if (typeof licenseValue === "object") { + console.log( + "DEBUG: license name is an object:", + licenseValue, + "full license:", + license + ); } return licenseValue; })()} @@ -1288,8 +1298,13 @@ const ExecutiveReportDocument = ({ > {(() => { const countUsed = license.CountUsed || license.countUsed || "0"; - if (typeof countUsed === 'object') { - console.log('DEBUG: license.CountUsed is an object:', countUsed, 'full license:', license); + if (typeof countUsed === "object") { + console.log( + "DEBUG: license.CountUsed is an object:", + countUsed, + "full license:", + license + ); } return countUsed; })()} @@ -1298,9 +1313,15 @@ const ExecutiveReportDocument = ({ style={[styles.cellName, { width: 60, textAlign: "center", fontSize: 8 }]} > {(() => { - const countAvailable = license.CountAvailable || license.countAvailable || "0"; - if (typeof countAvailable === 'object') { - console.log('DEBUG: license.CountAvailable is an object:', countAvailable, 'full license:', license); + const countAvailable = + license.CountAvailable || license.countAvailable || "0"; + if (typeof countAvailable === "object") { + console.log( + "DEBUG: license.CountAvailable is an object:", + countAvailable, + "full license:", + license + ); } return countAvailable; })()} @@ -1313,8 +1334,13 @@ const ExecutiveReportDocument = ({ > {(() => { const totalLicenses = license.TotalLicenses || license.totalLicenses || "0"; - if (typeof totalLicenses === 'object') { - console.log('DEBUG: license.TotalLicenses is an object:', totalLicenses, 'full license:', license); + if (typeof totalLicenses === "object") { + console.log( + "DEBUG: license.TotalLicenses is an object:", + totalLicenses, + "full license:", + license + ); } return totalLicenses; })()} @@ -1480,8 +1506,13 @@ const ExecutiveReportDocument = ({ {(() => { const deviceName = device.deviceName || "N/A"; - if (typeof deviceName === 'object') { - console.log('DEBUG: device.deviceName is an object:', deviceName, 'full device:', device); + if (typeof deviceName === "object") { + console.log( + "DEBUG: device.deviceName is an object:", + deviceName, + "full device:", + device + ); } return deviceName; })()} @@ -1489,8 +1520,13 @@ const ExecutiveReportDocument = ({ {(() => { const operatingSystem = device.operatingSystem || "N/A"; - if (typeof operatingSystem === 'object') { - console.log('DEBUG: device.operatingSystem is an object:', operatingSystem, 'full device:', device); + if (typeof operatingSystem === "object") { + console.log( + "DEBUG: device.operatingSystem is an object:", + operatingSystem, + "full device:", + device + ); } return operatingSystem; })()} @@ -1506,8 +1542,13 @@ const ExecutiveReportDocument = ({ > {(() => { const complianceState = device.complianceState || "Unknown"; - if (typeof complianceState === 'object') { - console.log('DEBUG: device.complianceState is an object:', complianceState, 'full device:', device); + if (typeof complianceState === "object") { + console.log( + "DEBUG: device.complianceState is an object:", + complianceState, + "full device:", + device + ); } return complianceState; })()} @@ -1680,8 +1721,13 @@ const ExecutiveReportDocument = ({ {(() => { const displayName = policy.displayName || "N/A"; - if (typeof displayName === 'object') { - console.log('DEBUG: policy.displayName is an object:', displayName, 'full policy:', policy); + if (typeof displayName === "object") { + console.log( + "DEBUG: policy.displayName is an object:", + displayName, + "full policy:", + policy + ); } return displayName; })()} @@ -1694,8 +1740,13 @@ const ExecutiveReportDocument = ({ {(() => { const includeApplications = policy.includeApplications || "All"; - if (typeof includeApplications === 'object') { - console.log('DEBUG: policy.includeApplications is an object:', includeApplications, 'full policy:', policy); + if (typeof includeApplications === "object") { + console.log( + "DEBUG: policy.includeApplications is an object:", + includeApplications, + "full policy:", + policy + ); } return includeApplications; })()} diff --git a/src/layouts/config.js b/src/layouts/config.js index 522f2038d740..f0991ef3fff5 100644 --- a/src/layouts/config.js +++ b/src/layouts/config.js @@ -205,10 +205,15 @@ export const nativeMenuItems = [ ], items: [ { - title: "Standards", + title: "Standard Templates", path: "/tenant/standards/list-standards", permissions: ["Tenant.Standards.*"], }, + { + title: "Tenant Alignment", + path: "/tenant/standards/tenant-alignment", + permissions: ["Tenant.Standards.*"], + }, { title: "Best Practice Analyser", path: "/tenant/standards/bpa-report", diff --git a/src/pages/tenant/standards/tenant-alignment/index.js b/src/pages/tenant/standards/tenant-alignment/index.js new file mode 100644 index 000000000000..65fc524a966b --- /dev/null +++ b/src/pages/tenant/standards/tenant-alignment/index.js @@ -0,0 +1,32 @@ +import { CippTablePage } from "/src/components/CippComponents/CippTablePage.jsx"; +import { Layout as DashboardLayout } from "/src/layouts/index.js"; +import { EyeIcon } from "@heroicons/react/24/outline"; + +const Page = () => { + const pageTitle = "Tenant Alignment"; + + const actions = [ + { + label: "View Tenant Report", + link: "/tenant/standards/compare?tenantFilter=[tenantFilter]&templateId=[standardId]", + icon: , + color: "info", + target: "_self", + }, + ]; + + return ( + + ); +}; + +Page.getLayout = (page) => {page}; + +export default Page; From 264726bef4aa4fe863ba4e74d5f8f6282d182435 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sat, 12 Jul 2025 23:25:42 +0200 Subject: [PATCH 102/112] interface pimpin; --- src/components/linearProgressWithLabel.jsx | 40 ++++++++++++++++++++-- src/utils/get-cipp-formatting.js | 9 +++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/components/linearProgressWithLabel.jsx b/src/components/linearProgressWithLabel.jsx index 1dea47fa4890..b7ecb2d0c38a 100644 --- a/src/components/linearProgressWithLabel.jsx +++ b/src/components/linearProgressWithLabel.jsx @@ -1,12 +1,48 @@ import { Box, LinearProgress } from "@mui/material"; export const LinearProgressWithLabel = (props) => { + const { value, colourLevels, addedLabel, ...otherProps } = props; + + // Function to determine color based on value and colourLevels + const getProgressColor = (value, colourLevels) => { + if (!colourLevels) { + return undefined; // Use default MUI color + } + + if (value >= 0 && value < 25) { + return colourLevels.level0to25 || "#f44336"; // Default red + } else if (value >= 25 && value < 50) { + return colourLevels.level25to50 || "#ff9800"; // Default orange + } else if (value >= 50 && value < 75) { + return colourLevels.level50to75 || "#ffeb3b"; // Default yellow + } else if (value >= 75 && value <= 100) { + return colourLevels.level75to100 || "#4caf50"; // Default green + } + + return undefined; // Fallback to default + }; + + const progressColor = getProgressColor(value, colourLevels); + return ( - + - {`${Math.round(props.value)}% ${props?.addedLabel ?? ""}`} + {`${Math.round(value)}% ${addedLabel ?? ""}`} ); }; diff --git a/src/utils/get-cipp-formatting.js b/src/utils/get-cipp-formatting.js index 37dd816fca98..9b8e9802f119 100644 --- a/src/utils/get-cipp-formatting.js +++ b/src/utils/get-cipp-formatting.js @@ -167,6 +167,7 @@ export const getCippFormatting = (data, cellName, type, canReceive, flatten = tr "LastOccurrence", "NotBefore", "NotAfter", + "latestDataCollection", ]; const matchDateTime = /[dD]ate[tT]ime/; @@ -195,6 +196,14 @@ export const getCippFormatting = (data, cellName, type, canReceive, flatten = tr return isText ? data : data; } + if (cellName === "alignmentScore") { + // Handle alignment score, return a percentage with a label + return isText ? ( + `${data}%` + ) : ( + + ); + } if (cellName === "RepeatsEvery") { //convert 1d to "Every 1 day", 1w to "Every 1 week" etc. const match = data.match(/(\d+)([a-zA-Z]+)/); From b75f1da2d17fd63fc6a3b1c92e1c5fda1423f3b4 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sun, 13 Jul 2025 01:03:20 +0200 Subject: [PATCH 103/112] Fixes standards --- src/data/standards.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data/standards.json b/src/data/standards.json index b6ea7d51dec7..ab8426a2b853 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -3185,13 +3185,13 @@ "name": "standards.intuneDeviceRetirementDays", "cat": "Intune Standards", "tag": [], - "helpText": "A value between 0 and 270 is supported. A value of 0 disables retirement, retired devices are removed from Intune after the specified number of days.", + "helpText": "A value between 31 and 365 is supported. retired devices are removed from Intune after the specified number of days.", "executiveText": "Automatically removes inactive devices from management after a specified period, helping maintain a clean device inventory and reducing security risks from abandoned or lost devices. This policy ensures that only actively used corporate devices remain in the management system.", "addedComponent": [ { "type": "number", "name": "standards.intuneDeviceRetirementDays.days", - "label": "Maximum days (0 equals disabled)" + "label": "Maximum days" } ], "label": "Set inactive device retirement days", From dc8657d1868e04d43c79db91c09b66c90cd12996 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sun, 13 Jul 2025 01:42:25 +0200 Subject: [PATCH 104/112] allow remediation again --- src/data/standards.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/standards.json b/src/data/standards.json index ab8426a2b853..a9a336b70b3a 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -1076,7 +1076,7 @@ "disabledFeatures": { "report": false, "warn": false, - "remediate": true + "remediate": false }, "label": "Cleanup stale Entra devices", "impact": "High Impact", From bbc6a6d5f73aba2a030b4a25920b351bb3f5c6f0 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 14 Jul 2025 00:13:18 +0200 Subject: [PATCH 105/112] Alerts added --- src/data/alerts.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/data/alerts.json b/src/data/alerts.json index af0270248982..9de9c369f85b 100644 --- a/src/data/alerts.json +++ b/src/data/alerts.json @@ -209,5 +209,15 @@ "label": "Alert on new risky users (P2 License Required)", "recommendedRunInterval": "30m", "description": "Monitors for new risky users in the tenant. Risky users are defined as users who have performed actions that are considered risky, such as password resets, MFA failures, or suspicious activity." + }, + { + "name": "LowTenantAlignment", + "label": "Alert on low tenant alignment percentage", + "requiresInput": true, + "inputType": "number", + "inputLabel": "Alert when alignment is below % (default: 99)", + "inputName": "InputValue", + "recommendedRunInterval": "1d", + "description": "Monitors tenant alignment scores against standards templates and alerts when the alignment percentage falls below the specified threshold. This helps ensure compliance across all managed tenants." } ] From 2cad20e71fd8d1ed640e0dabd8594bfebed9b272 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 14 Jul 2025 11:40:10 +0200 Subject: [PATCH 106/112] licensesing stuff --- src/pages/tenant/standards/tenant-alignment/index.js | 8 +++++++- src/utils/get-cipp-formatting.js | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/pages/tenant/standards/tenant-alignment/index.js b/src/pages/tenant/standards/tenant-alignment/index.js index 65fc524a966b..9dcaeee34145 100644 --- a/src/pages/tenant/standards/tenant-alignment/index.js +++ b/src/pages/tenant/standards/tenant-alignment/index.js @@ -21,7 +21,13 @@ const Page = () => { apiUrl="/api/ListTenantAlignment" tenantInTitle={false} actions={actions} - simpleColumns={["tenantFilter", "standardName", "alignmentScore", "latestDataCollection"]} + simpleColumns={[ + "tenantFilter", + "standardName", + "alignmentScore", + "LicenseMissingPercentage", + "latestDataCollection", + ]} queryKey="listTenantAlignment" /> ); diff --git a/src/utils/get-cipp-formatting.js b/src/utils/get-cipp-formatting.js index 9b8e9802f119..670120338df6 100644 --- a/src/utils/get-cipp-formatting.js +++ b/src/utils/get-cipp-formatting.js @@ -196,7 +196,7 @@ export const getCippFormatting = (data, cellName, type, canReceive, flatten = tr return isText ? data : data; } - if (cellName === "alignmentScore") { + if (cellName === "alignmentScore" || cellName === "LicenseMissingPercentage") { // Handle alignment score, return a percentage with a label return isText ? ( `${data}%` From cbb2bb8469f061b6a95ab0965bc7db48cc265269 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 14 Jul 2025 12:31:48 +0200 Subject: [PATCH 107/112] combined alignment score --- src/pages/tenant/standards/tenant-alignment/index.js | 1 + src/utils/get-cipp-formatting.js | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/pages/tenant/standards/tenant-alignment/index.js b/src/pages/tenant/standards/tenant-alignment/index.js index 9dcaeee34145..974946822cc5 100644 --- a/src/pages/tenant/standards/tenant-alignment/index.js +++ b/src/pages/tenant/standards/tenant-alignment/index.js @@ -26,6 +26,7 @@ const Page = () => { "standardName", "alignmentScore", "LicenseMissingPercentage", + "combinedAlignmentScore", "latestDataCollection", ]} queryKey="listTenantAlignment" diff --git a/src/utils/get-cipp-formatting.js b/src/utils/get-cipp-formatting.js index 670120338df6..505f071fba36 100644 --- a/src/utils/get-cipp-formatting.js +++ b/src/utils/get-cipp-formatting.js @@ -196,7 +196,11 @@ export const getCippFormatting = (data, cellName, type, canReceive, flatten = tr return isText ? data : data; } - if (cellName === "alignmentScore" || cellName === "LicenseMissingPercentage") { + if ( + cellName === "alignmentScore" || + cellName === "LicenseMissingPercentage" || + cellName === "combinedAlignmentScore" + ) { // Handle alignment score, return a percentage with a label return isText ? ( `${data}%` From 0c0cd0912a1ec0cfa201c3657b74ac3e674a3bc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Mon, 14 Jul 2025 12:43:35 +0200 Subject: [PATCH 108/112] Refactor .gitignore to update AI rules section --- .gitignore | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 50c2efb4b8d6..5ee28a7a617b 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,5 @@ yarn-error.log* debug.log app.log -# Cursor IDE -.cursor/rules - +# AI rules +.*/rules From da72513e0ad67fc7fe1c0416c1919d169eed0354 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 14 Jul 2025 12:49:32 +0200 Subject: [PATCH 109/112] adds new filters and status for licenses --- src/pages/tenant/standards/compare/index.js | 75 ++++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/src/pages/tenant/standards/compare/index.js b/src/pages/tenant/standards/compare/index.js index f3f66c2c390f..75e7892d81cf 100644 --- a/src/pages/tenant/standards/compare/index.js +++ b/src/pages/tenant/standards/compare/index.js @@ -315,10 +315,17 @@ const Page = () => { const categoryMatchesSearch = !searchQuery || category.toLowerCase().includes(searchLower); const filteredStandards = groupedStandards[category].filter((standard) => { + const tenantValue = standard.currentTenantValue?.Value || standard.currentTenantValue; + const hasLicenseMissing = typeof tenantValue === "string" && tenantValue.startsWith("License Missing:"); + const matchesFilter = filter === "all" || (filter === "compliant" && standard.complianceStatus === "Compliant") || - (filter === "nonCompliant" && standard.complianceStatus === "Non-Compliant"); + (filter === "nonCompliant" && standard.complianceStatus === "Non-Compliant") || + (filter === "nonCompliantWithLicense" && + standard.complianceStatus === "Non-Compliant" && !hasLicenseMissing) || + (filter === "nonCompliantWithoutLicense" && + standard.complianceStatus === "Non-Compliant" && hasLicenseMissing); const matchesSearch = !searchQuery || @@ -345,10 +352,38 @@ const Page = () => { const reportingDisabledCount = comparisonData?.filter((standard) => standard.complianceStatus === "Reporting Disabled") .length || 0; + + // Calculate license-related metrics + const missingLicenseCount = comparisonData?.filter((standard) => { + const tenantValue = standard.currentTenantValue?.Value || standard.currentTenantValue; + return typeof tenantValue === "string" && tenantValue.startsWith("License Missing:"); + }).length || 0; + + const nonCompliantWithLicenseCount = comparisonData?.filter((standard) => { + const tenantValue = standard.currentTenantValue?.Value || standard.currentTenantValue; + return standard.complianceStatus === "Non-Compliant" && + !(typeof tenantValue === "string" && tenantValue.startsWith("License Missing:")); + }).length || 0; + + const nonCompliantWithoutLicenseCount = comparisonData?.filter((standard) => { + const tenantValue = standard.currentTenantValue?.Value || standard.currentTenantValue; + return standard.complianceStatus === "Non-Compliant" && + (typeof tenantValue === "string" && tenantValue.startsWith("License Missing:")); + }).length || 0; + const compliancePercentage = allCount > 0 ? Math.round((compliantCount / (allCount - reportingDisabledCount || 1)) * 100) : 0; + + const missingLicensePercentage = + allCount > 0 + ? Math.round((missingLicenseCount / (allCount - reportingDisabledCount || 1)) * 100) + : 0; + + // Combined score: compliance percentage + missing license percentage + // This represents the total "addressable" compliance (compliant + could be compliant if licensed) + const combinedScore = compliancePercentage + missingLicensePercentage; return ( @@ -384,7 +419,7 @@ const Page = () => { {comparisonApi.data?.find( (comparison) => comparison.tenantFilter === currentTenant ) && ( - + @@ -403,6 +438,30 @@ const Page = () => { } sx={{ ml: 2 }} /> + + = 80 + ? "success" + : combinedScore >= 60 + ? "warning" + : "error" + } + /> )} @@ -575,6 +634,18 @@ const Page = () => { > Non-Compliant ({nonCompliantCount}) + + {comparisonApi.isError && ( From fced2b92adaca772491c425f15894a5d631b9714 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 14 Jul 2025 15:20:58 +0200 Subject: [PATCH 110/112] removes date --- src/pages/tenant/standards/tenant-alignment/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/tenant/standards/tenant-alignment/index.js b/src/pages/tenant/standards/tenant-alignment/index.js index 974946822cc5..34467ff6ea4b 100644 --- a/src/pages/tenant/standards/tenant-alignment/index.js +++ b/src/pages/tenant/standards/tenant-alignment/index.js @@ -27,7 +27,6 @@ const Page = () => { "alignmentScore", "LicenseMissingPercentage", "combinedAlignmentScore", - "latestDataCollection", ]} queryKey="listTenantAlignment" /> From 19159611f23cfd866d43fa0fe12a728ce47c4128 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 14 Jul 2025 16:03:09 +0200 Subject: [PATCH 111/112] flipped colour levels for clarify --- src/components/linearProgressWithLabel.jsx | 33 ++++++++++++++++------ src/utils/get-cipp-formatting.js | 14 +++++---- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/components/linearProgressWithLabel.jsx b/src/components/linearProgressWithLabel.jsx index b7ecb2d0c38a..55b4db2967bd 100644 --- a/src/components/linearProgressWithLabel.jsx +++ b/src/components/linearProgressWithLabel.jsx @@ -9,14 +9,31 @@ export const LinearProgressWithLabel = (props) => { return undefined; // Use default MUI color } - if (value >= 0 && value < 25) { - return colourLevels.level0to25 || "#f44336"; // Default red - } else if (value >= 25 && value < 50) { - return colourLevels.level25to50 || "#ff9800"; // Default orange - } else if (value >= 50 && value < 75) { - return colourLevels.level50to75 || "#ffeb3b"; // Default yellow - } else if (value >= 75 && value <= 100) { - return colourLevels.level75to100 || "#4caf50"; // Default green + // Check if flipped mode is enabled + const isFlipped = colourLevels === 'flipped' || colourLevels.flipped === true; + + if (isFlipped) { + // Flipped color order: green -> yellow -> orange -> red + if (value >= 0 && value < 25) { + return "#4caf50"; // Green for low values when flipped + } else if (value >= 25 && value < 50) { + return "#ffeb3b"; // Yellow + } else if (value >= 50 && value < 75) { + return "#ff9800"; // Orange + } else if (value >= 75 && value <= 100) { + return "#f44336"; // Red for high values when flipped + } + } else { + // Normal color order: red -> orange -> yellow -> green + if (value >= 0 && value < 25) { + return colourLevels.level0to25 || "#f44336"; // Default red + } else if (value >= 25 && value < 50) { + return colourLevels.level25to50 || "#ff9800"; // Default orange + } else if (value >= 50 && value < 75) { + return colourLevels.level50to75 || "#ffeb3b"; // Default yellow + } else if (value >= 75 && value <= 100) { + return colourLevels.level75to100 || "#4caf50"; // Default green + } } return undefined; // Fallback to default diff --git a/src/utils/get-cipp-formatting.js b/src/utils/get-cipp-formatting.js index 505f071fba36..832201a4898d 100644 --- a/src/utils/get-cipp-formatting.js +++ b/src/utils/get-cipp-formatting.js @@ -196,11 +196,7 @@ export const getCippFormatting = (data, cellName, type, canReceive, flatten = tr return isText ? data : data; } - if ( - cellName === "alignmentScore" || - cellName === "LicenseMissingPercentage" || - cellName === "combinedAlignmentScore" - ) { + if (cellName === "alignmentScore" || cellName === "combinedAlignmentScore") { // Handle alignment score, return a percentage with a label return isText ? ( `${data}%` @@ -208,6 +204,14 @@ export const getCippFormatting = (data, cellName, type, canReceive, flatten = tr ); } + + if (cellName === "LicenseMissingPercentage") { + return isText ? ( + `${data}%` + ) : ( + + ); + } if (cellName === "RepeatsEvery") { //convert 1d to "Every 1 day", 1w to "Every 1 week" etc. const match = data.match(/(\d+)([a-zA-Z]+)/); From 471c082769a6ab1b800efbc5bab4865a1f392b13 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 14 Jul 2025 17:07:47 +0200 Subject: [PATCH 112/112] up version --- public/version.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/version.json b/public/version.json index 7dce10929b8b..40f8aee65efa 100644 --- a/public/version.json +++ b/public/version.json @@ -1,3 +1,3 @@ { - "version": "8.1.1" -} \ No newline at end of file + "version": "8.2.0" +}