From 2cf08134bbd7e1d816505eb52169b6624a70be65 Mon Sep 17 00:00:00 2001 From: Luis Mengel Date: Mon, 16 Mar 2026 00:52:15 +0100 Subject: [PATCH 01/26] Show warning when TAP is not enabled in tenant in jit-admin add --- .../identity/administration/jit-admin/add.jsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/pages/identity/administration/jit-admin/add.jsx b/src/pages/identity/administration/jit-admin/add.jsx index 9deef7e51b1b..7713d193c8cc 100644 --- a/src/pages/identity/administration/jit-admin/add.jsx +++ b/src/pages/identity/administration/jit-admin/add.jsx @@ -28,6 +28,20 @@ const Page = () => { }); const watcher = useWatch({ control: formControl.control }); + const useTAP = useWatch({ control: formControl.control, name: "UseTAP" }); + + const tapPolicy = ApiGetCall({ + url: selectedTenant + ? `/api/ListGraphRequest` + : undefined, + data: { + Endpoint: "policies/authenticationMethodsPolicy/authenticationMethodConfigurations/TemporaryAccessPass", + tenantFilter: selectedTenant?.value, + }, + queryKey: selectedTenant ? `TAPPolicy-${selectedTenant.value}` : "TAPPolicy", + waiting: !!selectedTenant, + }); + const tapEnabled = tapPolicy.isSuccess && tapPolicy.data?.Results?.[0]?.state === "enabled"; // Simple duration parser for basic ISO 8601 durations const parseDuration = (duration) => { @@ -386,6 +400,11 @@ const Page = () => { name="UseTAP" formControl={formControl} /> + {useTAP && tapPolicy.isSuccess && !tapEnabled && ( + + TAP is not enabled in this tenant. TAP generation will fail. + + )} Date: Mon, 16 Mar 2026 19:51:08 +0100 Subject: [PATCH 02/26] feat(ooo): add calendar options UI for block calendar, decline invitations, cancel meetings Expose 3 new OOO calendar checkboxes across all OOO entry points: - "Block my calendar for this period" (CreateOOFEvent + OOFEventSubject) - "Automatically decline new invitations" (AutoDeclineFutureRequestsWhenOOF) - "Decline and cancel my meetings" (DeclineEventsForScheduledOOF + DeclineMeetingMessage) Updated components: - CippUserActions OutOfOfficeForm: calendar options gated on Scheduled mode - CippExchangeSettingsForm: same options with ooo.* field prefix, extended oooFields array for form reset preservation - CippWizardVacationActions: calendar options always visible (vacation is always scheduled), pre-populates from existing user config via Get API - CippWizardVacationConfirmation: maps wizard field names to API payload, adds summary chips for enabled calendar options - vacation-mode/add/index.js: new defaults in initialState --- .../CippComponents/CippUserActions.jsx | 59 +++++++++++++- .../CippExchangeSettingsForm.jsx | 72 +++++++++++++++++ .../CippWizard/CippWizardVacationActions.jsx | 80 +++++++++++++++++++ .../CippWizardVacationConfirmation.jsx | 51 +++++++++--- .../administration/vacation-mode/add/index.js | 5 ++ 5 files changed, 256 insertions(+), 11 deletions(-) diff --git a/src/components/CippComponents/CippUserActions.jsx b/src/components/CippComponents/CippUserActions.jsx index 0b365b506c77..94b664d1be84 100644 --- a/src/components/CippComponents/CippUserActions.jsx +++ b/src/components/CippComponents/CippUserActions.jsx @@ -23,8 +23,9 @@ import { import { getCippLicenseTranslation } from "../../utils/get-cipp-license-translation"; import { useSettings } from "../../hooks/use-settings.js"; import { usePermissions } from "../../hooks/use-permissions"; -import { Tooltip, Box } from "@mui/material"; +import { Tooltip, Box, Divider, Typography } from "@mui/material"; import CippFormComponent from "./CippFormComponent"; +import { CippFormCondition } from "./CippFormCondition"; import { useWatch } from "react-hook-form"; // Separate component for Manage Licenses form to avoid hook issues @@ -257,6 +258,62 @@ const OutOfOfficeForm = ({ formControl }) => { multiline rows={4} /> + + {!areDateFieldsDisabled && ( + <> + + Calendar Options + + + + + + + + + + + + + + )} ); }; diff --git a/src/components/CippFormPages/CippExchangeSettingsForm.jsx b/src/components/CippFormPages/CippExchangeSettingsForm.jsx index ee8bfc143074..ee44517b8c51 100644 --- a/src/components/CippFormPages/CippExchangeSettingsForm.jsx +++ b/src/components/CippFormPages/CippExchangeSettingsForm.jsx @@ -14,6 +14,7 @@ import { } from "@mui/material"; import { Check, Error, Sync } from "@mui/icons-material"; import CippFormComponent from "../CippComponents/CippFormComponent"; +import { CippFormCondition } from "../CippComponents/CippFormCondition"; import { ApiGetCall, ApiPostCall } from "../../api/ApiCall"; import { useSettings } from "../../hooks/use-settings"; import { Grid } from "@mui/system"; @@ -81,6 +82,11 @@ const CippExchangeSettingsForm = (props) => { "ExternalMessage", "StartTime", "EndTime", + "CreateOOFEvent", + "OOFEventSubject", + "AutoDeclineFutureRequestsWhenOOF", + "DeclineEventsForScheduledOOF", + "DeclineMeetingMessage", ]; // Reset the form @@ -266,6 +272,72 @@ const CippExchangeSettingsForm = (props) => { rows={4} /> + {!areDateFieldsDisabled && ( + <> + + + + Calendar Options + + + + + + + + + + + + + + + + + + + + + + + )} diff --git a/src/components/CippWizard/CippWizardVacationActions.jsx b/src/components/CippWizard/CippWizardVacationActions.jsx index c7376a1528da..1d8ac1611af7 100644 --- a/src/components/CippWizard/CippWizardVacationActions.jsx +++ b/src/components/CippWizard/CippWizardVacationActions.jsx @@ -42,6 +42,22 @@ export const CippWizardVacationActions = (props) => { if (!currentExternal) { formControl.setValue("oooExternalMessage", oooData.data.ExternalMessage || ""); } + // Pre-populate calendar options from existing config + if (oooData.data.CreateOOFEvent != null) { + formControl.setValue("oooCreateOOFEvent", !!oooData.data.CreateOOFEvent); + } + if (oooData.data.OOFEventSubject) { + formControl.setValue("oooOOFEventSubject", oooData.data.OOFEventSubject); + } + if (oooData.data.AutoDeclineFutureRequestsWhenOOF != null) { + formControl.setValue("oooAutoDeclineFutureRequests", !!oooData.data.AutoDeclineFutureRequestsWhenOOF); + } + if (oooData.data.DeclineEventsForScheduledOOF != null) { + formControl.setValue("oooDeclineEvents", !!oooData.data.DeclineEventsForScheduledOOF); + } + if (oooData.data.DeclineMeetingMessage) { + formControl.setValue("oooDeclineMeetingMessage", oooData.data.DeclineMeetingMessage); + } } }, [oooData.isSuccess, oooData.data]); @@ -359,6 +375,70 @@ export const CippWizardVacationActions = (props) => { /> )} + + {/* Calendar Options */} + + + + Calendar Options + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/CippWizard/CippWizardVacationConfirmation.jsx b/src/components/CippWizard/CippWizardVacationConfirmation.jsx index da86c414feb0..70a55423a6fb 100644 --- a/src/components/CippWizard/CippWizardVacationConfirmation.jsx +++ b/src/components/CippWizard/CippWizardVacationConfirmation.jsx @@ -58,18 +58,31 @@ export const CippWizardVacationConfirmation = (props) => { } if (values.enableOOO) { + const oooData = { + tenantFilter, + Users: values.Users, + internalMessage: values.oooInternalMessage, + externalMessage: values.oooExternalMessage, + startDate: values.startDate, + endDate: values.endDate, + reference: values.reference || null, + postExecution: values.postExecution || [], + }; + // Calendar options — only include when truthy + if (values.oooCreateOOFEvent) { + oooData.CreateOOFEvent = true; + if (values.oooOOFEventSubject) oooData.OOFEventSubject = values.oooOOFEventSubject; + } + if (values.oooAutoDeclineFutureRequests) { + oooData.AutoDeclineFutureRequestsWhenOOF = true; + } + if (values.oooDeclineEvents) { + oooData.DeclineEventsForScheduledOOF = true; + if (values.oooDeclineMeetingMessage) oooData.DeclineMeetingMessage = values.oooDeclineMeetingMessage; + } oooVacation.mutate({ url: "/api/ExecScheduleOOOVacation", - data: { - tenantFilter, - Users: values.Users, - internalMessage: values.oooInternalMessage, - externalMessage: values.oooExternalMessage, - startDate: values.startDate, - endDate: values.endDate, - reference: values.reference || null, - postExecution: values.postExecution || [], - }, + data: oooData, }); } }; @@ -244,6 +257,24 @@ export const CippWizardVacationConfirmation = (props) => { )} + {(values.oooCreateOOFEvent || values.oooAutoDeclineFutureRequests || values.oooDeclineEvents) && ( +
+ + Calendar Options + + + {values.oooCreateOOFEvent && ( + + )} + {values.oooAutoDeclineFutureRequests && ( + + )} + {values.oooDeclineEvents && ( + + )} + +
+ )} diff --git a/src/pages/identity/administration/vacation-mode/add/index.js b/src/pages/identity/administration/vacation-mode/add/index.js index eb4f73a75cbc..3730cb354c57 100644 --- a/src/pages/identity/administration/vacation-mode/add/index.js +++ b/src/pages/identity/administration/vacation-mode/add/index.js @@ -79,6 +79,11 @@ const Page = () => { enableOOO: false, oooInternalMessage: null, oooExternalMessage: null, + oooCreateOOFEvent: false, + oooOOFEventSubject: "", + oooAutoDeclineFutureRequests: false, + oooDeclineEvents: false, + oooDeclineMeetingMessage: "", startDate: null, endDate: null, postExecution: [], From a69b618eb82a3497ff4dfd85f3bb1b816f8b5887 Mon Sep 17 00:00:00 2001 From: Bobby <31723128+kris6673@users.noreply.github.com> Date: Thu, 19 Mar 2026 18:56:47 +0100 Subject: [PATCH 03/26] feat: refactor assignment fields and custom data formatter Fixes #5589 --- src/pages/endpoint/applications/list/index.js | 180 ++++++------------ 1 file changed, 62 insertions(+), 118 deletions(-) diff --git a/src/pages/endpoint/applications/list/index.js b/src/pages/endpoint/applications/list/index.js index 49e5bc4b2816..87ef0d12901a 100644 --- a/src/pages/endpoint/applications/list/index.js +++ b/src/pages/endpoint/applications/list/index.js @@ -62,47 +62,59 @@ const Page = () => { }, ]; + // Builds a customDataformatter that handles both single-row and bulk (array) inputs. + const makeAssignFormatter = (getRowData) => (row, action, formData) => { + const formatRow = (singleRow) => { + const tenantFilterValue = + tenant === "AllTenants" && singleRow?.Tenant ? singleRow.Tenant : tenant; + return { + tenantFilter: tenantFilterValue, + ID: singleRow?.id, + AppType: getAppAssignmentSettingsType(singleRow?.["@odata.type"]), + AssignmentFilterName: formData?.assignmentFilter?.value || null, + AssignmentFilterType: formData?.assignmentFilter?.value + ? formData?.assignmentFilterType || "include" + : null, + ...getRowData(singleRow, formData), + }; + }; + return Array.isArray(row) ? row.map(formatRow) : formatRow(row); + }; + + const assignmentFields = [ + { + type: "radio", + name: "Intent", + label: "Assignment intent", + options: assignmentIntentOptions, + defaultValue: "Required", + validators: { required: "Select an assignment intent" }, + helperText: + "Available assigns to Company Portal, Required installs automatically, Uninstall removes the app, Available without enrollment exposes it without device enrollment.", + }, + { + type: "radio", + name: "assignmentMode", + label: "Assignment mode", + options: assignmentModeOptions, + defaultValue: "replace", + helperText: + "Replace will overwrite existing assignments. Append keeps current assignments and adds/overwrites only for the selected groups/intents.", + }, + ...getAssignmentFilterFields(), + ]; + const actions = [ { label: "Assign to All Users", type: "POST", url: "/api/ExecAssignApp", - fields: [ - { - type: "radio", - name: "Intent", - label: "Assignment intent", - options: assignmentIntentOptions, - defaultValue: "Required", - validators: { required: "Select an assignment intent" }, - helperText: - "Available assigns to Company Portal, Required installs automatically, Uninstall removes the app, Available without enrollment exposes it without device enrollment.", - }, - { - type: "radio", - name: "assignmentMode", - label: "Assignment mode", - options: assignmentModeOptions, - defaultValue: "replace", - helperText: - "Replace will overwrite existing assignments. Append keeps current assignments and adds/overwrites only for the selected groups/intents.", - }, - ...getAssignmentFilterFields(), - ], - customDataformatter: (row, action, formData) => { - const tenantFilterValue = tenant === "AllTenants" && row?.Tenant ? row.Tenant : tenant; - return { - tenantFilter: tenantFilterValue, - ID: row?.id, - AssignTo: "AllUsers", - Intent: formData?.Intent || "Required", - assignmentMode: formData?.assignmentMode || "replace", - AssignmentFilterName: formData?.assignmentFilter?.value || null, - AssignmentFilterType: formData?.assignmentFilter?.value - ? formData?.assignmentFilterType || "include" - : null, - }; - }, + fields: assignmentFields, + customDataformatter: makeAssignFormatter((_singleRow, formData) => ({ + AssignTo: "AllUsers", + Intent: formData?.Intent || "Required", + assignmentMode: formData?.assignmentMode || "replace", + })), confirmText: 'Are you sure you want to assign "[displayName]" to all users?', icon: , color: "info", @@ -111,42 +123,12 @@ const Page = () => { label: "Assign to All Devices", type: "POST", url: "/api/ExecAssignApp", - fields: [ - { - type: "radio", - name: "Intent", - label: "Assignment intent", - options: assignmentIntentOptions, - defaultValue: "Required", - validators: { required: "Select an assignment intent" }, - helperText: - "Available assigns to Company Portal, Required installs automatically, Uninstall removes the app, Available without enrollment exposes it without device enrollment.", - }, - { - type: "radio", - name: "assignmentMode", - label: "Assignment mode", - options: assignmentModeOptions, - defaultValue: "replace", - helperText: - "Replace will overwrite existing assignments. Append keeps current assignments and adds/overwrites only for the selected groups/intents.", - }, - ...getAssignmentFilterFields(), - ], - customDataformatter: (row, action, formData) => { - const tenantFilterValue = tenant === "AllTenants" && row?.Tenant ? row.Tenant : tenant; - return { - tenantFilter: tenantFilterValue, - ID: row?.id, - AssignTo: "AllDevices", - Intent: formData?.Intent || "Required", - assignmentMode: formData?.assignmentMode || "replace", - AssignmentFilterName: formData?.assignmentFilter?.value || null, - AssignmentFilterType: formData?.assignmentFilter?.value - ? formData?.assignmentFilterType || "include" - : null, - }; - }, + fields: assignmentFields, + customDataformatter: makeAssignFormatter((_singleRow, formData) => ({ + AssignTo: "AllDevices", + Intent: formData?.Intent || "Required", + assignmentMode: formData?.assignmentMode || "replace", + })), confirmText: 'Are you sure you want to assign "[displayName]" to all devices?', icon: , color: "info", @@ -155,42 +137,12 @@ const Page = () => { label: "Assign Globally (All Users / All Devices)", type: "POST", url: "/api/ExecAssignApp", - fields: [ - { - type: "radio", - name: "Intent", - label: "Assignment intent", - options: assignmentIntentOptions, - defaultValue: "Required", - validators: { required: "Select an assignment intent" }, - helperText: - "Available assigns to Company Portal, Required installs automatically, Uninstall removes the app, Available without enrollment exposes it without device enrollment.", - }, - { - type: "radio", - name: "assignmentMode", - label: "Assignment mode", - options: assignmentModeOptions, - defaultValue: "replace", - helperText: - "Replace will overwrite existing assignments. Append keeps current assignments and adds/overwrites only for the selected groups/intents.", - }, - ...getAssignmentFilterFields(), - ], - customDataformatter: (row, action, formData) => { - const tenantFilterValue = tenant === "AllTenants" && row?.Tenant ? row.Tenant : tenant; - return { - tenantFilter: tenantFilterValue, - ID: row?.id, - AssignTo: "AllDevicesAndUsers", - Intent: formData?.Intent || "Required", - assignmentMode: formData?.assignmentMode || "replace", - AssignmentFilterName: formData?.assignmentFilter?.value || null, - AssignmentFilterType: formData?.assignmentFilter?.value - ? formData?.assignmentFilterType || "include" - : null, - }; - }, + fields: assignmentFields, + customDataformatter: makeAssignFormatter((_singleRow, formData) => ({ + AssignTo: "AllDevicesAndUsers", + Intent: formData?.Intent || "Required", + assignmentMode: formData?.assignmentMode || "replace", + })), confirmText: 'Are you sure you want to assign "[displayName]" to all users and devices?', icon: , color: "info", @@ -252,23 +204,15 @@ const Page = () => { }, ...getAssignmentFilterFields(), ], - customDataformatter: (row, action, formData) => { + customDataformatter: makeAssignFormatter((_singleRow, formData) => { const selectedGroups = Array.isArray(formData?.groupTargets) ? formData.groupTargets : []; - const tenantFilterValue = tenant === "AllTenants" && row?.Tenant ? row.Tenant : tenant; return { - tenantFilter: tenantFilterValue, - ID: row?.id, GroupIds: selectedGroups.map((group) => group.value).filter(Boolean), GroupNames: selectedGroups.map((group) => group.label).filter(Boolean), Intent: formData?.assignmentIntent || "Required", AssignmentMode: formData?.assignmentMode || "replace", - AppType: getAppAssignmentSettingsType(row?.["@odata.type"]), - AssignmentFilterName: formData?.assignmentFilter?.value || null, - AssignmentFilterType: formData?.assignmentFilter?.value - ? formData?.assignmentFilterType || "include" - : null, }; - }, + }), }, { label: "Delete Application", From 0615839e04e05a518e933744b5ac40c053559c02 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 19 Mar 2026 22:45:08 -0400 Subject: [PATCH 04/26] feat: add Custom Subject field and enhance Notification Settings layout in AlertWizard --- .../alert-configuration/alert.jsx | 144 +++++++++++------- 1 file changed, 89 insertions(+), 55 deletions(-) diff --git a/src/pages/tenant/administration/alert-configuration/alert.jsx b/src/pages/tenant/administration/alert-configuration/alert.jsx index 577134d1fcc6..26026bb4b984 100644 --- a/src/pages/tenant/administration/alert-configuration/alert.jsx +++ b/src/pages/tenant/administration/alert-configuration/alert.jsx @@ -184,6 +184,7 @@ const AlertWizard = () => { recurrence: recurrenceOption, postExecution: postExecutionValue, startDateTime: startDateTimeForForm, + CustomSubject: alert.RawAlert.CustomSubject || "", AlertComment: alert.RawAlert.AlertComment || "", }; if (usedCommand?.requiresInput && alert.RawAlert.Parameters) { @@ -256,6 +257,7 @@ const AlertWizard = () => { Actions: alert.RawAlert.Actions, logbook: foundLogbook, AlertComment: alert.RawAlert.AlertComment || "", + CustomSubject: alert.RawAlert.CustomSubject || "", conditions: [], // Include empty array to register field structure }; // Reset first without spawning rows to avoid rendering empty operator fields @@ -477,7 +479,9 @@ const AlertWizard = () => { RowKey: router.query.clone ? undefined : router.query.id ? router.query.id : undefined, tenantFilter: values.tenantFilter, excludedTenants: values.excludedTenants, - Name: `${values.tenantFilter?.label || values.tenantFilter?.value}: ${values.command.label}`, + Name: values.CustomSubject + ? `${values.tenantFilter?.label || values.tenantFilter?.value}: ${values.CustomSubject}` + : `${values.tenantFilter?.label || values.tenantFilter?.value}: ${values.command.label}`, Command: { value: `Get-CIPPAlert${values.command.value.name}` }, Parameters: getInputParams(), ScheduledTime: Math.floor(new Date().getTime() / 1000) + 60, @@ -485,6 +489,7 @@ const AlertWizard = () => { Recurrence: values.recurrence, PostExecution: values.postExecution, AlertComment: values.AlertComment, + CustomSubject: values.CustomSubject, }; apiRequest.mutate( { url: "/api/AddScheduledItem?hidden=true", data: postObject }, @@ -600,19 +605,7 @@ const AlertWizard = () => { - } - > - Save Alert - - } - sx={{ mb: 3 }} - > + { ))} + + + + } + > + Save Alert + + } + > { options={actionsToTake} /> + + + { placeholder="Add documentation, FAQ links, or instructions for when this alert triggers..." /> + @@ -897,19 +916,7 @@ const AlertWizard = () => { - } - > - Save Alert - - } - > + { ))} - - - - - - - - - + + + + + + } + > + Save Alert + + } + > + + + + + + + + + + + + From 4b5c6c79e8eee6cf84002e17b2c0c9c6976d1ff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= <31723128+kris6673@users.noreply.github.com> Date: Fri, 20 Mar 2026 10:52:26 +0100 Subject: [PATCH 05/26] fix: Remove 'scope' from Scheduler Removed 'scope' property from the Scheduler configuration. --- src/layouts/config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/layouts/config.js b/src/layouts/config.js index 4f8be5c348c7..db973f913e4a 100644 --- a/src/layouts/config.js +++ b/src/layouts/config.js @@ -903,7 +903,6 @@ export const nativeMenuItems = [ path: "/cipp/scheduler", roles: ["editor", "admin", "superadmin"], permissions: ["CIPP.Scheduler.*"], - scope: "global", }, ], }, From ef26c18fce46620333f815d4e29f390faf74196b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= <31723128+kris6673@users.noreply.github.com> Date: Fri, 20 Mar 2026 11:04:11 +0100 Subject: [PATCH 06/26] fix: Remove tenantInTitle prop from CippSchedulerDrawer --- src/pages/cipp/scheduler/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/cipp/scheduler/index.js b/src/pages/cipp/scheduler/index.js index 73b2396f5171..446f7ca0593f 100644 --- a/src/pages/cipp/scheduler/index.js +++ b/src/pages/cipp/scheduler/index.js @@ -66,7 +66,6 @@ const Page = () => { } - tenantInTitle={false} title="Scheduled Tasks" apiUrl={ showHiddenJobs ? `/api/ListScheduledItems?ShowHidden=true` : `/api/ListScheduledItems` From 2209de70445f71e1ce8cde7a8a66abc9729bba82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Fri, 20 Mar 2026 13:33:24 +0100 Subject: [PATCH 07/26] fix(CippApiResults): prevent alert from overflowing drawer on narrow screens/drawers Add `minWidth: 0` to the Stack so CSS Grid can constrain it to the cell width. Without this, the action buttons (Get Help, copy, close) forced the Alert wider than the drawer on mobile/narrow viewports. --- src/components/CippComponents/CippApiResults.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CippComponents/CippApiResults.jsx b/src/components/CippComponents/CippApiResults.jsx index 777fc9d07283..1c4ca364c362 100644 --- a/src/components/CippComponents/CippApiResults.jsx +++ b/src/components/CippComponents/CippApiResults.jsx @@ -244,7 +244,7 @@ export const CippApiResults = (props) => { const hasVisibleResults = finalResults.some((r) => r.visible); return ( - + {/* Loading alert */} {!errorsOnly && ( From f75df34ab7755b3128f1354f9b63797a429b68f6 Mon Sep 17 00:00:00 2001 From: Luis Mengel Date: Sat, 21 Mar 2026 23:04:20 +0100 Subject: [PATCH 08/26] fix(group-templates): allow resubmit on template edit page --- src/pages/identity/administration/group-templates/edit.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/identity/administration/group-templates/edit.jsx b/src/pages/identity/administration/group-templates/edit.jsx index 5fd7f3417805..8c83b0461005 100644 --- a/src/pages/identity/administration/group-templates/edit.jsx +++ b/src/pages/identity/administration/group-templates/edit.jsx @@ -46,6 +46,7 @@ const Page = () => { allowExternal: templateData.allowExternal, tenantFilter: userSettingsDefaults.currentTenant, }); + formControl.trigger(); } } }, [template, formControl, userSettingsDefaults.currentTenant]); From 52e2bbec0c033b657f01bbfdd8b27a018562fc53 Mon Sep 17 00:00:00 2001 From: Luis Mengel Date: Sun, 22 Mar 2026 01:37:37 +0100 Subject: [PATCH 09/26] feat(security): add MDE onboarding status page --- src/layouts/config.js | 7 +- .../security/reports/mde-onboarding/index.js | 235 ++++++++++++++++++ 2 files changed, 241 insertions(+), 1 deletion(-) create mode 100644 src/pages/security/reports/mde-onboarding/index.js diff --git a/src/layouts/config.js b/src/layouts/config.js index 4f8be5c348c7..a41154603b3d 100644 --- a/src/layouts/config.js +++ b/src/layouts/config.js @@ -347,13 +347,18 @@ export const nativeMenuItems = [ }, { title: "Reports", - permissions: ["Tenant.DeviceCompliance.*"], + permissions: ["Tenant.DeviceCompliance.*", "Security.Defender.*"], items: [ { title: "Device Compliance", path: "/security/reports/list-device-compliance", permissions: ["Tenant.DeviceCompliance.*"], }, + { + title: "MDE Onboarding", + path: "/security/reports/mde-onboarding", + permissions: ["Security.Defender.*"], + }, ], }, { diff --git a/src/pages/security/reports/mde-onboarding/index.js b/src/pages/security/reports/mde-onboarding/index.js new file mode 100644 index 000000000000..015653b411c1 --- /dev/null +++ b/src/pages/security/reports/mde-onboarding/index.js @@ -0,0 +1,235 @@ +import { Layout as DashboardLayout } from "../../../../layouts/index.js"; +import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx"; +import { useSettings } from "../../../../hooks/use-settings"; +import { + Card, + CardContent, + CardHeader, + Chip, + Container, + Stack, + Typography, + CircularProgress, + Button, + SvgIcon, + IconButton, + Tooltip, +} from "@mui/material"; +import { Sync, Info, OpenInNew } from "@mui/icons-material"; +import { ApiGetCall } from "../../../../api/ApiCall"; +import { CippHead } from "../../../../components/CippComponents/CippHead"; +import { useDialog } from "../../../../hooks/use-dialog"; +import { CippApiDialog } from "../../../../components/CippComponents/CippApiDialog"; +import { CippQueueTracker } from "../../../../components/CippTable/CippQueueTracker"; +import { useState } from "react"; + +const statusColors = { + enabled: "success", + available: "success", + unavailable: "error", + unresponsive: "warning", + notSetUp: "default", + error: "error", +}; + +const statusLabels = { + enabled: "Enabled", + available: "Available", + unavailable: "Unavailable", + unresponsive: "Unresponsive", + notSetUp: "Not Set Up", + error: "Error", +}; + +const SingleTenantView = ({ tenant }) => { + const syncDialog = useDialog(); + const [syncQueueId, setSyncQueueId] = useState(null); + + const tenantList = ApiGetCall({ + url: "/api/listTenants", + queryKey: "TenantSelector", + }); + + const tenantId = tenantList.data?.find( + (t) => t.defaultDomainName === tenant + )?.customerId; + + const { data, isFetching } = ApiGetCall({ + url: "/api/ListMDEOnboarding", + queryKey: `MDEOnboarding-${tenant}`, + data: { tenantFilter: tenant, UseReportDB: true }, + waiting: true, + }); + + const item = Array.isArray(data) ? data[0] : data; + const status = item?.partnerState || "Unknown"; + + return ( + <> + + + + + + + + + + + + + } + /> + + {isFetching ? ( + + ) : ( + + + Status: + + + {item?.CacheTimestamp && ( + + Last synced: {new Date(item.CacheTimestamp).toLocaleString()} + + )} + {item?.error && ( + + {item.error} + + )} + {tenantId && status !== "enabled" && status !== "available" && ( + + )} + + )} + + + + { + if (result?.Metadata?.QueueId) { + setSyncQueueId(result.Metadata.QueueId); + } + }, + }} + /> + + ); +}; + +const Page = () => { + const currentTenant = useSettings().currentTenant; + const isAllTenants = currentTenant === "AllTenants"; + const syncDialog = useDialog(); + const [syncQueueId, setSyncQueueId] = useState(null); + + if (!isAllTenants) { + return ; + } + + const pageActions = [ + + + + + + + + + , + ]; + + return ( + <> + + { + if (result?.Metadata?.QueueId) { + setSyncQueueId(result.Metadata.QueueId); + } + }, + }} + /> + + ); +}; + +Page.getLayout = (page) => {page}; + +export default Page; \ No newline at end of file From b5bd2bab080814c4b7af3040de32664ab1add688 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Sun, 22 Mar 2026 10:37:51 +0800 Subject: [PATCH 10/26] pr --- .../update_license_skus_frontend.yml | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/workflows/update_license_skus_frontend.yml diff --git a/.github/workflows/update_license_skus_frontend.yml b/.github/workflows/update_license_skus_frontend.yml new file mode 100644 index 000000000000..d3e0c7a35dd3 --- /dev/null +++ b/.github/workflows/update_license_skus_frontend.yml @@ -0,0 +1,44 @@ +name: Update License SKUs (Frontend) + +on: + workflow_dispatch: + schedule: + - cron: '0 6 * * 1' + +permissions: + contents: write + pull-requests: write + +jobs: + update-frontend-skus: + if: github.repository_owner == 'Zacgoose' + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download shared SKU update script + id: download-script + shell: pwsh + run: | + $scriptPath = Join-Path $env:RUNNER_TEMP 'Update-LicenseSKUFiles.ps1' + Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/Zacgoose/CIPP-API/dev/Tools/Update-LicenseSKUFiles.ps1' -OutFile $scriptPath -ErrorAction Stop + + "script_path=$scriptPath" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append + + - name: Update frontend SKU files + shell: pwsh + run: | + & "${{ steps.download-script.outputs.script_path }}" -Target frontend -FrontendRepoPath "${{ github.workspace }}" + + - name: Create pull request + uses: peter-evans/create-pull-request@v7 + with: + commit-message: 'chore: update frontend license SKU files' + title: 'chore: update frontend license SKU files' + body: 'Automated weekly update of frontend Microsoft license SKU data.' + branch: chore/update-frontend-license-skus + base: dev + delete-branch: true + add-paths: src/data/*M365Licenses.json From 8f51153862e781cda64a7c75bdae4dba0527344b Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Sun, 22 Mar 2026 10:40:22 +0800 Subject: [PATCH 11/26] Revert "pr" This reverts commit b5bd2bab080814c4b7af3040de32664ab1add688. --- .../update_license_skus_frontend.yml | 44 ------------------- 1 file changed, 44 deletions(-) delete mode 100644 .github/workflows/update_license_skus_frontend.yml diff --git a/.github/workflows/update_license_skus_frontend.yml b/.github/workflows/update_license_skus_frontend.yml deleted file mode 100644 index d3e0c7a35dd3..000000000000 --- a/.github/workflows/update_license_skus_frontend.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Update License SKUs (Frontend) - -on: - workflow_dispatch: - schedule: - - cron: '0 6 * * 1' - -permissions: - contents: write - pull-requests: write - -jobs: - update-frontend-skus: - if: github.repository_owner == 'Zacgoose' - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Download shared SKU update script - id: download-script - shell: pwsh - run: | - $scriptPath = Join-Path $env:RUNNER_TEMP 'Update-LicenseSKUFiles.ps1' - Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/Zacgoose/CIPP-API/dev/Tools/Update-LicenseSKUFiles.ps1' -OutFile $scriptPath -ErrorAction Stop - - "script_path=$scriptPath" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append - - - name: Update frontend SKU files - shell: pwsh - run: | - & "${{ steps.download-script.outputs.script_path }}" -Target frontend -FrontendRepoPath "${{ github.workspace }}" - - - name: Create pull request - uses: peter-evans/create-pull-request@v7 - with: - commit-message: 'chore: update frontend license SKU files' - title: 'chore: update frontend license SKU files' - body: 'Automated weekly update of frontend Microsoft license SKU data.' - branch: chore/update-frontend-license-skus - base: dev - delete-branch: true - add-paths: src/data/*M365Licenses.json From a37a0649f1fe9e1cff6535c550b242733f1be337 Mon Sep 17 00:00:00 2001 From: James Tarran Date: Mon, 23 Mar 2026 10:42:07 +0000 Subject: [PATCH 12/26] Update CippTextFieldWithVariables.jsx - Fix variable insertion not tracking cursor correctly on multiline fields --- .../CippTextFieldWithVariables.jsx | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/components/CippComponents/CippTextFieldWithVariables.jsx b/src/components/CippComponents/CippTextFieldWithVariables.jsx index 80d720ac9440..6c083ead4be1 100644 --- a/src/components/CippComponents/CippTextFieldWithVariables.jsx +++ b/src/components/CippComponents/CippTextFieldWithVariables.jsx @@ -33,7 +33,12 @@ export const CippTextFieldWithVariables = ({ // Track cursor position const handleSelectionChange = () => { if (textFieldRef.current) { - setCursorPosition(textFieldRef.current.selectionStart || 0); + // Check for input first, then textarea + const inputElement = + textFieldRef.current.querySelector("input") || + textFieldRef.current.querySelector("textarea") || + textFieldRef.current; + setCursorPosition(inputElement?.selectionStart || 0); } }; @@ -97,7 +102,11 @@ export const CippTextFieldWithVariables = ({ // Get fresh cursor position from the DOM let cursorPos = cursorPosition; if (textFieldRef.current) { - const inputElement = textFieldRef.current.querySelector("input") || textFieldRef.current; + // Check for input first, then textarea + const inputElement = + textFieldRef.current.querySelector("input") || + textFieldRef.current.querySelector("textarea") || + textFieldRef.current; if (inputElement && typeof inputElement.selectionStart === "number") { cursorPos = inputElement.selectionStart; } @@ -128,8 +137,11 @@ export const CippTextFieldWithVariables = ({ const newCursorPos = lastPercentIndex + variableString.length; // Access the actual input element for Material-UI TextField + // Check for input first, then textarea const inputElement = - textFieldRef.current.querySelector("input") || textFieldRef.current; + textFieldRef.current.querySelector("input") || + textFieldRef.current.querySelector("textarea") || + textFieldRef.current; if (inputElement && inputElement.setSelectionRange) { inputElement.setSelectionRange(newCursorPos, newCursorPos); inputElement.focus(); From d4fa117c3112f02de797f7c99a2599b7c05cb3bd Mon Sep 17 00:00:00 2001 From: Bobby <31723128+kris6673@users.noreply.github.com> Date: Mon, 23 Mar 2026 12:19:19 +0100 Subject: [PATCH 13/26] feat: add tooltip for truncated data in getCippFormatting Enhanced the getCippFormatting function to display a tooltip for truncated data in hardware and message fields. This improves user experience by providing full context on hover. --- src/utils/get-cipp-formatting.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/utils/get-cipp-formatting.js b/src/utils/get-cipp-formatting.js index 79139e2b2347..5e17c3735042 100644 --- a/src/utils/get-cipp-formatting.js +++ b/src/utils/get-cipp-formatting.js @@ -10,7 +10,7 @@ import { PrecisionManufacturing, BarChart, } from "@mui/icons-material"; -import { Chip, Link, SvgIcon } from "@mui/material"; +import { Chip, Link, SvgIcon, Tooltip } from "@mui/material"; import { alpha } from "@mui/material/styles"; import { Box } from "@mui/system"; import { CippCopyToClipBoard } from "../components/CippComponents/CippCopyToClipboard"; @@ -220,7 +220,11 @@ export const getCippFormatting = (data, cellName, type, canReceive, flatten = tr (hardwareHashFields.includes(cellName) || cellNameLower.includes("hardware")) ) { if (data.length > 15) { - return isText ? data : `${data.substring(0, 15)}...`; + return isText ? data : ( + + {data.substring(0, 15)}... + + ); } return isText ? data : data; } @@ -229,7 +233,11 @@ export const getCippFormatting = (data, cellName, type, canReceive, flatten = tr const messageFields = ["Message"]; if (messageFields.includes(cellName)) { if (typeof data === "string" && data.length > 120) { - return isText ? data : `${data.substring(0, 120)}...`; + return isText ? data : ( + + {data.substring(0, 120)}... + + ); } return isText ? data : data; } From a78136cec35830247cf32e5fda23f50965cd1bde Mon Sep 17 00:00:00 2001 From: k-grube Date: Mon, 23 Mar 2026 09:03:50 -0700 Subject: [PATCH 14/26] chore: update gitignore --- .gitignore | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index 78ea4526e7ce..44dac6dd492c 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,10 @@ app.log # AI rules .*/rules AGENTS.md + +# jetbrains +.idea + +# azurite +__* +AzuriteConfig From 390490975e2e7753383330d323b227f622643bcd Mon Sep 17 00:00:00 2001 From: k-grube Date: Mon, 23 Mar 2026 09:16:49 -0700 Subject: [PATCH 15/26] chore: add react type defs --- package.json | 2 ++ yarn.lock | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 1fe17fc166a6..6c8daea53c0f 100644 --- a/package.json +++ b/package.json @@ -115,6 +115,8 @@ }, "devDependencies": { "@svgr/webpack": "8.1.0", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", "eslint": "9.39.2", "eslint-config-next": "16.1.6" } diff --git a/yarn.lock b/yarn.lock index 388c3f43e078..d1493baeab00 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2487,6 +2487,11 @@ resolved "https://registry.yarnpkg.com/@types/raf/-/raf-3.4.3.tgz#85f1d1d17569b28b8db45e16e996407a56b0ab04" integrity sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw== +"@types/react-dom@^19.2.3": + version "19.2.3" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.2.3.tgz#c1e305d15a52a3e508d54dca770d202cb63abf2c" + integrity sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ== + "@types/react-redux@^7.1.20": version "7.1.34" resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.34.tgz#83613e1957c481521e6776beeac4fd506d11bd0e" @@ -2502,7 +2507,7 @@ resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.12.tgz#b5d76568485b02a307238270bfe96cb51ee2a044" integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w== -"@types/react@*": +"@types/react@*", "@types/react@^19.2.14": version "19.2.14" resolved "https://registry.yarnpkg.com/@types/react/-/react-19.2.14.tgz#39604929b5e3957e3a6fa0001dafb17c7af70bad" integrity sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w== From 28be83881275aa0e68b469db5f7418d07b9d560a Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Tue, 24 Mar 2026 10:14:57 +0800 Subject: [PATCH 16/26] Add UI action to clear HIBP API key Import ApiPostCall and add a clearHIBPKey mutation to the integrations configure page. Render a "Clear API Key" button for the HIBP extension that calls /api/ExecExtensionClearHIBPKey and disables while pending. The mutation invalidates the "Integrations" query, and its results are shown via CippApiResults. This provides a way to clear the stored HIBP API key and refresh integration state. --- src/pages/cipp/integrations/configure.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/pages/cipp/integrations/configure.js b/src/pages/cipp/integrations/configure.js index 330e03d55b13..578317466c54 100644 --- a/src/pages/cipp/integrations/configure.js +++ b/src/pages/cipp/integrations/configure.js @@ -13,7 +13,7 @@ import CippIntegrationSettings from "../../../components/CippIntegrations/CippIn import { Layout as DashboardLayout } from "../../../layouts/index.js"; import { useForm } from "react-hook-form"; import { useSettings } from "../../../hooks/use-settings"; -import { ApiGetCall } from "../../../api/ApiCall"; +import { ApiGetCall, ApiPostCall } from "../../../api/ApiCall"; import { useRouter } from "next/router"; import extensions from "../../../data/Extensions.json"; import { useEffect } from "react"; @@ -74,6 +74,9 @@ const Page = () => { const actionSyncResults = ApiGetCall({ ...syncQuery, }); + const clearHIBPKey = ApiPostCall({ + relatedQueryKeys: ["Integrations"], + }); const handleIntegrationSync = () => { setSyncQuery({ url: "/api/ExecExtensionSync", @@ -191,6 +194,23 @@ const Page = () => { )} + {extension?.id === "HIBP" && ( + + + + )} {extension?.links && ( <> {extension.links.map((link, index) => ( @@ -208,6 +228,7 @@ const Page = () => { + From 4fc72a414895f6e7c0f0e24f1600864a8955124a Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Tue, 24 Mar 2026 17:06:56 +0800 Subject: [PATCH 17/26] Add standardized schema toggle to form Introduce a UseStandardizedSchema option to the notification form. The changes set a default false value when loading config and add a switch control labeled "Use Standardized Alert Schema" with helper text explaining it enables a consistent JSON schema for webhook alerts (improves Power Automate / Logic Apps integration). The switch is disabled while notification config is being fetched; default remains off for backward compatibility. --- src/components/CippComponents/CippNotificationForm.jsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/components/CippComponents/CippNotificationForm.jsx b/src/components/CippComponents/CippNotificationForm.jsx index 9f499e4ac4c1..0621a6cb305e 100644 --- a/src/components/CippComponents/CippNotificationForm.jsx +++ b/src/components/CippComponents/CippNotificationForm.jsx @@ -69,6 +69,7 @@ export const CippNotificationForm = ({ onePerTenant: listNotificationConfig.data?.onePerTenant, sendtoIntegration: listNotificationConfig.data?.sendtoIntegration, includeTenantId: listNotificationConfig.data?.includeTenantId, + UseStandardizedSchema: listNotificationConfig.data?.UseStandardizedSchema || false, }); } }, [listNotificationConfig.isSuccess]); @@ -136,6 +137,14 @@ export const CippNotificationForm = ({ name="sendtoIntegration" formControl={formControl} /> + {showTestButton && ( From acfd6c411538fe5f0e177247ac96f5f1cdba4da7 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 24 Mar 2026 14:32:54 -0400 Subject: [PATCH 18/26] feat: DeployCheckChromeExtension to use CyberDrain branding and enhance configuration options --- src/data/standards.json | 71 +++++++++++++++++++++++++++++++++++------ 1 file changed, 62 insertions(+), 9 deletions(-) diff --git a/src/data/standards.json b/src/data/standards.json index fe08f2fcbb45..d68360bd98d3 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -5847,15 +5847,21 @@ "name": "standards.DeployCheckChromeExtension", "cat": "Intune Standards", "tag": [], - "helpText": "Deploys the Check Chrome extension via Intune OMA-URI custom policies for both Chrome and Edge browsers with configurable settings. Chrome ID: benimdeioplgkhanklclahllklceahbe, Edge ID: knepjpocdagponkonnbggpcnhnaikajg", - "docsDescription": "Creates Intune OMA-URI custom policies that automatically install and configure the Check Chrome extension on managed devices for both Google Chrome and Microsoft Edge browsers. This ensures the extension is deployed consistently across all corporate devices with customizable settings.", - "executiveText": "Automatically deploys the Check browser extension across all company devices with configurable security and branding settings, ensuring consistent security monitoring and compliance capabilities. This extension provides enhanced security features and monitoring tools that help protect against threats while maintaining user productivity.", + "helpText": "Deploys the Check by CyberDrain browser extension via a Win32 script app in Intune for both Chrome and Edge browsers with configurable settings. Chrome ID: benimdeioplgkhanklclahllklceahbe, Edge ID: knepjpocdagponkonnbggpcnhnaikajg", + "docsDescription": "Creates an Intune Win32 script application that writes registry keys to install and configure the Check by CyberDrain browser extension on managed devices for both Google Chrome and Microsoft Edge browsers. Uses a PowerShell detection script to enforce configuration drift — when settings change in CIPP the app is automatically redeployed.", + "executiveText": "Automatically deploys the Check by CyberDrain browser extension across all company devices with configurable security and branding settings, ensuring consistent security monitoring and compliance capabilities. This extension provides enhanced security features and monitoring tools that help protect against threats while maintaining user productivity.", "addedComponent": [ + { + "type": "switch", + "name": "standards.DeployCheckChromeExtension.showNotifications", + "label": "Show notifications", + "defaultValue": true + }, { "type": "switch", "name": "standards.DeployCheckChromeExtension.enableValidPageBadge", "label": "Enable valid page badge", - "defaultValue": true + "defaultValue": false }, { "type": "switch", @@ -5863,6 +5869,12 @@ "label": "Enable page blocking", "defaultValue": true }, + { + "type": "switch", + "name": "standards.DeployCheckChromeExtension.forceToolbarPin", + "label": "Force pin extension to toolbar", + "defaultValue": false + }, { "type": "switch", "name": "standards.DeployCheckChromeExtension.enableCippReporting", @@ -5874,13 +5886,14 @@ "name": "standards.DeployCheckChromeExtension.customRulesUrl", "label": "Custom Rules URL", "placeholder": "https://YOUR-CIPP-SERVER-URL/rules.json", + "helperText": "Enter the URL for custom rules if you have them. This should point to a JSON file with the same structure as the rules.json used for CIPP reporting.", "required": false }, { "type": "number", "name": "standards.DeployCheckChromeExtension.updateInterval", "label": "Update interval (hours)", - "defaultValue": 12 + "defaultValue": 24 }, { "type": "switch", @@ -5888,6 +5901,39 @@ "label": "Enable debug logging", "defaultValue": false }, + { + "type": "switch", + "name": "standards.DeployCheckChromeExtension.enableGenericWebhook", + "label": "Enable generic webhook", + "defaultValue": false + }, + { + "type": "textField", + "name": "standards.DeployCheckChromeExtension.webhookUrl", + "label": "Webhook URL", + "placeholder": "https://webhook.example.com/endpoint", + "required": false + }, + { + "type": "autoComplete", + "multiple": true, + "creatable": true, + "required": false, + "name": "standards.DeployCheckChromeExtension.webhookEvents", + "label": "Webhook Events", + "placeholder": "e.g. pageBlocked, pageAllowed" + }, + { + "type": "autoComplete", + "multiple": true, + "creatable": true, + "required": false, + "freeSolo": true, + "name": "standards.DeployCheckChromeExtension.urlAllowlist", + "label": "URL Allowlist", + "placeholder": "e.g. https://example.com/*", + "helperText": "Enter URLs to allowlist in the extension. Press enter to add each URL. Wildcards are allowed. This should be used for sites that are being blocked by the extension but are known to be safe." + }, { "type": "textField", "name": "standards.DeployCheckChromeExtension.companyName", @@ -5895,6 +5941,13 @@ "placeholder": "YOUR-COMPANY", "required": false }, + { + "type": "textField", + "name": "standards.DeployCheckChromeExtension.companyURL", + "label": "Company URL", + "placeholder": "https://yourcompany.com", + "required": false + }, { "type": "textField", "name": "standards.DeployCheckChromeExtension.productName", @@ -5913,7 +5966,7 @@ "type": "textField", "name": "standards.DeployCheckChromeExtension.primaryColor", "label": "Primary Color", - "placeholder": "#0044CC", + "placeholder": "#F77F00", "required": false }, { @@ -5925,7 +5978,7 @@ }, { "name": "AssignTo", - "label": "Who should this policy be assigned to?", + "label": "Who should this app be assigned to?", "type": "radio", "options": [ { @@ -5957,11 +6010,11 @@ "label": "Enter the custom group name if you selected 'Assign to Custom Group'. Wildcards are allowed." } ], - "label": "Deploy Check Chrome Extension", + "label": "Deploy Check by CyberDrain Browser Extension", "impact": "Low Impact", "impactColour": "info", "addedDate": "2025-09-18", - "powershellEquivalent": "New-GraphPostRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/configurationPolicies'", + "powershellEquivalent": "Add-CIPPW32ScriptApplication", "recommendedBy": ["CIPP"] }, { From 824d66fed7778dc390610e7e2b2bc59ce8826e5a Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 24 Mar 2026 15:08:02 -0400 Subject: [PATCH 19/26] feat: add detection script support to custom application --- .../CippApplicationDeployDrawer.jsx | 57 +++++++++++++++---- 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/src/components/CippComponents/CippApplicationDeployDrawer.jsx b/src/components/CippComponents/CippApplicationDeployDrawer.jsx index 410be49561c0..7d6d2366301f 100644 --- a/src/components/CippComponents/CippApplicationDeployDrawer.jsx +++ b/src/components/CippComponents/CippApplicationDeployDrawer.jsx @@ -869,22 +869,55 @@ export const CippApplicationDeployDrawer = ({ rows={6} /> - - - - + + + + + + + + + + + + + + Date: Tue, 24 Mar 2026 17:24:42 -0400 Subject: [PATCH 20/26] chore: remove merge conflict markers --- src/pages/identity/administration/jit-admin/add.jsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/pages/identity/administration/jit-admin/add.jsx b/src/pages/identity/administration/jit-admin/add.jsx index 34f39fa8e5db..16842bd313f6 100644 --- a/src/pages/identity/administration/jit-admin/add.jsx +++ b/src/pages/identity/administration/jit-admin/add.jsx @@ -29,7 +29,6 @@ const Page = () => { }); const watcher = useWatch({ control: formControl.control }); -<<<<<<< fix/jit-admin-tap-policy-check const useTAP = useWatch({ control: formControl.control, name: "UseTAP" }); const tapPolicy = ApiGetCall({ @@ -44,7 +43,6 @@ const Page = () => { waiting: !!selectedTenant, }); const tapEnabled = tapPolicy.isSuccess && tapPolicy.data?.Results?.[0]?.state === "enabled"; -======= const useRoles = useWatch({ control: formControl.control, name: "useRoles" }); const useGroups = useWatch({ control: formControl.control, name: "useGroups" }); @@ -78,7 +76,6 @@ const Page = () => { formControl.setValue("expireAction", null); } }, [useRoles, useGroups]); ->>>>>>> dev // Simple duration parser for basic ISO 8601 durations const parseDuration = (duration) => { From a46d96f11f12f3fad4d35710d186b114622609b1 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Wed, 25 Mar 2026 17:52:33 +0800 Subject: [PATCH 21/26] Add webhook auth types and tenant filter Introduce webhook authentication options and related form fields for Notification settings. Imports CippFormCondition and useSettings, adds a webhookAuthTypes list, and loads additional webhook auth values (type, token, username, password, header name/value, headers) from API config. Adds conditional UI controls for Bearer, Basic, API Key Header, and Custom Headers (JSON). Updates effect deps to include dataUpdatedAt, changes Send Test Alert button disabling to depend on form dirty state, and includes currentTenant as tenantFilter in the test alert POST payload. --- .../CippComponents/CippNotificationForm.jsx | 138 +++++++++++++++++- 1 file changed, 135 insertions(+), 3 deletions(-) diff --git a/src/components/CippComponents/CippNotificationForm.jsx b/src/components/CippComponents/CippNotificationForm.jsx index 0621a6cb305e..03ecce096ec9 100644 --- a/src/components/CippComponents/CippNotificationForm.jsx +++ b/src/components/CippComponents/CippNotificationForm.jsx @@ -2,17 +2,20 @@ import { useEffect } from "react"; import { Button, Box } from "@mui/material"; import { Grid } from "@mui/system"; import CippFormComponent from "./CippFormComponent"; +import { CippFormCondition } from "./CippFormCondition"; import { ApiGetCall } from "../../api/ApiCall"; import { useDialog } from "../../hooks/use-dialog"; import { CippApiDialog } from "./CippApiDialog"; import { useFormState } from "react-hook-form"; +import { useSettings } from "../../hooks/use-settings"; export const CippNotificationForm = ({ formControl, showTestButton = true, - hideButtons = false, }) => { const notificationDialog = useDialog(); + const settings = useSettings(); + const currentTenant = settings.currentTenant; // API call to get notification configuration const listNotificationConfig = ApiGetCall({ @@ -49,6 +52,14 @@ export const CippNotificationForm = ({ { label: "Critical", value: "Critical" }, ]; + const webhookAuthTypes = [ + { label: "None", value: "None" }, + { label: "Bearer Token", value: "Bearer" }, + { label: "Basic Auth", value: "Basic" }, + { label: "API Key Header", value: "ApiKey" }, + { label: "Custom Headers (JSON)", value: "CustomHeaders" }, + ]; + // Load notification config data into form useEffect(() => { if (listNotificationConfig.isSuccess) { @@ -70,9 +81,18 @@ export const CippNotificationForm = ({ sendtoIntegration: listNotificationConfig.data?.sendtoIntegration, includeTenantId: listNotificationConfig.data?.includeTenantId, UseStandardizedSchema: listNotificationConfig.data?.UseStandardizedSchema || false, + webhookAuthType: webhookAuthTypes.find( + (type) => type.value === listNotificationConfig.data?.webhookAuthType, + ) || webhookAuthTypes[0], + webhookAuthToken: listNotificationConfig.data?.webhookAuthToken, + webhookAuthUsername: listNotificationConfig.data?.webhookAuthUsername, + webhookAuthPassword: listNotificationConfig.data?.webhookAuthPassword, + webhookAuthHeaderName: listNotificationConfig.data?.webhookAuthHeaderName, + webhookAuthHeaderValue: listNotificationConfig.data?.webhookAuthHeaderValue, + webhookAuthHeaders: listNotificationConfig.data?.webhookAuthHeaders, }); } - }, [listNotificationConfig.isSuccess]); + }, [listNotificationConfig.isSuccess, listNotificationConfig.dataUpdatedAt]); return ( <> @@ -99,6 +119,117 @@ export const CippNotificationForm = ({ helperText="Enter the webhook URL to send notifications to. The URL should be configured to receive a POST request." /> + + + + + + + + + + + <> + + + + + + + + + + + <> + + + + + + + + + + + + + + Send Test Alert @@ -194,6 +325,7 @@ export const CippNotificationForm = ({ type: "POST", dataFunction: (row) => ({ ...row, + tenantFilter: currentTenant, text: "This is a test from Notification Settings", }), }} From 5d217dc6419b85e04851ef5fabcedfe9687f5f1b Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Wed, 25 Mar 2026 19:04:10 +0800 Subject: [PATCH 22/26] Integrate Universal Search dialog in TopNav Add a Universal Search dialog to the top navigation and wire up CippUniversalSearchV2. This change introduces a button and Ctrl/Cmd+Shift+K keyboard shortcut to open the dialog, passes autoFocus to the search component, and resets the component via a key increment on close. CippUniversalSearchV2 was updated to support autoFocus, use placeholder instead of label, clear its input and call onConfirm when items are selected, and adjust internal layout alignment. The standalone Universal Search card was removed from the dashboard page. --- .../CippCards/CippUniversalSearchV2.jsx | 11 ++- src/layouts/top-nav.js | 76 ++++++++++++++++++- src/pages/dashboardv2/index.js | 8 -- 3 files changed, 83 insertions(+), 12 deletions(-) diff --git a/src/components/CippCards/CippUniversalSearchV2.jsx b/src/components/CippCards/CippUniversalSearchV2.jsx index 0f0d6e88ad8b..27495a690cb2 100644 --- a/src/components/CippCards/CippUniversalSearchV2.jsx +++ b/src/components/CippCards/CippUniversalSearchV2.jsx @@ -20,7 +20,7 @@ import { CippOffCanvas } from "../CippComponents/CippOffCanvas"; import { CippBitlockerKeySearch } from "../CippComponents/CippBitlockerKeySearch"; export const CippUniversalSearchV2 = React.forwardRef( - ({ onConfirm = () => {}, onChange = () => {}, maxResults = 10, value = "" }, ref) => { + ({ onConfirm = () => {}, onChange = () => {}, maxResults = 10, value = "", autoFocus = false }, ref) => { const [searchValue, setSearchValue] = useState(value); const [searchType, setSearchType] = useState("Users"); const [bitlockerLookupType, setBitlockerLookupType] = useState("keyId"); @@ -104,7 +104,9 @@ export const CippUniversalSearchV2 = React.forwardRef( `/identity/administration/groups/group?groupId=${itemData.id}&tenantFilter=${tenantDomain}`, ); } + setSearchValue(""); setShowDropdown(false); + onConfirm(match); }; const handleTypeChange = (type) => { @@ -124,7 +126,9 @@ export const CippUniversalSearchV2 = React.forwardRef( searchType: bitlockerLookupType, }); setBitlockerDrawerVisible(true); + setSearchValue(""); setShowDropdown(false); + onConfirm(match); }; const typeMenuActions = [ @@ -216,7 +220,7 @@ export const CippUniversalSearchV2 = React.forwardRef( return ( <> - + { const searchDialog = useDialog(); + const universalSearchDialog = useDialog(); const { onNavOpen } = props; const settings = useSettings(); const { bookmarks, setBookmarks } = useUserBookmarks(); @@ -64,6 +70,7 @@ export const TopNav = (props) => { const [animatingPair, setAnimatingPair] = useState(null); const [flashSort, setFlashSort] = useState(false); const [flashLock, setFlashLock] = useState(false); + const [universalSearchKey, setUniversalSearchKey] = useState(0); const itemRefs = useRef({}); const touchDragRef = useRef({ startIdx: null, overIdx: null }); const tenantSelectorRef = useRef(null); @@ -198,11 +205,23 @@ export const TopNav = (props) => { searchDialog.handleOpen(); }, [searchDialog.handleOpen]); + const openUniversalSearch = useCallback(() => { + universalSearchDialog.handleOpen(); + }, [universalSearchDialog.handleOpen]); + + const closeUniversalSearch = useCallback(() => { + universalSearchDialog.handleClose(); + setUniversalSearchKey((prev) => prev + 1); + }, [universalSearchDialog.handleClose]); + useEffect(() => { const handleKeyDown = (event) => { if ((event.metaKey || event.ctrlKey) && event.altKey && event.key === "k") { event.preventDefault(); tenantSelectorRef.current?.focus(); + } else if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.key === "K") { + event.preventDefault(); + openUniversalSearch(); } else if ((event.metaKey || event.ctrlKey) && event.key === "k") { event.preventDefault(); openSearch(); @@ -212,7 +231,7 @@ export const TopNav = (props) => { return () => { window.removeEventListener("keydown", handleKeyDown); }; - }, [openSearch]); + }, [openSearch, openUniversalSearch]); return ( { { )} + {!mdDown && ( + + + + )} {!mdDown && ( @@ -570,6 +618,32 @@ export const TopNav = (props) => { )} + + Universal Search + + + + + + { return ( - {/* Universal Search */} - - - - - - From fe399a6d65ad38aa8a13a8905e30e2ad369d13ff Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Wed, 25 Mar 2026 19:20:15 +0800 Subject: [PATCH 23/26] Add username space handling and replacement Allow username templates to control how spaces are handled when generating usernames. CippAddEditUser.generateUsername now accepts spaceHandling (keep|remove|replace) and spaceReplacement parameters and applies them before lowercasing. The user defaults page adds two new fields: usernameSpaceHandling (autoComplete with Keep/Remove/Replace) and usernameSpaceReplacement (textField), and includes these keys in the saved/default field lists. This enables admins to configure whether generated usernames keep, remove, or replace spaces (with a custom character). --- .../CippFormPages/CippAddEditUser.jsx | 31 ++++++++++++++++++- src/pages/tenant/manage/user-defaults.js | 22 +++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/components/CippFormPages/CippAddEditUser.jsx b/src/components/CippFormPages/CippAddEditUser.jsx index b479cacd197a..e24c944acd97 100644 --- a/src/components/CippFormPages/CippAddEditUser.jsx +++ b/src/components/CippFormPages/CippAddEditUser.jsx @@ -86,7 +86,13 @@ const CippAddEditUser = (props) => { const watcher = useWatch({ control: formControl.control }); // Helper function to generate username from template format - const generateUsername = (format, firstName, lastName) => { + const generateUsername = ( + format, + firstName, + lastName, + spaceHandling = "keep", + spaceReplacement = "", + ) => { if (!format || !firstName || !lastName) return ""; // Ensure format is a string @@ -108,6 +114,13 @@ const CippAddEditUser = (props) => { username = username.replace(/%FirstName%/gi, firstName); username = username.replace(/%LastName%/gi, lastName); + // Apply optional space handling + if (spaceHandling === "remove") { + username = username.replace(/\s+/g, ""); + } else if (spaceHandling === "replace") { + username = username.replace(/\s+/g, spaceReplacement || ""); + } + // Convert to lowercase return username.toLowerCase(); }; @@ -152,10 +165,26 @@ const CippAddEditUser = (props) => { : selectedTemplate.usernameFormat?.value || selectedTemplate.usernameFormat?.label; if (formatString) { + const spaceHandling = + typeof selectedTemplate.usernameSpaceHandling === "string" + ? selectedTemplate.usernameSpaceHandling + : selectedTemplate.usernameSpaceHandling?.value || + selectedTemplate.usernameSpaceHandling?.label || + "keep"; + + const spaceReplacement = + typeof selectedTemplate.usernameSpaceReplacement === "string" + ? selectedTemplate.usernameSpaceReplacement + : selectedTemplate.usernameSpaceReplacement?.value || + selectedTemplate.usernameSpaceReplacement?.label || + ""; + const generatedUsername = generateUsername( formatString, watcher.givenName, watcher.surname, + spaceHandling, + spaceReplacement, ); if (generatedUsername) { formControl.setValue("username", generatedUsername, { shouldDirty: true }); diff --git a/src/pages/tenant/manage/user-defaults.js b/src/pages/tenant/manage/user-defaults.js index 08979dd2ed50..62395554d31d 100644 --- a/src/pages/tenant/manage/user-defaults.js +++ b/src/pages/tenant/manage/user-defaults.js @@ -56,6 +56,25 @@ const Page = () => { multiple: false, creatable: true, }, + { + label: "Username Space Handling", + name: "usernameSpaceHandling", + type: "autoComplete", + options: [ + { label: "Keep spaces", value: "keep" }, + { label: "Remove spaces", value: "remove" }, + { label: "Replace spaces", value: "replace" }, + ], + helperText: "How spaces in the generated username should be handled.", + multiple: false, + creatable: false, + }, + { + label: "Username Space Replacement", + name: "usernameSpaceReplacement", + type: "textField", + helperText: "Used when space handling is set to Replace spaces (example: _ or .).", + }, { label: "Primary Domain", name: "primDomain", @@ -184,6 +203,8 @@ const Page = () => { "defaultForTenant", "displayName", "usernameFormat", + "usernameSpaceHandling", + "usernameSpaceReplacement", "primDomain", "usageLocation", "licenses", @@ -222,6 +243,7 @@ const Page = () => { "defaultForTenant", "displayName", "usernameFormat", + "usernameSpaceHandling", "usageLocation", "department", ]} From 016ae9ba42f2672966fb1b2e748724b86068b6d8 Mon Sep 17 00:00:00 2001 From: Roel van der Wegen Date: Wed, 25 Mar 2026 13:12:28 +0100 Subject: [PATCH 24/26] Replace ListOrg calls with ListGraphRequest --- src/components/ExecutiveReportButton.js | 18 ++++++++++-------- src/pages/dashboardv1.js | 22 ++++++++++++---------- src/pages/dashboardv2/index.js | 12 +++++++----- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/components/ExecutiveReportButton.js b/src/components/ExecutiveReportButton.js index e25d22d56b11..0e8e67064886 100644 --- a/src/components/ExecutiveReportButton.js +++ b/src/components/ExecutiveReportButton.js @@ -2709,12 +2709,14 @@ export const ExecutiveReportButton = (props) => { // Fetch organization data - only when preview is open const organization = ApiGetCall({ - url: "/api/ListOrg", - queryKey: `${settings.currentTenant}-ListOrg-report`, - data: { tenantFilter: settings.currentTenant }, + url: "/api/ListGraphRequest", + queryKey: `${settings.currentTenant}-ListGraphRequest-organization-report`, + data: { tenantFilter: settings.currentTenant, Endpoint: "organization" }, waiting: previewOpen, }); + const organizationRecord = organization.data?.Results?.[0]; + // Fetch user counts - only when preview is open const dashboard = ApiGetCall({ url: "/api/ListuserCounts", @@ -2812,8 +2814,8 @@ export const ExecutiveReportButton = (props) => { // Button is always available now since we don't need to wait for data const shouldShowButton = true; - const tenantName = organization.data?.displayName || "Tenant"; - const tenantId = organization.data?.id; + const tenantName = organizationRecord?.displayName || "Tenant"; + const tenantId = organizationRecord?.id; const userStats = { licensedUsers: dashboard.data?.LicUsers || 0, unlicensedUsers: @@ -2855,7 +2857,7 @@ export const ExecutiveReportButton = (props) => { tenantId={tenantId} userStats={userStats} standardsData={driftComplianceData.data} - organizationData={organization.data} + organizationData={organizationRecord} brandingSettings={brandingSettings} secureScoreData={secureScore.isSuccess ? secureScore : null} licensingData={licenseData.isSuccess ? licenseData?.data : null} @@ -2889,7 +2891,7 @@ export const ExecutiveReportButton = (props) => { tenantName, tenantId, userStats, - organization.data, + organizationRecord, dashboard.data, brandingSettings, secureScore?.isSuccess, @@ -3205,7 +3207,7 @@ export const ExecutiveReportButton = (props) => { tenantId={tenantId} userStats={userStats} standardsData={driftComplianceData.data} - organizationData={organization.data} + organizationData={organizationRecord} brandingSettings={brandingSettings} secureScoreData={secureScore.isSuccess ? secureScore : null} licensingData={licenseData.isSuccess ? licenseData?.data : null} diff --git a/src/pages/dashboardv1.js b/src/pages/dashboardv1.js index a2054d962e5e..8e45642518cd 100644 --- a/src/pages/dashboardv1.js +++ b/src/pages/dashboardv1.js @@ -21,11 +21,13 @@ const Page = () => { const [domainVisible, setDomainVisible] = useState(false); const organization = ApiGetCall({ - url: "/api/ListOrg", - queryKey: `${currentTenant}-ListOrg`, - data: { tenantFilter: currentTenant }, + url: "/api/ListGraphRequest", + queryKey: `${currentTenant}-ListGraphRequest-organization`, + data: { tenantFilter: currentTenant, Endpoint: "organization" }, }); + const organizationRecord = organization.data?.Results?.[0]; + const dashboard = ApiGetCall({ url: "/api/ListuserCounts", data: { tenantFilter: currentTenant }, @@ -68,12 +70,12 @@ const Page = () => { // Top bar data const tenantInfo = [ - { name: "Tenant Name", data: organization.data?.displayName }, + { name: "Tenant Name", data: organizationRecord?.displayName }, { name: "Tenant ID", data: ( <> - + ), }, @@ -83,7 +85,7 @@ const Page = () => { <> domain.isDefault === true)?.name + organizationRecord?.verifiedDomains?.find((domain) => domain.isDefault === true)?.name } type="chip" /> @@ -92,7 +94,7 @@ const Page = () => { }, { name: "AD Sync Enabled", - data: getCippFormatting(organization.data?.onPremisesSyncEnabled, "dirsync"), + data: getCippFormatting(organizationRecord?.onPremisesSyncEnabled, "dirsync"), }, ]; @@ -369,14 +371,14 @@ const Page = () => { showDivider={false} copyItems={true} isFetching={organization.isFetching} - propertyItems={organization.data?.verifiedDomains + propertyItems={organizationRecord?.verifiedDomains ?.slice(0, domainVisible ? undefined : 3) .map((domain, idx) => ({ label: "", value: domain.name, }))} actionButton={ - organization.data?.verifiedDomains?.length > 3 && ( + organizationRecord?.verifiedDomains?.length > 3 && ( @@ -417,7 +419,7 @@ const Page = () => { propertyItems={[ { label: "Services", - value: organization.data?.assignedPlans + value: organizationRecord?.assignedPlans ?.filter( (plan) => plan.capabilityStatus === "Enabled" && diff --git a/src/pages/dashboardv2/index.js b/src/pages/dashboardv2/index.js index fcb983b4e36d..c5c4283ce475 100644 --- a/src/pages/dashboardv2/index.js +++ b/src/pages/dashboardv2/index.js @@ -87,11 +87,13 @@ const Page = () => { }, [reportIdValue]); const organization = ApiGetCall({ - url: "/api/ListOrg", - queryKey: `${currentTenant}-ListOrg`, - data: { tenantFilter: currentTenant }, + url: "/api/ListGraphRequest", + queryKey: `${currentTenant}-ListGraphRequest-organization`, + data: { tenantFilter: currentTenant, Endpoint: "organization" }, }); + const organizationRecord = organization.data?.Results?.[0]; + const testsApi = ApiGetCall({ url: "/api/ListTests", data: { tenantFilter: currentTenant, reportId: selectedReport }, @@ -112,7 +114,7 @@ const Page = () => { testsApi.isSuccess && testsApi.data?.TenantCounts ? { ExecutedAt: testsApi.data?.LatestReportTimeStamp || null, - TenantName: organization.data?.displayName || "", + TenantName: organizationRecord?.displayName || "", Domain: currentTenant || "", TestResultSummary: { IdentityPassed: testsApi.data.TestCounts?.Identity?.Passed || 0, @@ -326,7 +328,7 @@ const Page = () => { {/* Column 1: Tenant Information */} - + {/* Column 2: Tenant Metrics - 2x3 Grid */} From 6e6a1dc2ee02ec4c938c173363cb42faccf47577 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Wed, 25 Mar 2026 20:16:42 +0800 Subject: [PATCH 25/26] Update top-nav.js --- src/layouts/top-nav.js | 38 ++++++++++---------------------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/src/layouts/top-nav.js b/src/layouts/top-nav.js index f8b47ed7bd44..917fffbbe275 100644 --- a/src/layouts/top-nav.js +++ b/src/layouts/top-nav.js @@ -5,6 +5,7 @@ import Bars3Icon from "@heroicons/react/24/outline/Bars3Icon"; import MoonIcon from "@heroicons/react/24/outline/MoonIcon"; import SunIcon from "@heroicons/react/24/outline/SunIcon"; import BookmarkIcon from "@mui/icons-material/Bookmark"; +import TravelExploreIcon from "@mui/icons-material/TravelExplore"; import DragIndicatorIcon from "@mui/icons-material/DragIndicator"; import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; @@ -16,7 +17,6 @@ import LockIcon from "@mui/icons-material/Lock"; import LockOpenIcon from "@mui/icons-material/LockOpen"; import { Box, - Button, Divider, Dialog, DialogContent, @@ -30,6 +30,7 @@ import { ListItem, ListItemText, Typography, + TravelExplore, } from "@mui/material"; import { Logo } from "../components/logo"; import { useSettings } from "../hooks/use-settings"; @@ -289,35 +290,16 @@ export const TopNav = (props) => { )} - {!mdDown && ( - - - - )} - + + + )} {!mdDown && ( From 95083b65702f3d19b4d35e2719f25624fcede52b Mon Sep 17 00:00:00 2001 From: Roel van der Wegen Date: Wed, 25 Mar 2026 13:27:49 +0100 Subject: [PATCH 26/26] Replace ListDevices with ListGraphRequest --- src/components/ExecutiveReportButton.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/ExecutiveReportButton.js b/src/components/ExecutiveReportButton.js index 0e8e67064886..64d2609015af 100644 --- a/src/components/ExecutiveReportButton.js +++ b/src/components/ExecutiveReportButton.js @@ -2740,11 +2740,12 @@ export const ExecutiveReportButton = (props) => { // Get real device data - only when preview is open const deviceData = ApiGetCall({ - url: "/api/ListDevices", + url: "/api/ListGraphRequest", data: { tenantFilter: settings.currentTenant, + Endpoint: "deviceManagement/managedDevices", }, - queryKey: `devices-report-${settings.currentTenant}`, + queryKey: `ListGraphRequest-devices-report-${settings.currentTenant}`, waiting: previewOpen, }); @@ -2861,7 +2862,7 @@ export const ExecutiveReportButton = (props) => { brandingSettings={brandingSettings} secureScoreData={secureScore.isSuccess ? secureScore : null} licensingData={licenseData.isSuccess ? licenseData?.data : null} - deviceData={deviceData.isSuccess ? deviceData?.data : null} + deviceData={deviceData.isSuccess ? deviceData?.data?.Results : null} conditionalAccessData={ conditionalAccessData.isSuccess ? conditionalAccessData?.data?.Results : null } @@ -3211,7 +3212,7 @@ export const ExecutiveReportButton = (props) => { brandingSettings={brandingSettings} secureScoreData={secureScore.isSuccess ? secureScore : null} licensingData={licenseData.isSuccess ? licenseData?.data : null} - deviceData={deviceData.isSuccess ? deviceData?.data : null} + deviceData={deviceData.isSuccess ? deviceData?.data?.Results : null} conditionalAccessData={ conditionalAccessData.isSuccess ? conditionalAccessData?.data?.Results : null }