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 && ( 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} /> - - - - + + + + + + + + + + + + + + { 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/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"] }, { diff --git a/src/layouts/config.js b/src/layouts/config.js index 4f8be5c348c7..bcd5bb786896 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.*"], + }, ], }, { @@ -903,7 +908,6 @@ export const nativeMenuItems = [ path: "/cipp/scheduler", roles: ["editor", "admin", "superadmin"], permissions: ["CIPP.Scheduler.*"], - scope: "global", }, ], }, 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` 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", 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]); diff --git a/src/pages/identity/administration/jit-admin/add.jsx b/src/pages/identity/administration/jit-admin/add.jsx index 3e7f80d73559..16842bd313f6 100644 --- a/src/pages/identity/administration/jit-admin/add.jsx +++ b/src/pages/identity/administration/jit-admin/add.jsx @@ -29,6 +29,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"; const useRoles = useWatch({ control: formControl.control, name: "useRoles" }); const useGroups = useWatch({ control: formControl.control, name: "useGroups" }); @@ -473,6 +487,11 @@ const Page = () => { name="UseTAP" formControl={formControl} /> + {useTAP && tapPolicy.isSuccess && !tapEnabled && ( + + TAP is not enabled in this tenant. TAP generation will fail. + + )} { enableOOO: false, oooInternalMessage: null, oooExternalMessage: null, + oooCreateOOFEvent: false, + oooOOFEventSubject: "", + oooAutoDeclineFutureRequests: false, + oooDeclineEvents: false, + oooDeclineMeetingMessage: "", startDate: null, endDate: null, postExecution: [], 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 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 + + } + > + + + + + + + + + + + +