From c7f3b863fb86fd451b10d93b3f69487e223d3141 Mon Sep 17 00:00:00 2001 From: "McGhee, Garrett" Date: Thu, 5 Jun 2025 15:04:40 -0400 Subject: [PATCH 01/12] Added Missing GDAP Roles --- src/data/GDAPRoles.json | 96 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/src/data/GDAPRoles.json b/src/data/GDAPRoles.json index 22553236b533..26d4fc6e5eea 100644 --- a/src/data/GDAPRoles.json +++ b/src/data/GDAPRoles.json @@ -63,6 +63,22 @@ "Name": "Attribute Definition Reader", "ObjectId": "1d336d2c-4ae8-42ef-9711-b3604ce3fc2c" }, + { + "ExtensionData": {}, + "Description": "Read audit logs and configure diagnostic settings for events related to custom security attributes.", + "IsEnabled": true, + "IsSystem": true, + "Name": "Attribute Log Administrator", + "ObjectId": "5b784334-f94b-471a-a387-e7219fc49ca2" + }, + { + "ExtensionData": {}, + "Description": "Read audit logs related to custom security attributes.", + "IsEnabled": true, + "IsSystem": true, + "Name": "Attribute Log Reader", + "ObjectId": "9c99539d-8186-4804-835f-fd51ef9e2dcd" + }, { "ExtensionData": {}, "Description": "Allowed to view, set and reset authentication method information for any non-admin user.", @@ -79,6 +95,14 @@ "Name": "Authentication Policy Administrator", "ObjectId": "0526716b-113d-4c15-b2c8-68e3c22b9f80" }, + { + "ExtensionData": {}, + "Description": "Customize sign in and sign up experiences for users by creating and managing custom authentication extensions.", + "IsEnabled": true, + "IsSystem": true, + "Name": "Authentication Extensibility Administrator", + "ObjectId": "25a516ed-2fa0-40ea-a2d0-12923a21473a" + }, { "ExtensionData": {}, "Description": "Users assigned to this role are added to the local administrators group on Azure AD-joined devices.", @@ -311,6 +335,14 @@ "Name": "Global Reader", "ObjectId": "f2ef992c-3afb-46b9-b7cf-a126ee74c451" }, + { + "ExtensionData": {}, + "Description": "Create and manage all aspects of Microsoft Entra Internet Access and Microsoft Entra Private Access, including managing access to public and private endpoints.", + "IsEnabled": true, + "IsSystem": true, + "Name": "Global Secure Access Administrator", + "ObjectId": "ac434307-12b9-4fa1-a708-88bf58caabc1" + }, { "ExtensionData": {}, "Description": "Members of this role can create/manage groups, create/manage groups settings like naming and expiration policies, and view groups activity and audit reports.", @@ -439,6 +471,30 @@ "Name": "Message Center Reader", "ObjectId": "790c1fb9-7f7d-4f88-86a1-ef1f95c05c1b" }, + { + "ExtensionData": {}, + "Description": "Perform all migration functionality to migrate content to Microsoft 365 using Migration Manager.", + "IsEnabled": true, + "IsSystem": true, + "Name": "Microsoft 365 Migration Administrator", + "ObjectId": "8c8b803f-96e1-4129-9349-20738d9f9652" + }, + { + "ExtensionData": {}, + "Description": "Create and manage all aspects warranty claims and entitlements for Microsoft manufactured hardware, like Surface and HoloLens.", + "IsEnabled": true, + "IsSystem": true, + "Name": "Microsoft Hardware Warranty Administrator", + "ObjectId": "1501b917-7653-4ff9-a4b5-203eaf33784f" + }, + { + "ExtensionData": {}, + "Description": "Create and read warranty claims for Microsoft manufactured hardware, like Surface and HoloLens.", + "IsEnabled": true, + "IsSystem": true, + "Name": "Microsoft Hardware Warranty Specialist", + "ObjectId": "281fe777-fb20-4fbb-b7a3-ccebce5b0d96" + }, { "ExtensionData": {}, "Description": "Can manage network locations and review enterprise network design insights for Microsoft 365 Software as a Service applications.", @@ -455,6 +511,14 @@ "Name": "Office Apps Administrator", "ObjectId": "2b745bdf-0803-4d80-aa65-822c4493daac" }, + { + "ExtensionData": {}, + "Description": "Write, publish, manage, and review the organizational messages for end-users through Microsoft product surfaces.", + "IsEnabled": true, + "IsSystem": true, + "Name": "Organizational Messages Writer", + "ObjectId": "507f53e4-4e52-4077-abd3-d2e1558b6ea2" + }, { "ExtensionData": {}, "Description": "Can reset passwords for non-administrators and Password Administrators.", @@ -583,6 +647,14 @@ "Name": "SharePoint Administrator", "ObjectId": "f28a1f50-f6e7-4571-818b-6a12f2af6b6c" }, + { + "ExtensionData": {}, + "Description": "Manage all aspects of SharePoint Embedded containers.", + "IsEnabled": true, + "IsSystem": true, + "Name": "SharePoint Embedded Administrator", + "ObjectId": "1a7d78b6-429f-476b-8eb-35fb715fffd4" + }, { "ExtensionData": {}, "Description": "Can manage all aspects of the Skype for Business product.", @@ -631,6 +703,22 @@ "Name": "Teams Devices Administrator", "ObjectId": "3d762c5a-1b6c-493f-843e-55a3b42923d4" }, + { + "ExtensionData": {}, + "Description": "Manage voice and telephony features and troubleshoot communication issues within the Microsoft Teams service.", + "IsEnabled": true, + "IsSystem": true, + "Name": "Teams Telephony Administrator", + "ObjectId": "aa38014f-0993-46e9-9b45-30501a20909d" + }, + { + "ExtensionData": {}, + "Description": "Create new Microsoft Entra or Azure AD B2C tenants.", + "IsEnabled": true, + "IsSystem": true, + "Name": "Tenant Creator", + "ObjectId": "112ca1a2-15ad-4102-995e-45b0bc479a6a" + }, { "ExtensionData": {}, "Description": "Can see only tenant level aggregates in Microsoft 365 Usage Analytics and Productivity Score.", @@ -647,6 +735,14 @@ "Name": "User Administrator", "ObjectId": "fe930be7-5e62-47db-91af-98c3a49a38b1" }, + { + "ExtensionData": {}, + "Description": "View product feedback, survey results, and reports to find training and communication opportunities.", + "IsEnabled": true, + "IsSystem": true, + "Name": "User Experience Success Manager", + "ObjectId": "27460883-1df1-4691-b032-3b79643e5e63" + }, { "ExtensionData": {}, "Description": "Manage and share Virtual Visits information and metrics from admin centers or the Virtual Visits app.", From 3c6f973f34b35e288e8ccf59073e6e56ca41d29b Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 5 Jun 2025 17:40:28 -0400 Subject: [PATCH 02/12] fix unsaved detection --- package.json | 4 ++-- .../CippStandards/CippStandardsSideBar.jsx | 14 ++++++++++++++ src/pages/tenant/standards/template.jsx | 13 +++++++++++-- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 51a2591ae67b..624037bd4884 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cipp", - "version": "7.1.3", + "version": "8.0.2", "author": "CIPP Contributors", "homepage": "https://cipp.app/", "bugs": { @@ -112,4 +112,4 @@ "eslint": "9.22.0", "eslint-config-next": "15.2.2" } -} +} \ No newline at end of file diff --git a/src/components/CippStandards/CippStandardsSideBar.jsx b/src/components/CippStandards/CippStandardsSideBar.jsx index f14945745a04..601ebe1e7aa3 100644 --- a/src/components/CippStandards/CippStandardsSideBar.jsx +++ b/src/components/CippStandards/CippStandardsSideBar.jsx @@ -64,11 +64,24 @@ const CippStandardsSideBar = ({ formControl, createDialog, edit, + onSaveSuccess, }) => { const [currentStep, setCurrentStep] = useState(0); const [savedItem, setSavedItem] = useState(null); const dialogAfterEffect = (id) => { setSavedItem(id); + + // Reset form's dirty state to prevent unsaved changes warning + if (formControl && formControl.reset) { + // Get current values and reset the form with them to clear dirty state + const currentValues = formControl.getValues(); + formControl.reset(currentValues); + } + + // Call the onSaveSuccess callback if provided + if (typeof onSaveSuccess === "function") { + onSaveSuccess(); + } }; const watchForm = useWatch({ control: formControl.control }); @@ -276,6 +289,7 @@ CippStandardsSideBar.propTypes = { ).isRequired, updatedAt: PropTypes.string, formControl: PropTypes.object.isRequired, + onSaveSuccess: PropTypes.func, }; export default CippStandardsSideBar; diff --git a/src/pages/tenant/standards/template.jsx b/src/pages/tenant/standards/template.jsx index 66bb78c9e842..14c01db09c18 100644 --- a/src/pages/tenant/standards/template.jsx +++ b/src/pages/tenant/standards/template.jsx @@ -101,6 +101,10 @@ const Page = () => { // Track form changes useEffect(() => { + // Compare the current form values with the initial values to check for real changes + const currentValues = formControl.getValues(); + const initialValues = initialStandardsRef.current; + if ( formState.isDirty || JSON.stringify(selectedStandards) !== JSON.stringify(initialStandardsRef.current) @@ -109,7 +113,7 @@ const Page = () => { } else { setHasUnsavedChanges(false); } - }, [formState.isDirty, selectedStandards]); + }, [formState.isDirty, selectedStandards, formControl]); useEffect(() => { if (router.query.id) { @@ -341,7 +345,12 @@ const Page = () => { selectedStandards={selectedStandards} edit={editMode} updatedAt={updatedAt} - onSaveSuccess={() => setHasUnsavedChanges(false)} + onSaveSuccess={() => { + // Reset unsaved changes flag + setHasUnsavedChanges(false); + // Update reference for future change detection + initialStandardsRef.current = { ...selectedStandards }; + }} /> From 2e57b2701b167498bc2dd052f65d33c16bda6eed Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 5 Jun 2025 17:48:01 -0400 Subject: [PATCH 03/12] pass editmode to accordion --- .../CippStandards/CippStandardAccordion.jsx | 84 ++++++++++--------- src/pages/tenant/standards/template.jsx | 3 +- 2 files changed, 47 insertions(+), 40 deletions(-) diff --git a/src/components/CippStandards/CippStandardAccordion.jsx b/src/components/CippStandards/CippStandardAccordion.jsx index c07bc67c27d2..06f8cd2acb12 100644 --- a/src/components/CippStandards/CippStandardAccordion.jsx +++ b/src/components/CippStandards/CippStandardAccordion.jsx @@ -93,6 +93,7 @@ const CippStandardAccordion = ({ handleRemoveStandard, handleAddMultipleStandard, formControl, + editMode = false, }) => { const [configuredState, setConfiguredState] = useState({}); const [filter, setFilter] = useState("all"); @@ -188,46 +189,48 @@ const CippStandardAccordion = ({ // Initialize when watchedValues are available useEffect(() => { - // Only run initialization if we have watchedValues and they contain data - if (!watchedValues || Object.keys(watchedValues).length === 0) { - return; - } - - // Prevent re-initialization if we already have configuration state - const hasConfigState = Object.keys(configuredState).length > 0; - if (hasConfigState) { - return; - } - - console.log("Initializing configuration state from template values"); - const initial = {}; - const initialConfigured = {}; - - // For each standard, get its current values and determine if it's configured - Object.keys(selectedStandards).forEach((standardName) => { - const currentValues = _.get(watchedValues, standardName); - if (!currentValues) return; - - initial[standardName] = _.cloneDeep(currentValues); + if (editMode) { + // Only run initialization if we have watchedValues and they contain data + if (!watchedValues || Object.keys(watchedValues).length === 0) { + return; + } - const baseStandardName = standardName.split("[")[0]; - const standard = providedStandards.find((s) => s.name === baseStandardName); - if (standard) { - initialConfigured[standardName] = isStandardConfigured( - standardName, - standard, - currentValues - ); + // Prevent re-initialization if we already have configuration state + const hasConfigState = Object.keys(configuredState).length > 0; + if (hasConfigState) { + return; } - }); - // Store both the initial values and set them as current saved values - setOriginalValues(initial); - setSavedValues(initial); - setConfiguredState(initialConfigured); - // Only depend on watchedValues and selectedStandards to avoid infinite loops - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [watchedValues, selectedStandards]); + console.log("Initializing configuration state from template values"); + const initial = {}; + const initialConfigured = {}; + + // For each standard, get its current values and determine if it's configured + Object.keys(selectedStandards).forEach((standardName) => { + const currentValues = _.get(watchedValues, standardName); + if (!currentValues) return; + + initial[standardName] = _.cloneDeep(currentValues); + + const baseStandardName = standardName.split("[")[0]; + const standard = providedStandards.find((s) => s.name === baseStandardName); + if (standard) { + initialConfigured[standardName] = isStandardConfigured( + standardName, + standard, + currentValues + ); + } + }); + + // Store both the initial values and set them as current saved values + setOriginalValues(initial); + setSavedValues(initial); + setConfiguredState(initialConfigured); + // Only depend on watchedValues and selectedStandards to avoid infinite loops + // eslint-disable-next-line react-hooks/exhaustive-deps + } + }, [watchedValues, selectedStandards, editMode]); // Save changes for a standard const handleSave = (standardName, standard, current) => { @@ -519,6 +522,9 @@ const CippStandardAccordion = ({ // Get current values and check if they differ from saved values const current = _.get(watchedValues, standardName); const saved = _.get(savedValues, standardName) || {}; + console.log(`Current values for ${standardName}:`, current); + console.log(`Saved values for ${standardName}:`, saved); + const hasUnsaved = !_.isEqual(current, saved); // Check if all required fields are filled @@ -609,7 +615,7 @@ const CippStandardAccordion = ({ const canSave = hasAction && requiredFieldsFilled && hasUnsaved; console.log( - `Standard: ${standardName}, Action Required: ${actionRequired}, Has Action: ${hasAction}, Required Fields Filled: ${requiredFieldsFilled}, Can Save: ${canSave}` + `Standard: ${standardName}, Action Required: ${actionRequired}, Has Action: ${hasAction}, Required Fields Filled: ${requiredFieldsFilled}, Unsaved Changes: ${hasUnsaved}, Can Save: ${canSave}` ); return ( @@ -760,7 +766,7 @@ const CippStandardAccordion = ({ - + - - - - - - {comparisonApi.isError && ( - - - Error fetching comparison data - - - There was an error retrieving the comparison data. Please try running the report again - by clicking the "Run Report Once" button above. - - {comparisonApi.error && ( - - - {comparisonApi.error.message || JSON.stringify(comparisonApi.error, null, 2)} + + setSearchQuery(e.target.value)} + slotProps={{ + input: { + startAdornment: ( + + + + ), + endAdornment: searchQuery && ( + + + setSearchQuery("")} + aria-label="Clear search" + > + + + + + ), + }, + }} + /> + + + + + + + + + {comparisonApi.isError && ( + + + Error fetching comparison data + + + There was an error retrieving the comparison data. Please try running the report + again by clicking the "Run Report Once" button above. - + {comparisonApi.error && ( + + + {comparisonApi.error.message || JSON.stringify(comparisonApi.error, null, 2)} + + + )} + )} - - )} - {comparisonApi.isSuccess && (!comparisonApi.data || comparisonApi.data.length === 0) && ( - - - No comparison data is available. This might be because: - - - - • The tenant has not been scanned yet - - - • The template has no standards configured - - - • There was an issue with the comparison - - - - Try running the report by clicking the "Run Report Once" button above. - - - )} - - {filteredGroupedStandards && Object.keys(filteredGroupedStandards).length === 0 && ( - - - No standards match the selected filter criteria or search query. - - - Try selecting a different filter or modifying the search query. - - - )} + {comparisonApi.isSuccess && + (!comparisonApi.data || comparisonApi.data.length === 0) && ( + + + No comparison data is available. This might be because: + + + + • The tenant has not been scanned yet + + + • The template has no standards configured + + + • There was an issue with the comparison + + + + Try running the report by clicking the "Run Report Once" button above. + + + )} + + {filteredGroupedStandards && Object.keys(filteredGroupedStandards).length === 0 && ( + + + No standards match the selected filter criteria or search query. + + + Try selecting a different filter or modifying the search query. + + + )} - {Object.keys(filteredGroupedStandards).map((category) => ( - - - {category} - + {Object.keys(filteredGroupedStandards).map((category) => ( + + + {category} + - {filteredGroupedStandards[category].map((standard, index) => ( - - - - - - - ( + + + + + - {standard.complianceStatus === "Compliant" ? ( - - ) : standard.complianceStatus === "Reporting Disabled" ? ( - - ) : ( - - )} - - - {standard?.standardName} + + + {standard.complianceStatus === "Compliant" ? ( + + ) : standard.complianceStatus === "Reporting Disabled" ? ( + + ) : ( + + )} + + + {standard?.standardName} + + + + + + + + + + {!standard.standardValue ? ( + + This data has not yet been collected. Collect the data by pressing the + report button on the top of the page. + + ) : ( - + + {standard.standardValue && + typeof standard.standardValue === "object" && + Object.keys(standard.standardValue).length > 0 ? ( + Object.entries(standard.standardValue).map(([key, value]) => ( + + + {key}: + + + {typeof value === "object" && value !== null + ? value?.label || JSON.stringify(value) + : value === true + ? "Enabled" + : value === false + ? "Disabled" + : String(value)} + + + )) + ) : ( + + {standard.standardValue === true ? ( + + This setting is configured correctly + + ) : standard.standardValue === false ? ( + + This setting is not configured correctly + + ) : standard.standardValue !== undefined ? ( + typeof standard.standardValue === "object" ? ( + "No settings configured" + ) : ( + String(standard.standardValue) + ) + ) : ( + + This setting is not configured, or data has not been + collected. If you are getting this after data collection, + the tenant might not be licensed for this feature + + )} + + )} + + + + )} + + + + + + + + + + + + + + + + + + {currentTenant} + + + + + + + + + {standard.complianceStatus} + - - - - - {!standard.standardValue ? ( - - This data has not yet been collected. Collect the data by pressing the - report button on the top of the page. - - ) : ( - - + + + {/* Existing tenant comparison content */} + {typeof standard.currentTenantValue === "object" && + standard.currentTenantValue !== null ? ( { borderColor: "divider", }} > - {standard.standardValue && - typeof standard.standardValue === "object" && - Object.keys(standard.standardValue).length > 0 ? ( - Object.entries(standard.standardValue).map(([key, value]) => ( - - - {key}: - - - {typeof value === "object" && value !== null - ? value?.label || JSON.stringify(value) - : value === true - ? "Enabled" - : value === false - ? "Disabled" - : String(value)} - - - )) + {standard.complianceStatus === "Reporting Disabled" ? ( + + Reporting is disabled for this standard in the template + configuration. + ) : ( - - {standard.standardValue === true ? ( - - This setting is configured correctly - - ) : standard.standardValue === false ? ( - - This setting is not configured correctly - - ) : standard.standardValue !== undefined ? ( - typeof standard.standardValue === "object" ? ( - "No settings configured" - ) : ( - String(standard.standardValue) - ) - ) : ( - - This setting is not configured, or data has not been - collected. If you are getting this after data collection, the - tenant might not be licensed for this feature - - )} - + Object.entries(standard.currentTenantValue).map(([key, value]) => { + const standardValueForKey = + standard.standardValue && + typeof standard.standardValue === "object" + ? standard.standardValue[key] + : undefined; + + const isDifferent = + standardValueForKey !== undefined && + JSON.stringify(value) !== JSON.stringify(standardValueForKey); + + return ( + + + {key}: + + + {standard.complianceStatus === "Compliant" && value === true + ? "Compliant" + : typeof value === "object" && value !== null + ? value?.label || JSON.stringify(value) + : value === true + ? "Enabled" + : value === false + ? "Disabled" + : String(value)} + + + ); + }) )} - - - )} - - - - - - - - - - - - - - - - - - {currentTenant} - - - - - - - - - {standard.complianceStatus} - - - - - - - {/* Existing tenant comparison content */} - {typeof standard.currentTenantValue === "object" && - standard.currentTenantValue !== null ? ( - - {standard.complianceStatus === "Reporting Disabled" ? ( - - Reporting is disabled for this standard in the template configuration. - ) : ( - Object.entries(standard.currentTenantValue).map(([key, value]) => { - const standardValueForKey = - standard.standardValue && typeof standard.standardValue === "object" - ? standard.standardValue[key] - : undefined; - - const isDifferent = - standardValueForKey !== undefined && - JSON.stringify(value) !== JSON.stringify(standardValueForKey); - - return ( - - - {key}: - - - {standard.complianceStatus === "Compliant" && value === true - ? "Compliant" - : typeof value === "object" && value !== null - ? value?.label || JSON.stringify(value) - : value === true - ? "Enabled" - : value === false - ? "Disabled" - : String(value)} - - - ); - }) + + {standard.complianceStatus === "Reporting Disabled" ? ( + + Reporting is disabled for this standard in the template + configuration. + + ) : standard.complianceStatus === "Compliant" && + standard.currentTenantValue === true ? ( + + This setting is configured correctly + + ) : standard.currentTenantValue === false ? ( + + This setting is not configured correctly + + ) : standard.currentTenantValue !== undefined ? ( + String(standard.currentTenantValue) + ) : ( + + This setting is not configured, or data has not been collected. If + you are getting this after data collection, the tenant might not + be licensed for this feature + + )} + )} - ) : ( - - {standard.complianceStatus === "Reporting Disabled" ? ( - - Reporting is disabled for this standard in the template configuration. - - ) : standard.complianceStatus === "Compliant" && - standard.currentTenantValue === true ? ( - - This setting is configured correctly - - ) : standard.currentTenantValue === false ? ( - - This setting is not configured correctly - - ) : standard.currentTenantValue !== undefined ? ( - String(standard.currentTenantValue) - ) : ( - - This setting is not configured, or data has not been collected. If you - are getting this after data collection, the tenant might not be - licensed for this feature - - )} - - )} - - - + + - {standard.complianceDetails && ( - - - - - - - {standard.complianceDetails} - - + {standard.complianceDetails && ( + + + + + + + {standard.complianceDetails} + + + + )} - )} - + ))} + ))} - - ))} + + )} Date: Fri, 6 Jun 2025 14:12:00 -0400 Subject: [PATCH 10/12] update compare report to support new format --- src/pages/tenant/standards/compare/index.js | 245 ++++++++++++-------- 1 file changed, 148 insertions(+), 97 deletions(-) diff --git a/src/pages/tenant/standards/compare/index.js b/src/pages/tenant/standards/compare/index.js index 7cb15ab8d136..e66e706e511e 100644 --- a/src/pages/tenant/standards/compare/index.js +++ b/src/pages/tenant/standards/compare/index.js @@ -118,8 +118,9 @@ const Page = () => { (s) => s.standardId === standardId ); - // Get the direct standard value from the tenant object - const directStandardValue = currentTenantObj?.[standardId]; + // Get the standard object and its value from the tenant object + const standardObject = currentTenantObj?.[standardId]; + const directStandardValue = standardObject?.Value; // Determine compliance status let isCompliant = false; @@ -151,8 +152,11 @@ const Page = () => { templateItem.TemplateList?.label || templateId }`, currentTenantValue: - directStandardValue !== undefined - ? directStandardValue + standardObject !== undefined + ? { + Value: directStandardValue, + LastRefresh: standardObject?.LastRefresh, + } : currentTenantStandard?.value, standardValue: templateSettings, // Use the template settings object instead of true complianceStatus: isCompliant ? "Compliant" : "Non-Compliant", @@ -188,9 +192,12 @@ const Page = () => { let isCompliant = false; let reportingDisabled = !reportingEnabled; - // Check if the standard is directly in the tenant object (like "standards.AuditLog": true) + // Check if the standard is directly in the tenant object (like "standards.AuditLog": {...}) const standardIdWithoutPrefix = standardId.replace("standards.", ""); - const directStandardValue = currentTenantObj?.[standardId]; + const standardObject = currentTenantObj?.[standardId]; + + // Extract the actual value from the standard object (new data structure includes .Value property) + const directStandardValue = standardObject?.Value; // Special case for boolean standards that are true in the tenant if (directStandardValue === true) { @@ -223,8 +230,11 @@ const Page = () => { standardId, standardName: standardInfo?.label || standardKey, currentTenantValue: - directStandardValue !== undefined - ? directStandardValue + standardObject !== undefined + ? { + Value: directStandardValue, + LastRefresh: standardObject?.LastRefresh, + } : currentTenantStandard?.value, standardValue: standardSettings, complianceStatus, @@ -365,7 +375,9 @@ const Page = () => { - {comparisonApi.data?.find((comparison) => comparison.RowKey === currentTenant) && ( + {comparisonApi.data?.find( + (comparison) => comparison.tenantFilter === currentTenant + ) && ( { } sx={{ ml: 2 }} /> - - - - } - size="small" - label={`Updated on ${new Date( - comparisonApi.data.find( - (comparison) => comparison.RowKey === currentTenant - ).LastRefresh - ).toLocaleString()}`} - /> )} @@ -826,37 +825,55 @@ const Page = () => { - + - - {standard.complianceStatus} - - + > + + + {standard.complianceStatus} + + + {standard.currentTenantValue?.LastRefresh && ( + + + + } + size="small" + label={`${new Date( + standard.currentTenantValue.LastRefresh + ).toLocaleString()}`} + variant="outlined" + /> + )} + {/* Existing tenant comparison content */} - {typeof standard.currentTenantValue === "object" && - standard.currentTenantValue !== null ? ( + {typeof standard.currentTenantValue?.Value === "object" && + standard.currentTenantValue?.Value !== null ? ( { configuration. ) : ( - Object.entries(standard.currentTenantValue).map(([key, value]) => { - const standardValueForKey = - standard.standardValue && - typeof standard.standardValue === "object" - ? standard.standardValue[key] - : undefined; - - const isDifferent = - standardValueForKey !== undefined && - JSON.stringify(value) !== JSON.stringify(standardValueForKey); - - return ( - - - {key}: - - - {standard.complianceStatus === "Compliant" && value === true - ? "Compliant" - : typeof value === "object" && value !== null - ? value?.label || JSON.stringify(value) - : value === true - ? "Enabled" - : value === false - ? "Disabled" - : String(value)} - - - ); - }) + <> + {standard.complianceStatus === "Compliant" ? ( + + This setting is configured correctly + + ) : standard.currentTenantValue?.Value === false ? ( + + This setting is not configured correctly + + ) : null} + + {/* Only show values if they're not simple true/false that's already covered by the alerts above */} + {!( + standard.complianceStatus === "Compliant" && + (standard.currentTenantValue?.Value === true || + standard.currentTenantValue?.Value === false) + ) && + Object.entries(standard.currentTenantValue) + .filter( + ([key]) => + key !== "LastRefresh" && + // Skip showing the Value field separately if it's just true/false + !( + key === "Value" && + (standard.currentTenantValue?.Value === true || + standard.currentTenantValue?.Value === false) + ) + ) + .map(([key, value]) => { + const actualValue = key === "Value" ? value : value; + + const standardValueForKey = + standard.standardValue && + typeof standard.standardValue === "object" + ? standard.standardValue[key] + : undefined; + + const isDifferent = + standardValueForKey !== undefined && + JSON.stringify(actualValue) !== + JSON.stringify(standardValueForKey); + + return ( + + + {key}: + {" "} + + {typeof value === "object" && value !== null + ? value?.label || JSON.stringify(value) + : value === true + ? "Enabled" + : value === false + ? "Disabled" + : String(value)} + + + ); + })} + )} ) : ( @@ -944,17 +991,21 @@ const Page = () => { Reporting is disabled for this standard in the template configuration. - ) : standard.complianceStatus === "Compliant" && - standard.currentTenantValue === true ? ( + ) : standard.complianceStatus === "Compliant" ? ( This setting is configured correctly - ) : standard.currentTenantValue === false ? ( + ) : standard.currentTenantValue?.Value === false || + standard.currentTenantValue === false ? ( This setting is not configured correctly ) : standard.currentTenantValue !== undefined ? ( - String(standard.currentTenantValue) + String( + standard.currentTenantValue?.Value !== undefined + ? standard.currentTenantValue?.Value + : standard.currentTenantValue + ) ) : ( This setting is not configured, or data has not been collected. If From 3a07023085d4e3e68a9f952e9ac47c444dbfbdc1 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 6 Jun 2025 15:25:28 -0400 Subject: [PATCH 11/12] fix automap switch layout --- .../CippComponents/CippMailboxPermissionsDialog.jsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/CippComponents/CippMailboxPermissionsDialog.jsx b/src/components/CippComponents/CippMailboxPermissionsDialog.jsx index b49b8529532f..b890544cc216 100644 --- a/src/components/CippComponents/CippMailboxPermissionsDialog.jsx +++ b/src/components/CippComponents/CippMailboxPermissionsDialog.jsx @@ -25,7 +25,7 @@ const CippMailboxPermissionsDialog = ({ formHook }) => { }); return ( - + { })) || [] } /> - {fullAccess && ( + + {fullAccess?.length > 0 && ( + - )} - + + )} Date: Fri, 6 Jun 2025 17:22:19 -0400 Subject: [PATCH 12/12] up version --- public/version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/version.json b/public/version.json index f47e65940e3b..6e2baa8faabc 100644 --- a/public/version.json +++ b/public/version.json @@ -1,3 +1,3 @@ { - "version": "8.0.2" + "version": "8.0.3" } \ No newline at end of file