Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/pages/email/reports/activesync-devices/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ const Page = () => {
'firstSyncTime',
'lastSyncAttemptTime',
'lastSuccessSync',
'syncInfoNote',
'deviceID',
],
actions: actions,
Expand Down
4 changes: 4 additions & 0 deletions src/pages/identity/administration/jit-admin-templates/add.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,10 @@ const Page = () => {
validators={{ required: "Expiration action is required" }}
/>
</Grid>

<Grid size={{ md: 6, xs: 12 }}>
<CippFormComponent
type="autoComplete"
fullWidth
label="Default Notification Actions"
name="defaultNotificationActions"
Expand Down
147 changes: 136 additions & 11 deletions src/pages/tenant/manage/drift.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,49 @@ const ManageDriftPage = () => {
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)
Expand Down Expand Up @@ -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
Expand All @@ -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: (
<Stack spacing={2} sx={{ p: 2 }}>
{isPersistentDenyEnabled && (
<Box>
<Chip
size="small"
color="warning"
variant="outlined"
label="Permanently denied - resets every 12 hours"
/>
</Box>
)}

{description && description !== 'No description available' && (
<Typography variant="body2" color="text.secondary">
{description}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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: (
<Button
variant="outlined"
endIcon={<ExpandMore />}
onClick={(e) => {
e.stopPropagation()
handleMenuClick(e, `aligned-${item.id}`)
}}
size="small"
>
Actions
</Button>
),
}))

// 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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
>
<CippHead title="Manage Drift" />
<Box sx={{ py: 2 }}>
Expand Down Expand Up @@ -1918,12 +2011,7 @@ const ManageDriftPage = () => {
<Typography variant="h6" sx={{ mb: 2 }}>
Compliant Standards
</Typography>
<CippBannerListCard
items={filteredAlignedItems}
isCollapsible={true}
layout={'single'}
isFetching={driftApi.isFetching}
/>
{renderItemsByCategory(filteredAlignedItems)}
</Box>
)}

Expand Down Expand Up @@ -1989,7 +2077,7 @@ const ManageDriftPage = () => {
},
}}
row={actionData.data}
relatedQueryKeys={[`TenantDrift-${tenantFilter}`]}
relatedQueryKeys={[`TenantDrift-${tenantFilter}`, `PersistentDriftTasks-${tenantFilter}`]}
/>
)}

Expand Down Expand Up @@ -2148,6 +2236,15 @@ const ManageDriftPage = () => {
open={Boolean(anchorEl[`denied-${item.id}`])}
onClose={() => handleMenuClose(`denied-${item.id}`)}
>
<MenuItem
onClick={() => {
handleDeviationAction('deny', item)
handleMenuClose(`denied-${item.id}`)
}}
>
<Error sx={{ mr: 1, color: 'error.main' }} />
Rerun standard to align with template
</MenuItem>
<MenuItem
onClick={() => {
handleDeviationAction('deny-remediate', item)
Expand Down Expand Up @@ -2178,6 +2275,34 @@ const ManageDriftPage = () => {
</Menu>
))}

{alignedItemsWithActions.map((item) => (
<Menu
key={`menu-aligned-${item.id}`}
anchorEl={anchorEl[`aligned-${item.id}`]}
open={Boolean(anchorEl[`aligned-${item.id}`])}
onClose={() => handleMenuClose(`aligned-${item.id}`)}
>
<MenuItem
onClick={() => {
handleDeviationAction('deny', item)
handleMenuClose(`aligned-${item.id}`)
}}
>
<Error sx={{ mr: 1, color: 'error.main' }} />
Rerun standard to align with template
</MenuItem>
<MenuItem
onClick={() => {
handleDeviationAction('deny-remediate', item)
handleMenuClose(`aligned-${item.id}`)
}}
>
<Cancel sx={{ mr: 1, color: 'error.main' }} />
Deny - Remediate to align with template
</MenuItem>
</Menu>
))}

{/* Hidden ExecutiveReportButton that gets triggered programmatically */}
<Box sx={{ position: 'absolute', top: -9999, left: -9999 }}>
<ExecutiveReportButton
Expand Down