diff --git a/frontend/interfaces/activity.ts b/frontend/interfaces/activity.ts
index fd2baa00d1..8077d65bcf 100644
--- a/frontend/interfaces/activity.ts
+++ b/frontend/interfaces/activity.ts
@@ -207,12 +207,12 @@ export interface IActivityDetails {
labels_exclude_any?: ILabelSoftwareTitle[];
labels_include_any?: ILabelSoftwareTitle[];
location?: string; // name of location associated with VPP token
- mdm_platform?: "microsoft" | "apple";
+ mdm_platform?: "microsoft" | "apple" | "android" | "ios" | "ipados";
minimum_version?: string;
name?: string;
pack_id?: number;
pack_name?: string;
- platform?: Platform; // software platform
+ platform?: Platform; // OS platform
policy_id?: number;
policy_name?: string;
profile_identifier?: string;
diff --git a/frontend/pages/DashboardPage/cards/ActivityFeed/GlobalActivityItem/GlobalActivityItem.tests.tsx b/frontend/pages/DashboardPage/cards/ActivityFeed/GlobalActivityItem/GlobalActivityItem.tests.tsx
index 519c9f7054..b01219b352 100644
--- a/frontend/pages/DashboardPage/cards/ActivityFeed/GlobalActivityItem/GlobalActivityItem.tests.tsx
+++ b/frontend/pages/DashboardPage/cards/ActivityFeed/GlobalActivityItem/GlobalActivityItem.tests.tsx
@@ -1036,23 +1036,20 @@ describe("Activity Feed", () => {
).toBeInTheDocument();
});
- it("renders a 'mdm_enrolled' type for apple with host display name and personal enrollment provided", () => {
+ it("renders a 'mdm_enrolled' type for android or apple personal devices with actor full name provided", () => {
const activity = createMockActivity({
type: ActivityType.MdmEnrolled,
details: {
host_display_name: "Test Host",
enrollment_id: "test-enrollment-id",
- mdm_platform: "apple",
+ platform: "android",
},
});
render();
expect(
screen.getByText((content, node) => {
- return (
- node?.innerHTML ===
- "Test User An end user turned on MDM features for Test Host (personal)."
- );
+ return node?.innerHTML === "Test Host enrolled to Fleet.";
})
).toBeInTheDocument();
});
@@ -1583,4 +1580,35 @@ describe("Activity Feed", () => {
).toBeInTheDocument();
expect(screen.getByText(/HYDRANT_TEST/)).toBeInTheDocument();
});
+
+ it("renders an mdm unenroll activity with an actor name for ios, ipados, and android devices", () => {
+ const activity = createMockActivity({
+ type: ActivityType.MdmUnenrolled,
+ actor_full_name: "Test User",
+ details: {
+ platform: "ios",
+ host_display_name: "Test Host",
+ },
+ });
+ render();
+
+ expect(screen.getByText(/Test User/)).toBeInTheDocument();
+ expect(screen.getByText(/told Fleet to unenroll/)).toBeInTheDocument();
+ expect(screen.getByText(/Test Host/)).toBeInTheDocument();
+ });
+
+ it("renders an mdm unenroll activity with no actor name for ios, ipados, and android devices", () => {
+ const activity = createMockActivity({
+ type: ActivityType.MdmUnenrolled,
+ actor_full_name: undefined,
+ details: {
+ platform: "ios",
+ host_display_name: "Test Host",
+ },
+ });
+ render();
+
+ expect(screen.getByText(/Test Host/)).toBeInTheDocument();
+ expect(screen.getByText(/is unenrolled from Fleet/)).toBeInTheDocument();
+ });
});
diff --git a/frontend/pages/DashboardPage/cards/ActivityFeed/GlobalActivityItem/GlobalActivityItem.tsx b/frontend/pages/DashboardPage/cards/ActivityFeed/GlobalActivityItem/GlobalActivityItem.tsx
index a44a7e168b..dce7dc03f5 100644
--- a/frontend/pages/DashboardPage/cards/ActivityFeed/GlobalActivityItem/GlobalActivityItem.tsx
+++ b/frontend/pages/DashboardPage/cards/ActivityFeed/GlobalActivityItem/GlobalActivityItem.tsx
@@ -4,6 +4,8 @@ import React from "react";
import { ActivityType, IActivity } from "interfaces/activity";
import {
AppleDisplayPlatform,
+ isAndroid,
+ isIPadOrIPhone,
PLATFORM_DISPLAY_NAMES,
} from "interfaces/platform";
import { getInstallStatusPredicate } from "interfaces/software";
@@ -272,12 +274,24 @@ const TAGGED_TEMPLATES = {
);
return <>{hostDisplayName} enrolled in Fleet.>;
},
+
mdmEnrolled: (activity: IActivity) => {
- if (activity.details?.mdm_platform === "microsoft") {
+ const { mdm_platform, platform = "", host_display_name, host_serial } =
+ activity.details || {};
+
+ if (mdm_platform === "microsoft") {
return (
<>
- Mobile device management (MDM) was turned on for{" "}
- {activity.details?.host_display_name} (manual).
+ {activity.actor_full_name} Mobile device management (MDM) was
+ turned on for {activity.details?.host_display_name} (manual).
+ >
+ );
+ }
+
+ if (isAndroid(platform) || isIPadOrIPhone(platform)) {
+ return (
+ <>
+ {host_display_name} enrolled to Fleet.
>
);
}
@@ -285,24 +299,21 @@ const TAGGED_TEMPLATES = {
// note: if mdm_platform is missing, we assume this is Apple MDM for backwards
// compatibility
let enrollmentTypeText = "";
- if (activity.details?.enrollment_id) {
- enrollmentTypeText = "personal";
- } else if (activity.details?.installed_from_dep) {
+ if (activity.details?.installed_from_dep) {
enrollmentTypeText = "automatic";
} else {
enrollmentTypeText = "manual";
}
- const hostDisplayText =
- activity.details?.host_display_name || activity.details?.host_serial;
-
- const hostDisplayPrefixText = activity.details?.host_display_name
+ const hostDisplayText = host_display_name || host_serial;
+ const hostDisplayPrefixText = host_display_name
? ""
: "a host with serial number ";
return (
<>
- An end user turned on MDM features for {hostDisplayPrefixText}
+ {activity.actor_full_name} An end user turned on MDM features for{" "}
+ {hostDisplayPrefixText}
{hostDisplayText} ({enrollmentTypeText})
@@ -310,16 +321,39 @@ const TAGGED_TEMPLATES = {
>
);
},
+
mdmUnenrolled: (activity: IActivity) => {
+ const { actor_full_name } = activity;
+ const { platform = "", host_display_name } = activity.details || {};
+
+ if (isAndroid(platform) || isIPadOrIPhone(platform)) {
+ return actor_full_name ? (
+ <>
+ {actor_full_name} told Fleet to unenroll{" "}
+ {host_display_name}.
+ >
+ ) : (
+ <>
+ {host_display_name} is unenrolled from Fleet.
+ >
+ );
+ }
+
return (
<>
- {activity.actor_full_name
- ? " told Fleet to turn off mobile device management (MDM) for"
- : "Mobile device management (MDM) was turned off for"}{" "}
- {activity.details?.host_display_name}.
+ {actor_full_name ? (
+ <>
+ {actor_full_name} told Fleet to turn off mobile device
+ management (MDM) for
+ >
+ ) : (
+ "Mobile device management (MDM) was turned off for"
+ )}{" "}
+ {host_display_name}.
>
);
},
+
editedAppleosMinVersion: (
applePlatform: AppleDisplayPlatform,
activity: IActivity
@@ -1823,7 +1857,12 @@ const GlobalActivityItem = ({
) : (
DEFAULT_ACTOR_DISPLAY
);
-
+ // MdmEnrolled and MdmUnenroll activities have more complicated logic to
+ // determine if we display the actor name so we will handle that in the
+ // template function
+ case ActivityType.MdmUnenrolled:
+ case ActivityType.MdmEnrolled:
+ return null;
default:
return DEFAULT_ACTOR_DISPLAY;
}
diff --git a/frontend/pages/hosts/details/HostDetailsPage/HostActionsDropdown/HostActionsDropdown.tsx b/frontend/pages/hosts/details/HostDetailsPage/HostActionsDropdown/HostActionsDropdown.tsx
index c71a98fe6e..d43f9de907 100644
--- a/frontend/pages/hosts/details/HostDetailsPage/HostActionsDropdown/HostActionsDropdown.tsx
+++ b/frontend/pages/hosts/details/HostDetailsPage/HostActionsDropdown/HostActionsDropdown.tsx
@@ -41,6 +41,7 @@ const HostActionsDropdown = ({
isGlobalMaintainer = false,
isMacMdmEnabledAndConfigured = false,
isWindowsMdmEnabledAndConfigured = false,
+ isAndroidMdmEnabledAndConfigured = false,
currentUser,
config: globalConfig,
} = useContext(AppContext);
@@ -69,6 +70,7 @@ const HostActionsDropdown = ({
isConnectedToFleetMdm,
isMacMdmEnabledAndConfigured,
isWindowsMdmEnabledAndConfigured,
+ isAndroidMdmEnabledAndConfigured,
doesStoreEncryptionKey: doesStoreEncryptionKey ?? false,
hostMdmDeviceStatus,
hostScriptsEnabled,
diff --git a/frontend/pages/hosts/details/HostDetailsPage/HostActionsDropdown/_styles.scss b/frontend/pages/hosts/details/HostDetailsPage/HostActionsDropdown/_styles.scss
index eda3a72674..1659d8f801 100644
--- a/frontend/pages/hosts/details/HostDetailsPage/HostActionsDropdown/_styles.scss
+++ b/frontend/pages/hosts/details/HostDetailsPage/HostActionsDropdown/_styles.scss
@@ -8,4 +8,8 @@
right: 0;
min-width: max-content;
}
+
+ .actions-dropdown-select__menu {
+ min-width: 200px;
+ }
}
diff --git a/frontend/pages/hosts/details/HostDetailsPage/HostActionsDropdown/helpers.tsx b/frontend/pages/hosts/details/HostDetailsPage/HostActionsDropdown/helpers.tsx
index f163655112..b115d2be63 100644
--- a/frontend/pages/hosts/details/HostDetailsPage/HostActionsDropdown/helpers.tsx
+++ b/frontend/pages/hosts/details/HostDetailsPage/HostActionsDropdown/helpers.tsx
@@ -66,7 +66,6 @@ const DEFAULT_OPTIONS = [
},
] as const;
-// eslint-disable-next-line import/prefer-default-export
interface IHostActionConfigOptions {
hostPlatform: string;
isPremiumTier: boolean;
@@ -81,6 +80,7 @@ interface IHostActionConfigOptions {
isConnectedToFleetMdm?: boolean;
isMacMdmEnabledAndConfigured: boolean;
isWindowsMdmEnabledAndConfigured: boolean;
+ isAndroidMdmEnabledAndConfigured: boolean;
doesStoreEncryptionKey: boolean;
hostMdmDeviceStatus: HostMdmDeviceStatusUIState;
hostScriptsEnabled: boolean | null;
@@ -108,14 +108,12 @@ const canTurnOffMdm = (config: IHostActionConfigOptions) => {
isEnrolledInMdm,
isConnectedToFleetMdm,
isMacMdmEnabledAndConfigured,
- hostMdmEnrollmentStatus,
+ isAndroidMdmEnabledAndConfigured,
} = config;
return (
- !isAndroid(hostPlatform) && // TODO(android): confirm can't turn off MDM for windows, iOS, iPadOS?
- isAppleDevice(hostPlatform) &&
- isMacMdmEnabledAndConfigured &&
+ ((isAndroid(hostPlatform) && isAndroidMdmEnabledAndConfigured) ||
+ (isAppleDevice(hostPlatform) && isMacMdmEnabledAndConfigured)) &&
isEnrolledInMdm &&
- hostMdmEnrollmentStatus !== "On (personal)" && // can't turn off MDM for personally enrolled hosts
isConnectedToFleetMdm &&
(isGlobalAdmin || isGlobalMaintainer || isTeamAdmin || isTeamMaintainer)
);
@@ -336,6 +334,19 @@ export const getDropdownOptionTooltipContent = (
return undefined;
};
+/** for ios, ipad, and android we want to display different text for mdmOff.
+ * The functionality is the same, but the action is called unenroll on those platforms.
+ */
+const formatTurnOffOptionLabel = (
+ options: IDropdownOption[],
+ hostPlatform: string
+) => {
+ const option = options.find((opt) => opt.value === "mdmOff");
+ if (option && (isIPadOrIPhone(hostPlatform) || isAndroid(hostPlatform))) {
+ option.label = "Unenroll";
+ }
+};
+
const modifyOptions = (
options: IDropdownOption[],
{
@@ -396,6 +407,7 @@ const modifyOptions = (
}
}
disableOptions(optionsToDisable);
+ formatTurnOffOptionLabel(options, hostPlatform);
return options;
};
diff --git a/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx b/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx
index 613dfcb758..157db12217 100644
--- a/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx
+++ b/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx
@@ -1357,6 +1357,9 @@ const HostDetailsPage = ({
hostId={host.id}
hostPlatform={host.platform}
hostName={host.display_name}
+ isBYODEnrollment={isPersonalEnrollmentInMdm(
+ host.mdm.enrollment_status
+ )}
onClose={toggleUnenrollMdmModal}
/>
)}
diff --git a/frontend/pages/hosts/details/HostDetailsPage/modals/UnenrollMdmModal/UnenrollMdmModal.tsx b/frontend/pages/hosts/details/HostDetailsPage/modals/UnenrollMdmModal/UnenrollMdmModal.tsx
index ce902622c9..b21e4042dd 100644
--- a/frontend/pages/hosts/details/HostDetailsPage/modals/UnenrollMdmModal/UnenrollMdmModal.tsx
+++ b/frontend/pages/hosts/details/HostDetailsPage/modals/UnenrollMdmModal/UnenrollMdmModal.tsx
@@ -6,13 +6,13 @@ import Modal from "components/Modal";
import { NotificationContext } from "context/notification";
import mdmAPI from "services/entities/mdm";
-import { isIPadOrIPhone } from "interfaces/platform";
-import CustomLink from "components/CustomLink";
+import { isAndroid, isIPadOrIPhone } from "interfaces/platform";
interface IUnenrollMdmModalProps {
hostId: number;
hostPlatform: string;
hostName: string;
+ isBYODEnrollment?: boolean;
onClose: () => void;
}
@@ -22,6 +22,7 @@ const UnenrollMdmModal = ({
hostId,
hostPlatform,
hostName,
+ isBYODEnrollment = false,
onClose,
}: IUnenrollMdmModalProps) => {
const [requestState, setRequestState] = useState<
@@ -34,22 +35,77 @@ const UnenrollMdmModal = ({
setRequestState("unenrolling");
try {
await mdmAPI.unenrollHostFromMdm(hostId, 5000);
- renderFlash(
- "success",
- <>
- MDM will be turned off for {hostName} next time this host
- checks in.
- >
- );
+ const successMessage =
+ isIPadOrIPhone(hostPlatform) || isAndroid(hostPlatform) ? (
+ <>
+ {hostName} will unenrolled next time this host checks in.
+ >
+ ) : (
+ <>
+ MDM will be turned off for {hostName} next time this host
+ checks in.
+ >
+ );
+ renderFlash("success", successMessage);
+ onClose();
} catch (unenrollMdmError: unknown) {
- renderFlash(
- "error",
+ const errorMessage =
+ isIPadOrIPhone(hostPlatform) || isAndroid(hostPlatform) ? (
+ "Couldn't unenroll. Please try again."
+ ) : (
+ <>
+ Failed to turn off MDM for {hostName}. Please try again.
+ >
+ );
+ renderFlash("error", errorMessage);
+ }
+ setRequestState(undefined);
+ };
+
+ const generateDescription = () => {
+ if (isIPadOrIPhone(hostPlatform)) {
+ return (
<>
- Failed to turn off MDM for {hostName}.
+
Settings configured by Fleet will be removed.
+ {isBYODEnrollment ? (
+
+ To re-enroll, ask your end user to navigate to{" "}
+
+ Settings > General > VPN & Device Management > Sign
+ in to Work or School Account...
+ {" "}
+ on their host and to log in with their work email.
+
+ ) : (
+
+ To re-enroll, make sure that the host is still in Apple Business
+ Manager (ABM). The host will automatically enroll after it's
+ reset.
+