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/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 diff --git a/src/components/CippComponents/CippMailboxPermissionsDialog.jsx b/src/components/CippComponents/CippMailboxPermissionsDialog.jsx index 52b2f3cb7372..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 && ( + - )} - + + )} { 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. - - - )} + {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. - - - )} + {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.currentTenantValue?.LastRefresh && ( + + + + } + size="small" + label={`${new Date( + standard.currentTenantValue.LastRefresh + ).toLocaleString()}`} + variant="outlined" + /> + )} + - - - - - {!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?.Value === "object" && + standard.currentTenantValue?.Value !== 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 ? ( - + <> + {standard.complianceStatus === "Compliant" ? ( + This setting is configured correctly - ) : standard.standardValue === false ? ( - + ) : standard.currentTenantValue?.Value === 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 - - )} - - )} - - - - )} - - - - - - - + ) : null} - - - - - - - - - - {currentTenant} - - + {/* 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)} + + + ); + })} + + )} - - - - - - {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" ? ( + + This setting is configured correctly + + ) : standard.currentTenantValue?.Value === false || + standard.currentTenantValue === false ? ( + + This setting is not configured correctly + + ) : standard.currentTenantValue !== undefined ? ( + String( + standard.currentTenantValue?.Value !== undefined + ? standard.currentTenantValue?.Value + : 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} + + + + )} - )} - + ))} + ))} - - ))} + + )} { // 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 }; + }} /> @@ -358,6 +367,7 @@ const Page = () => { handleRemoveStandard={handleRemoveStandard} handleAddMultipleStandard={handleAddMultipleStandard} // Pass the handler for adding multiple formControl={formControl} + editMode={editMode} /> )}