diff --git a/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx b/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx index fe17790381..a83b74327c 100644 --- a/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx +++ b/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx @@ -142,7 +142,7 @@ import { } from "../helpers"; import WipeModal from "./modals/WipeModal"; import { parseHostSoftwareQueryParams } from "../cards/Software/HostSoftware"; -import { getErrorMessage } from "./helpers"; +import { canShowMyDeviceButton, getErrorMessage } from "./helpers"; import CancelActivityModal from "./modals/CancelActivityModal"; import CertificateDetailsModal from "../modals/CertificateDetailsModal"; import HostHeader from "../cards/HostHeader"; @@ -1317,10 +1317,9 @@ const HostDetailsPage = ({ // "My device" link points to that host's end-user My device page. The URL // embeds the device auth token so it acts as a credential, hence global - // admin only. The endpoint guarantees a valid link on every fetch — it - // refreshes an expired token or generates one for a host that has never - // had one — so we don't gate visibility on orbit/MDM state. - const canViewMyDeviceLink = isGlobalAdmin; + // admin only. Also hide it on hosts that have no live end-user surface — + // no Fleet Desktop (so no token, and no page to load) or wiped. + const canViewMyDeviceLink = isGlobalAdmin && canShowMyDeviceButton(host); const canEditCustomHostVitals = isGlobalAdmin || diff --git a/frontend/pages/hosts/details/HostDetailsPage/helpers.tests.ts b/frontend/pages/hosts/details/HostDetailsPage/helpers.tests.ts new file mode 100644 index 0000000000..626556a6ef --- /dev/null +++ b/frontend/pages/hosts/details/HostDetailsPage/helpers.tests.ts @@ -0,0 +1,60 @@ +import createMockHost from "__mocks__/hostMock"; + +import { canShowMyDeviceButton } from "./helpers"; + +describe("canShowMyDeviceButton", () => { + it("returns true when Fleet Desktop is installed and the host is not wiped", () => { + const host = createMockHost({ + fleet_desktop_version: "1.22.1", + mdm: { ...createMockHost().mdm, device_status: "unlocked" }, + }); + expect(canShowMyDeviceButton(host)).toBe(true); + }); + + it("returns true for a locked host that still has Fleet Desktop", () => { + const host = createMockHost({ + fleet_desktop_version: "1.22.1", + mdm: { ...createMockHost().mdm, device_status: "locked" }, + }); + expect(canShowMyDeviceButton(host)).toBe(true); + }); + + it("returns false when Fleet Desktop is not installed", () => { + const host = createMockHost({ fleet_desktop_version: null }); + expect(canShowMyDeviceButton(host)).toBe(false); + }); + + it("returns false when the host has been wiped", () => { + const host = createMockHost({ + fleet_desktop_version: "1.22.1", + mdm: { ...createMockHost().mdm, device_status: "wiped" }, + }); + expect(canShowMyDeviceButton(host)).toBe(false); + }); + + it("returns false when the host has a wipe in flight", () => { + const host = createMockHost({ + fleet_desktop_version: "1.22.1", + mdm: { + ...createMockHost().mdm, + device_status: "unlocked", + pending_action: "wipe", + }, + }); + expect(canShowMyDeviceButton(host)).toBe(false); + }); + + // Only wipe-related states hide the button. Other transient states leave the + // end-user page reachable, so the button stays visible. + it("returns true for non-wipe transient states like clear_passcode", () => { + const host = createMockHost({ + fleet_desktop_version: "1.22.1", + mdm: { + ...createMockHost().mdm, + device_status: "unlocked", + pending_action: "clear_passcode", + }, + }); + expect(canShowMyDeviceButton(host)).toBe(true); + }); +}); diff --git a/frontend/pages/hosts/details/HostDetailsPage/helpers.ts b/frontend/pages/hosts/details/HostDetailsPage/helpers.ts index a5a362e026..484a6c6b22 100644 --- a/frontend/pages/hosts/details/HostDetailsPage/helpers.ts +++ b/frontend/pages/hosts/details/HostDetailsPage/helpers.ts @@ -1,8 +1,10 @@ import { getErrorReason } from "interfaces/errors"; +import { IHost } from "interfaces/host"; + +import { getHostDeviceStatusUIState } from "../helpers"; const DEFAULT_ERROR_MESSAGE = "refetch error."; -// eslint-disable-next-line import/prefer-default-export export const getErrorMessage = (e: unknown, hostName: string) => { let errorMessage = getErrorReason(e, { reasonIncludes: "Host does not have MDM turned on", @@ -14,3 +16,19 @@ export const getErrorMessage = (e: unknown, hostName: string) => { return `Host "${hostName}" ${errorMessage}`; }; + +// The "My device" link opens the end-user page authed by the host's device +// auth token. Fleet Desktop is what mints that token on orbit check-in, so a +// host missing fleet_desktop_version is also missing a token and has no live +// end-user surface. Hide the button on wiped hosts and on hosts with a wipe +// in flight — the device is about to have no end-user session to review. +export const canShowMyDeviceButton = ( + host: Pick +) => { + if (!host.fleet_desktop_version) return false; + const uiState = getHostDeviceStatusUIState( + host.mdm.device_status, + host.mdm.pending_action + ); + return uiState !== "wiped" && uiState !== "wiping"; +};