diff --git a/src/pages/email/reports/activesync-devices/index.js b/src/pages/email/reports/activesync-devices/index.js index 38b7ea65f87d..4ddd6306cd99 100644 --- a/src/pages/email/reports/activesync-devices/index.js +++ b/src/pages/email/reports/activesync-devices/index.js @@ -76,6 +76,7 @@ const Page = () => { 'firstSyncTime', 'lastSyncAttemptTime', 'lastSuccessSync', + 'syncInfoNote', 'deviceID', ], actions: actions, diff --git a/src/pages/identity/administration/jit-admin-templates/add.jsx b/src/pages/identity/administration/jit-admin-templates/add.jsx index f2549b66ec4d..986e7ea378d1 100644 --- a/src/pages/identity/administration/jit-admin-templates/add.jsx +++ b/src/pages/identity/administration/jit-admin-templates/add.jsx @@ -236,6 +236,10 @@ const Page = () => { validators={{ required: "Expiration action is required" }} /> + + + { enabled: !!templateId && !!tenantFilter, }) + // API call for persistent drift remediation tasks + const persistentDriftTasksApi = ApiGetCall({ + url: '/api/ListScheduledItems', + data: { + tenantFilter: tenantFilter, + SearchTitle: 'Persistent Drift Remediation:*', + }, + queryKey: `PersistentDriftTasks-${tenantFilter}`, + waiting: !!tenantFilter, + }) + + const persistentTaskNameSet = new Set( + (persistentDriftTasksApi.data || []) + .map((task) => (task?.Name ? String(task.Name).toLowerCase() : null)) + .filter(Boolean) + ) + + const getDriftTaskSettingName = (standardName) => { + if (!standardName) return '' + + const normalizedName = String(standardName) + const withoutPrefix = normalizedName.replace(/^standards\./, '') + + if (withoutPrefix.startsWith('IntuneTemplate.')) { + return 'IntuneTemplate' + } + + if (withoutPrefix.startsWith('ConditionalAccessTemplate.')) { + return 'ConditionalAccessTemplate' + } + + return withoutPrefix + } + + const hasPersistentDenyTask = (standardName) => { + const settingName = getDriftTaskSettingName(standardName) + if (!settingName || !tenantFilter) return false + + const expectedTaskName = + `Persistent Drift Remediation: ${settingName} - ${tenantFilter}`.toLowerCase() + return persistentTaskNameSet.has(expectedTaskName) + } + // Process drift data for chart - filter by current tenant and aggregate const rawDriftData = driftApi.data || [] const tenantDriftData = Array.isArray(rawDriftData) @@ -512,6 +555,7 @@ const ManageDriftPage = () => { : isLicenseSkipped ? 'Skipped - No License Available' : getDeviationStatusText(actualStatus) + const isPersistentDenyEnabled = hasPersistentDenyTask(deviation.standardName) // For skipped items, show different expected/received values let displayExpectedValue = deviation.ExpectedValue || deviation.expectedValue @@ -536,15 +580,29 @@ const ManageDriftPage = () => { text: prettyName, subtext: description, statusColor: isLicenseSkipped ? 'text.secondary' : getDeviationColor(actualStatus), - statusText: actualStatusText, + statusText: isPersistentDenyEnabled + ? `${actualStatusText} | Persistent deny (12h)` + : actualStatusText, standardName: deviation.standardName, // Store the original standardName for action handlers receivedValue: deviation.receivedValue, // Store the original receivedValue for action handlers expectedValue: deviation.expectedValue, // Store the original expectedValue for action handlers originalDeviation: deviation, // Store the complete original deviation object for reference isLicenseSkipped: isLicenseSkipped, // Flag for filtering and disabling actions isActuallyCompliant: isActuallyCompliant, // Flag to move to compliant section + isPersistentDenyEnabled: isPersistentDenyEnabled, children: ( + {isPersistentDenyEnabled && ( + + + + )} + {description && description !== 'No description available' && ( {description} @@ -1134,6 +1192,17 @@ const ManageDriftPage = () => { const handleDeviationAction = (action, deviation) => { if (!deviation) return + const resolvedReceivedValue = + deviation.receivedValue ?? + deviation.CurrentValue ?? + deviation.currentValue ?? + deviation.originalDeviation?.receivedValue ?? + deviation.originalDeviation?.CurrentValue ?? + deviation.originalDeviation?.currentValue ?? + deviation.expectedValue ?? + deviation.ExpectedValue ?? + null + let status let actionText switch (action) { @@ -1168,7 +1237,7 @@ const ManageDriftPage = () => { { standardName: deviation.standardName, // Use the standardName from the original deviation data status: status, - receivedValue: deviation.receivedValue, + receivedValue: resolvedReceivedValue, }, ], tenantFilter: tenantFilter, @@ -1407,6 +1476,25 @@ const ManageDriftPage = () => { ), })) + // Add action buttons to compliant/aligned items so previously denied and now compliant entries + // can be denied again or denied with remediation persistence. + const alignedItemsWithActions = allAlignedItems.map((item) => ({ + ...item, + cardLabelBoxActions: ( + + ), + })) + // Calculate compliance metrics for badges // Accepted and Customer Specific deviations count as compliant since they are user-approved // Denied deviations are included in total but not in compliant count (they haven't been fixed yet) @@ -1485,7 +1573,7 @@ const ManageDriftPage = () => { const filteredAcceptedItems = applyFilters(acceptedDeviationItemsWithActions) const filteredCustomerSpecificItems = applyFilters(customerSpecificDeviationItemsWithActions) const filteredDeniedItems = applyFilters(deniedDeviationItemsWithActions) - const filteredAlignedItems = applyFilters(allAlignedItems) + const filteredAlignedItems = applyFilters(alignedItemsWithActions) const filteredLicenseSkippedItems = applyFilters(licenseSkippedItems) // Helper function to render items grouped by category when category sort is active @@ -1569,7 +1657,12 @@ const ManageDriftPage = () => { subtitle={subtitle} actions={actions} actionsData={{}} - isFetching={driftApi.isFetching || standardsApi.isFetching || comparisonApi.isFetching} + isFetching={ + driftApi.isFetching || + standardsApi.isFetching || + comparisonApi.isFetching || + persistentDriftTasksApi.isFetching + } > @@ -1918,12 +2011,7 @@ const ManageDriftPage = () => { Compliant Standards - + {renderItemsByCategory(filteredAlignedItems)} )} @@ -1989,7 +2077,7 @@ const ManageDriftPage = () => { }, }} row={actionData.data} - relatedQueryKeys={[`TenantDrift-${tenantFilter}`]} + relatedQueryKeys={[`TenantDrift-${tenantFilter}`, `PersistentDriftTasks-${tenantFilter}`]} /> )} @@ -2148,6 +2236,15 @@ const ManageDriftPage = () => { open={Boolean(anchorEl[`denied-${item.id}`])} onClose={() => handleMenuClose(`denied-${item.id}`)} > + { + handleDeviationAction('deny', item) + handleMenuClose(`denied-${item.id}`) + }} + > + + Rerun standard to align with template + { handleDeviationAction('deny-remediate', item) @@ -2178,6 +2275,34 @@ const ManageDriftPage = () => { ))} + {alignedItemsWithActions.map((item) => ( + handleMenuClose(`aligned-${item.id}`)} + > + { + handleDeviationAction('deny', item) + handleMenuClose(`aligned-${item.id}`) + }} + > + + Rerun standard to align with template + + { + handleDeviationAction('deny-remediate', item) + handleMenuClose(`aligned-${item.id}`) + }} + > + + Deny - Remediate to align with template + + + ))} + {/* Hidden ExecutiveReportButton that gets triggered programmatically */}