gkarr 23242 fe (#47754)

This commit is contained in:
George Karr
2026-06-29 11:55:24 -05:00
committed by GitHub
parent 08d725889b
commit 065a52cb9b
19 changed files with 333 additions and 60 deletions
@@ -116,9 +116,9 @@ describe("AddHostsModal", () => {
);
await user.click(screen.getByRole("tab", { name: "iOS & iPadOS" }));
expect(
screen.queryByText(/Send this to your end users:/i)
).toBeInTheDocument();
expect(screen.queryByText(/Enrollment instructions:/i)).toBeInTheDocument();
expect(screen.getByLabelText("Personal (BYOD)")).toBeInTheDocument();
expect(screen.getByLabelText("Company-owned")).toBeInTheDocument();
});
it("renders enroll url input for android if android mdm is enabled", async () => {
@@ -66,7 +66,7 @@ const AndroidPanel = ({ enrollSecret }: IAndroidPanelProps) => {
<Radio
name="enrollmentType"
id="workProfile"
label="Work profile"
label="Personal (BYOD)"
value="workProfile"
checked={enrollmentType === "workProfile"}
onChange={() => setEnrollmentType("workProfile")}
@@ -74,7 +74,7 @@ const AndroidPanel = ({ enrollSecret }: IAndroidPanelProps) => {
<Radio
name="enrollmentType"
id="fullyManaged"
label="Fully-managed (no work profile)"
label="Company-owned (fully-managed)"
value="fullyManaged"
checked={enrollmentType === "fullyManaged"}
onChange={() => setEnrollmentType("fullyManaged")}
@@ -1,16 +1,14 @@
import React, { useContext } from "react";
import React, { useContext, useState } from "react";
import CustomLink from "components/CustomLink";
import PATHS from "router/paths";
import { AppContext } from "context/app";
import { getPathWithQueryParams } from "utilities/url";
import InputField from "components/forms/fields/InputField";
import Radio from "components/forms/fields/Radio";
const generateUrl = (serverUrl: string, enrollSecret: string) => {
return `${serverUrl}/enroll?enroll_secret=${encodeURIComponent(
enrollSecret
)}`;
};
type EnrollmentType = "personal" | "companyOwned";
const baseClass = "ios-ipados-panel";
@@ -21,6 +19,11 @@ interface IosIpadosPanelProps {
const IosIpadosPanel = ({ enrollSecret }: IosIpadosPanelProps) => {
const { config, isMacMdmEnabledAndConfigured } = useContext(AppContext);
// Default to "Personal (BYOD)" per #23242 design.
const [enrollmentType, setEnrollmentType] = useState<EnrollmentType>(
"personal"
);
const helpText =
"When the end user navigates to this URL, the enrollment profile " +
"will download in their browser. End users will have to install the profile " +
@@ -40,19 +43,45 @@ const IosIpadosPanel = ({ enrollSecret }: IosIpadosPanelProps) => {
);
}
const url = generateUrl(config.server_settings.server_url, enrollSecret);
const url = getPathWithQueryParams(
`${config.server_settings.server_url}/enroll`,
{
enroll_secret: enrollSecret,
byod: enrollmentType === "personal" ? "true" : undefined,
}
);
return (
<div className={baseClass}>
<InputField
label="Send this to your end users:"
enableCopy
readOnly
inputWrapperClass={`${baseClass}__enroll-link`}
name="enroll-link"
value={url}
helpText={helpText}
/>
<form>
<fieldset className="form-field">
<Radio
name="iosIpadosEnrollmentType"
id="iosIpadosPersonal"
label="Personal (BYOD)"
value="personal"
checked={enrollmentType === "personal"}
onChange={() => setEnrollmentType("personal")}
/>
<Radio
name="iosIpadosEnrollmentType"
id="iosIpadosCompanyOwned"
label="Company-owned"
value="companyOwned"
checked={enrollmentType === "companyOwned"}
onChange={() => setEnrollmentType("companyOwned")}
/>
</fieldset>
<InputField
label="Enrollment instructions:"
enableCopy
readOnly
inputWrapperClass={`${baseClass}__enroll-link`}
name="enroll-link"
value={url}
helpText={helpText}
/>
</form>
</div>
);
};
@@ -33,9 +33,9 @@ describe("HostMdmStatusCell", () => {
expect(screen.getByText("On (company-owned)")).toBeInTheDocument();
});
it("renders 'On (BYOD)' for iOS hosts with personal enrollment", () => {
renderCell("ios", "On (personal)");
expect(screen.getByText("On (BYOD)")).toBeInTheDocument();
it("renders 'On (manual - personal)' for iOS hosts with personal enrollment", () => {
renderCell("ios", "On (manual - personal)");
expect(screen.getByText("On (manual - personal)")).toBeInTheDocument();
});
it("renders 'Pending' for macOS hosts with pending enrollment", () => {
@@ -44,8 +44,8 @@ describe("HostMdmStatusCell", () => {
});
it("renders the MDM status for Android hosts", () => {
renderCell("android", "On (personal)");
expect(screen.getByText("On (BYOD)")).toBeInTheDocument();
renderCell("android", "On (manual - personal)");
expect(screen.getByText("On (manual - personal)")).toBeInTheDocument();
});
it("renders the MDM status for Windows hosts", () => {
+9
View File
@@ -179,6 +179,15 @@ export interface IHostMdmData {
device_status: HostMdmDeviceStatus;
pending_action: HostMdmPendingAction;
connected_to_fleet?: boolean;
/**
* wipe/lock/clear_passcode_allowed indicate whether the corresponding MDM
* commands are permitted for this host based on the AccessRights delivered
* in the host's manual (SCEP/ACME) enrollment profile. They are only
* populated for the host-details endpoint; absent on list-hosts payloads.
*/
wipe_allowed?: boolean;
lock_allowed?: boolean;
clear_passcode_allowed?: boolean;
}
export interface IHostMaintenanceWindow {
+9 -7
View File
@@ -61,7 +61,7 @@ export const getMdmServerUrl = ({ server_url }: IConfigServerSettings) => {
export type MdmEnrollmentStatus =
| "On (manual)"
| "On (automatic)"
| "On (personal)"
| "On (manual - personal)"
| "On (company-owned)"
| "Off"
| "Pending";
@@ -96,8 +96,8 @@ export const MDM_ENROLLMENT_STATUS_UI_MAP: Record<
displayName: "On (company-owned)",
filterValue: "automatic",
},
"On (personal)": {
displayName: "On (BYOD)",
"On (manual - personal)": {
displayName: "On (manual - personal)",
filterValue: "personal",
},
Off: {
@@ -302,7 +302,7 @@ export const isEnrolledInMdm = (
return [
"On (automatic)",
"On (manual)",
"On (personal)",
"On (manual - personal)",
"On (company-owned)",
].includes(hostMdmEnrollmentStatus);
};
@@ -314,11 +314,13 @@ export const isBYODManualEnrollment = (
};
/** This checks if the device is enrolled via an Apple ID user enrollment.
* We refer to that as "account driven user enrollment" */
* We refer to that as "account driven user enrollment". Note that this same
* status now also covers manual BYOD enrollments (Apple) and Android BYO
* (work profile); see issue #23242. */
export const isBYODAccountDrivenUserEnrollment = (
enrollmentStatus: MdmEnrollmentStatus | null
) => {
return enrollmentStatus === "On (personal)";
return enrollmentStatus === "On (manual - personal)";
};
/** This check is the device is enrolled via Automated Device Enrollment (ADE, also known as DEP)
@@ -335,7 +337,7 @@ export const isAutomaticDeviceEnrollment = (
/** Android BYO (work profile, personally-owned) enrollment. */
export const isAndroidBYO = (enrollmentStatus: MdmEnrollmentStatus | null) => {
return enrollmentStatus === "On (personal)";
return enrollmentStatus === "On (manual - personal)";
};
/** Android COBO (company-owned, fully managed) enrollment. */
@@ -485,7 +485,7 @@ const DashboardPage = ({ router, location }: IDashboardProps): JSX.Element => {
hosts: enrolled_automated_hosts_count,
},
{
status: "On (personal)",
status: "On (manual - personal)",
hosts: enrolled_personal_hosts_count,
},
{ status: "Off", hosts: unenrolled_hosts_count },
@@ -43,7 +43,7 @@ describe("MDM Card", () => {
mdmStatusData={[
{ status: "On (automatic)", hosts: 10 },
{ status: "On (manual)", hosts: 5 },
{ status: "On (personal)", hosts: 3 },
{ status: "On (manual - personal)", hosts: 3 },
{ status: "Off", hosts: 1 },
{ status: "Pending", hosts: 3 },
]}
@@ -65,7 +65,7 @@ describe("MDM Card", () => {
).toBeInTheDocument();
expect(
screen.getByRole("row", {
name: /On \(BYOD\)(.*?)3 view all hosts/i,
name: /On \(manual - personal\)(.*?)3 view all hosts/i,
})
).toBeInTheDocument();
@@ -1557,7 +1557,7 @@ describe("Host Actions Dropdown", () => {
onSelect={noop}
hostStatus="online"
hostPlatform="android"
hostMdmEnrollmentStatus="On (personal)"
hostMdmEnrollmentStatus="On (manual - personal)"
isConnectedToFleetMdm
hostMdmDeviceStatus="unlocked"
hostScriptsEnabled={false}
@@ -1785,7 +1785,7 @@ describe("Host Actions Dropdown", () => {
);
});
describe("personally enrolled hosts (e.g. enrollment status => On (personal)", () => {
describe("personally enrolled hosts (e.g. enrollment status => On (manual - personal))", () => {
it("render only the Transfer and Delete options for personally enrolled ios host", async () => {
const render = createCustomRenderer({
context: {
@@ -1803,7 +1803,7 @@ describe("Host Actions Dropdown", () => {
hostTeamId={null}
onSelect={noop}
hostStatus="online"
hostMdmEnrollmentStatus={"On (personal)"}
hostMdmEnrollmentStatus={"On (manual - personal)"}
hostMdmDeviceStatus="unlocked"
isConnectedToFleetMdm
hostScriptsEnabled
@@ -1843,7 +1843,7 @@ describe("Host Actions Dropdown", () => {
hostTeamId={null}
onSelect={noop}
hostStatus="online"
hostMdmEnrollmentStatus={"On (personal)"}
hostMdmEnrollmentStatus={"On (manual - personal)"}
isConnectedToFleetMdm
hostMdmDeviceStatus="unlocked"
hostScriptsEnabled
@@ -29,6 +29,14 @@ interface IHostActionsDropdownProps {
isManagedLocalAccountEnabled?: boolean;
managedAccountStatus?: string | null;
managedAccountPasswordAvailable?: boolean;
/**
* BYOD permission gates from the host MDM payload. Undefined when the host's
* stored AccessRights are not known (non-Apple-MDM or pre-#23242 hosts);
* treat undefined as "allowed" so the dropdown matches today's behavior.
*/
wipeAllowed?: boolean;
lockAllowed?: boolean;
clearPasscodeAllowed?: boolean;
}
const HostActionsDropdown = ({
@@ -48,6 +56,9 @@ const HostActionsDropdown = ({
isManagedLocalAccountEnabled = false,
managedAccountStatus,
managedAccountPasswordAvailable = false,
wipeAllowed,
lockAllowed,
clearPasscodeAllowed,
}: IHostActionsDropdownProps) => {
const {
isPremiumTier = false,
@@ -105,6 +116,9 @@ const HostActionsDropdown = ({
isManagedLocalAccountEnabled,
managedAccountStatus,
managedAccountPasswordAvailable,
wipeAllowed,
lockAllowed,
clearPasscodeAllowed,
});
// No options to render. Exit early
@@ -118,6 +118,14 @@ interface IHostActionConfigOptions {
isManagedLocalAccountEnabled: boolean;
managedAccountStatus: string | null | undefined;
managedAccountPasswordAvailable: boolean;
/**
* BYOD permission gates (issue #23242). Undefined when the host's stored
* AccessRights are not yet known; treat undefined as "allowed" to preserve
* pre-feature behavior.
*/
wipeAllowed?: boolean;
lockAllowed?: boolean;
clearPasscodeAllowed?: boolean;
}
const canTransferTeam = (config: IHostActionConfigOptions) => {
@@ -540,12 +548,47 @@ const removeUnavailableOptions = (
return options;
};
// Tooltip copy for the BYOD-disabled state per issue #23242. Shown when the
// host's stored AccessRights bitmask omits the relevant bit.
const BYOD_DISABLED_TOOLTIPS: Record<string, JSX.Element> = {
wipe: (
<>
Wipe permissions
<br />
are disabled for this host.
</>
),
lock: (
<>
Lock permissions
<br />
are disabled for this host.
</>
),
clearPasscode: (
<>
Clear passcode permissions
<br />
are disabled for this host.
</>
),
};
// Available tooltips for disabled options
export const getDropdownOptionTooltipContent = (
value: string | number,
isHostOnline?: boolean,
scriptsGloballyDisabled?: boolean
scriptsGloballyDisabled?: boolean,
byodDisabled?: boolean
) => {
if (
byodDisabled &&
typeof value === "string" &&
BYOD_DISABLED_TOOLTIPS[value]
) {
return BYOD_DISABLED_TOOLTIPS[value];
}
if (value === "runScript" && scriptsGloballyDisabled) {
return <>Running scripts is disabled in organization settings.</>;
}
@@ -598,6 +641,9 @@ const modifyOptions = (
recoveryLockPasswordAvailable,
managedAccountStatus,
managedAccountPasswordAvailable,
wipeAllowed,
lockAllowed,
clearPasscodeAllowed,
}: IHostActionConfigOptions
) => {
const disableOptions = (optionsToDisable: IDropdownOption[]) => {
@@ -611,6 +657,33 @@ const modifyOptions = (
});
};
// BYOD-disabled options get a different tooltip. Each action maps to its
// own *Allowed flag; only treat the boolean false as disabled (undefined =
// unknown rights, leave the action enabled).
const byodDisableOptions = (optionsToDisable: IDropdownOption[]) => {
optionsToDisable.forEach((option) => {
option.disabled = true;
option.tooltipContent = getDropdownOptionTooltipContent(
option.value,
isHostOnline,
scriptsGloballyDisabled,
true
);
});
};
if (wipeAllowed === false) {
byodDisableOptions(options.filter((option) => option.value === "wipe"));
}
if (lockAllowed === false) {
byodDisableOptions(options.filter((option) => option.value === "lock"));
}
if (clearPasscodeAllowed === false) {
byodDisableOptions(
options.filter((option) => option.value === "clearPasscode")
);
}
let optionsToDisable: IDropdownOption[] = [];
// When the host is offline, always disable Query, but allow Unenroll for iOS/iPadOS and Android.
if (!isHostOnline) {
@@ -1088,6 +1088,9 @@ const HostDetailsPage = ({
host.mdm.os_settings?.managed_local_account?.password_available ??
false
}
wipeAllowed={host.mdm.wipe_allowed}
lockAllowed={host.mdm.lock_allowed}
clearPasscodeAllowed={host.mdm.clear_passcode_allowed}
/>
);
};
@@ -33,7 +33,7 @@ const ClearPasscodeModal = ({
const isAndroidHost = isAndroid(hostPlatform);
const isAndroidBYO =
isAndroidHost && hostMdmEnrollmentStatus === "On (personal)";
isAndroidHost && hostMdmEnrollmentStatus === "On (manual - personal)";
const onClearPasscode = async () => {
setIsClearingPasscode(true);
@@ -304,7 +304,7 @@ describe("SelfService", () => {
<SelfService
{...TEST_PROPS}
isMobileView
mdmEnrollmentStatus="On (personal)"
mdmEnrollmentStatus="On (manual - personal)"
/>
);
@@ -481,10 +481,10 @@ describe("getUiStatus", () => {
});
describe("getSoftwareSubheader", () => {
test("iOS device, MDM status 'On (personal)', my device page", () => {
test("iOS device, MDM status 'On (manual - personal)', my device page", () => {
const result = getSoftwareSubheader({
platform: "ios",
hostMdmEnrollmentStatus: "On (personal)",
hostMdmEnrollmentStatus: "On (manual - personal)",
isMyDevicePage: true,
});
expect(result).toBe(
@@ -492,10 +492,10 @@ describe("getSoftwareSubheader", () => {
);
});
test("iOS device, MDM status 'On (personal)', NOT my device page", () => {
test("iOS device, MDM status 'On (manual - personal)', NOT my device page", () => {
const result = getSoftwareSubheader({
platform: "ios",
hostMdmEnrollmentStatus: "On (personal)",
hostMdmEnrollmentStatus: "On (manual - personal)",
isMyDevicePage: false,
});
expect(result).toBe(
@@ -488,7 +488,7 @@ export const getSoftwareSubheader = ({
isMyDevicePage,
}: IGetSoftwareSubheader): string => {
if (isIPadOrIPhone(platform)) {
if (hostMdmEnrollmentStatus === "On (personal)") {
if (hostMdmEnrollmentStatus === "On (manual - personal)") {
return isMyDevicePage
? "Software installed on your work profile (Managed Apple Account)."
: "Software installed on work profile (Managed Apple Account).";
@@ -34,7 +34,7 @@ describe("Vitals Card component", () => {
hardware_serial: "",
uuid: "enrollment-id-12345",
mdm: createMockHostMdmData({
enrollment_status: "On (personal)",
enrollment_status: "On (manual - personal)",
}),
});
@@ -56,7 +56,7 @@ describe("Vitals Card component", () => {
hardware_serial: "",
uuid: "enrollment-id-12345",
mdm: createMockHostMdmData({
enrollment_status: "On (personal)",
enrollment_status: "On (manual - personal)",
}),
});
@@ -78,7 +78,7 @@ describe("Vitals Card component", () => {
hardware_serial: "",
uuid: "enrollment-id-12345",
mdm: createMockHostMdmData({
enrollment_status: "On (personal)",
enrollment_status: "On (manual - personal)",
}),
});
@@ -146,7 +146,7 @@ describe("Vitals Card component", () => {
public_ip: "203.0.113.1",
uuid: "enrollment-id-12345",
mdm: createMockHostMdmData({
enrollment_status: "On (personal)",
enrollment_status: "On (manual - personal)",
}),
});
+146 -2
View File
@@ -197,6 +197,47 @@
text-align: center;
}
.byod-tabs {
display: flex;
gap: 8px;
margin-bottom: 16px;
}
.byod-tab {
cursor: pointer;
padding: 6px 12px;
border-radius: 6px;
font-size: 14px;
line-height: 21px;
font-weight: 400;
color: #515774;
background: transparent;
border: none;
font-family: inherit;
}
.byod-tab.active {
background-color: #f1f0ff;
color: #25234a;
font-weight: 600;
}
.byod-info-banner {
display: flex;
align-items: flex-start;
gap: 16px;
padding: 16px;
background-color: #f9fafc;
border-radius: 8px;
font-size: 14px;
margin-bottom: 24px;
}
.byod-info-banner-icon {
flex-shrink: 0;
margin-top: 2px;
}
@media screen and (max-width: 1344px) and (pointer: coarse) {
.device-instructions-content {
gap: 24px;
@@ -320,6 +361,24 @@
<span data-attribute="dynamic-device-type">iPhone or iPad</span> to
Fleet
</h1>
<div>
<div class="byod-tabs" role="tablist">
<button type="button" class="byod-tab byod-tab--personal" role="tab" aria-selected="false" data-byod="personal">Personal (BYOD)</button>
<button type="button" class="byod-tab byod-tab--company active" role="tab" aria-selected="true" data-byod="company">Company-owned</button>
</div>
<div class="byod-info-banner">
<span class="byod-info-banner-icon" aria-hidden="true">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="8" cy="8" r="7.5" stroke="#515774"/>
<path d="M8 7v4M8 5v.5" stroke="#515774" stroke-linecap="round" stroke-width="1.5"/>
</svg>
</span>
<span class="byod-info-banner-text">
<span data-byod-content="personal" hidden>Your organization can only remotely remove work data and settings. They cannot wipe your device or lock you out.</span>
<span data-byod-content="company">Your organization can see and delete all device information.</span>
</span>
</div>
</div>
<ol>
<li>
<p>
@@ -329,7 +388,7 @@
prompted, tap <b>Allow</b>.
</span>
</p>
<a class="download-link" href="{{.EnrollURL}}">Download</a>
<a class="download-link" href="{{.EnrollURL}}" data-base-href="{{.EnrollURL}}">Download</a>
</li>
<li>
<p>
@@ -375,6 +434,24 @@
<div class="content-with-sidebar">
<section class="device-instructions-content">
<h1>How to turn on MDM on your Mac</h1>
<div>
<div class="byod-tabs" role="tablist">
<button type="button" class="byod-tab byod-tab--personal" role="tab" aria-selected="false" data-byod="personal">Personal (BYOD)</button>
<button type="button" class="byod-tab byod-tab--company active" role="tab" aria-selected="true" data-byod="company">Company-owned</button>
</div>
<div class="byod-info-banner">
<span class="byod-info-banner-icon" aria-hidden="true">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="8" cy="8" r="7.5" stroke="#515774"/>
<path d="M8 7v4M8 5v.5" stroke="#515774" stroke-linecap="round" stroke-width="1.5"/>
</svg>
</span>
<span class="byod-info-banner-text">
<span data-byod-content="personal" hidden>Your organization can only remotely remove work data and settings. They cannot wipe your device or lock you out.</span>
<span data-byod-content="company">Your organization can see and delete all device information.</span>
</span>
</div>
</div>
<ol>
<li>
<p>
@@ -384,7 +461,7 @@
You'll see a warning, which is expected.
</span>
</p>
<a class="download-link" href="{{.EnrollURL}}">Download</a>
<a class="download-link" href="{{.EnrollURL}}" data-base-href="{{.EnrollURL}}">Download</a>
</li>
<li>
<p>
@@ -628,6 +705,71 @@
}
};
// Wires up the Personal (BYOD) / Company-owned tabs that appear on the
// macOS, iOS, and iPadOS instruction screens. The active tab determines
// both the visible info-banner copy and whether the Download link's URL
// includes &byod=true (consumed by the OTA endpoint to strip lock/erase
// rights from the enrollment profile for personal devices).
//
// The default selection follows the byod URL query param (set by the
// Add hosts modal when an admin chooses "Personal (BYOD)"); only an
// explicit byod=true selects personal — everything else (absent param,
// byod=false, byod=0) defaults to company-owned.
const wireBYODTabs = () => {
const tabs = document.querySelectorAll(".byod-tab");
if (tabs.length === 0) {
return;
}
const downloadLink = document.querySelector(".download-link");
if (!downloadLink) {
return;
}
const baseHref = downloadLink.getAttribute("data-base-href") || downloadLink.getAttribute("href");
const params = new URLSearchParams(window.location.search);
const initial =
params.get("byod") === "true" || params.get("byod") === "1"
? "personal"
: "company";
const setSelection = (selection) => {
tabs.forEach((tab) => {
const isActive = tab.getAttribute("data-byod") === selection;
tab.classList.toggle("active", isActive);
tab.setAttribute("aria-selected", isActive ? "true" : "false");
});
document
.querySelectorAll("[data-byod-content]")
.forEach((el) => {
el.hidden = el.getAttribute("data-byod-content") !== selection;
});
// Append byod=true to the OTA download URL for BYOD; remove it for
// company-owned. Routing the DOM-sourced href through the URL parser
// keeps existing query params intact, resolves relative URLs against
// the origin, and avoids reinterpreting that value unsafely.
try {
const downloadUrl = new URL(baseHref, window.location.origin);
if (selection === "personal") {
downloadUrl.searchParams.set("byod", "true");
} else {
downloadUrl.searchParams.delete("byod");
}
downloadLink.setAttribute("href", downloadUrl.toString());
} catch (e) {
// baseHref came from the DOM; if it can't be parsed as a URL, leave
// the server-rendered href untouched rather than reinterpreting it.
}
};
tabs.forEach((tab) => {
tab.addEventListener("click", () => {
setSelection(tab.getAttribute("data-byod"));
});
});
setSelection(initial);
};
const setEnrollTokenUrl = (url) => {
document.querySelector(".enroll-link").setAttribute("href", url);
};
@@ -804,6 +946,7 @@
window.location.href,
document.querySelector(".qr-code")
);
wireBYODTabs();
}
// handle rendering for ios and ipad
@@ -828,6 +971,7 @@
renderContent(templateId);
setIosIpadContent(platform);
wireBYODTabs();
}
});
</script>
+5 -6
View File
@@ -370,15 +370,14 @@ export const MDM_STATUS_TOOLTIP: Record<
),
"On (manual)": (
<span>
On Apple hosts, the enrollment profile was installed manually. Windows
hosts were enrolled without Autopilot. End users can turn MDM off.
Enrolled with a manual enrollment profile as a company-owned device. IT
admins can wipe this device and enforce all MDM restrictions.
</span>
),
"On (personal)": (
"On (manual - personal)": (
<span>
MDM was turned on by signing in with a Managed Apple Account on
iOS/iPadOS, or by adding a work profile on Android. End users can turn MDM
off.
Enrolled with a manual enrollment profile as a personal (BYOD) device. IT
admins cannot wipe this device or lock the end user out.
</span>
),
"On (company-owned)": null,