Add /enroll URL for macOS in Add hosts modal (#47528)

**Related issue:** Resolves #38874

# Checklist for submitter

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.


## Testing

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added macOS enrollment details in the “Add hosts” flow, including a
clearer choice between **Personal (BYOD)** and **Company-owned**
devices.
* Shows a copyable macOS enrollment URL when MDM is configured, updating
the URL based on the selected device type.
* Keeps the macOS setup experience aligned with the enrollment method,
including packaging guidance when MDM isn’t enabled.
* **Tests**
* Added coverage for macOS enrollment URL rendering and device-type
switching in the “Add hosts” modal.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Steven Palmesano
2026-07-07 11:18:45 -05:00
committed by GitHub
parent d85dd50166
commit 1d1be298a9
6 changed files with 164 additions and 3 deletions
+1
View File
@@ -0,0 +1 @@
- Added enrollment profile URL to the macOS tab in the "Add hosts" modal, with enrollment type selection (company-owned or personal/BYOD) for MDM users.
@@ -94,6 +94,48 @@ describe("AddHostsModal", () => {
expect(screen.queryByText(/--enable-scripts/i)).not.toBeInTheDocument();
});
it("renders enroll url input for macOS if mac mdm is enabled", async () => {
const render = createCustomRenderer({
withBackendMock: true,
context: {
app: {
isMacMdmEnabledAndConfigured: true,
isPreviewMode: false,
config: createMockConfig(),
},
},
});
const { user } = render(
<AddHostsModal
isAnyTeamSelected
enrollSecret={ENROLL_SECRET}
isLoading={false}
onCancel={noop}
/>
);
await user.click(screen.getByRole("tab", { name: "macOS" }));
expect(screen.getByLabelText("Personal (BYOD)")).toBeInTheDocument();
expect(screen.getByLabelText("Company-owned")).toBeInTheDocument();
expect(
screen.getByText(/Send this to your end users:/i)
).toBeInTheDocument();
// Company-owned is selected by default — URL has no byod param
const urlInput = screen.getByDisplayValue(
new RegExp(`/enroll\\?enroll_secret=${ENROLL_SECRET}$`)
);
expect(urlInput).toBeInTheDocument();
// Switching to Personal (BYOD) appends byod=true
await user.click(screen.getByLabelText("Personal (BYOD)"));
const byodUrlInput = screen.getByDisplayValue(
new RegExp(`/enroll\\?enroll_secret=${ENROLL_SECRET}&byod=true`)
);
expect(byodUrlInput).toBeInTheDocument();
});
it("renders enroll url input for ios & ipadOS if mac mdm is enabled", async () => {
const render = createCustomRenderer({
withBackendMock: true,
@@ -0,0 +1,109 @@
import React, { useContext, useState } from "react";
import { AppContext } from "context/app";
import { LEARN_MORE_ABOUT_BASE_LINK } from "utilities/constants";
import { getPathWithQueryParams } from "utilities/url";
import CustomLink from "components/CustomLink";
import Radio from "components/forms/fields/Radio";
import InputField from "components/forms/fields/InputField";
type DeviceType = "companyOwned" | "personalBYOD";
const generateInstallerString = (
serverUrl: string,
enrollSecret: string,
scriptsDisabled: boolean
) => {
return `fleetctl package --type=pkg ${
!scriptsDisabled ? "--enable-scripts " : ""
}--fleet-desktop --fleet-url=${serverUrl} --enroll-secret=${enrollSecret}`;
};
const baseClass = "macos-panel";
interface IMacosPanelProps {
enrollSecret: string;
}
const MacosPanel = ({ enrollSecret }: IMacosPanelProps) => {
const { config, isMacMdmEnabledAndConfigured } = useContext(AppContext);
const [deviceType, setDeviceType] = useState<DeviceType>("companyOwned");
if (!config) return null;
if (isMacMdmEnabledAndConfigured) {
const enrollUrl = getPathWithQueryParams(
`${config.server_settings.server_url}/enroll`,
{
enroll_secret: enrollSecret,
byod: deviceType === "personalBYOD" ? "true" : undefined,
}
);
return (
<div className={baseClass}>
<form>
<fieldset className="form-field">
<Radio
label="Personal (BYOD)"
id="personal-byod"
checked={deviceType === "personalBYOD"}
value="personalBYOD"
name="device-type"
onChange={() => setDeviceType("personalBYOD")}
/>
<Radio
label="Company-owned"
id="company-owned"
checked={deviceType === "companyOwned"}
value="companyOwned"
name="device-type"
onChange={() => setDeviceType("companyOwned")}
/>
</fieldset>
<InputField
readOnly
inputWrapperClass={`${baseClass}__enroll-link`}
name="enroll-link"
enableCopy
label="Send this to your end users:"
value={enrollUrl}
/>
</form>
</div>
);
}
const installerString = generateInstallerString(
config.server_settings.server_url,
enrollSecret,
config.server_settings.scripts_disabled
);
return (
<div className={baseClass}>
<InputField
readOnly
inputWrapperClass={`${baseClass}__installer-input`}
name="installer"
enableCopy
label={
<>
Use this command to generate Fleet&apos;s agent.{" "}
<CustomLink
url={`${LEARN_MORE_ABOUT_BASE_LINK}/generate-fleets-agent`}
text="Learn how"
newTab
/>
</>
}
type="textarea"
value={installerString}
helpText="Run this on your computer, then deploy the generated package to your hosts."
/>
</div>
);
};
export default MacosPanel;
@@ -0,0 +1,6 @@
.macos-panel {
&__enroll-link input {
font-family: "SourceCodePro", $monospace;
color: $core-fleet-blue;
}
}
@@ -0,0 +1 @@
export { default } from "./MacosPanel";
@@ -20,6 +20,7 @@ import TabText from "components/TabText";
import { isValidPemCertificate } from "../../../pages/hosts/ManageHostsPage/helpers";
import IosIpadosPanel from "./IosIpadosPanel";
import AndroidPanel from "./AndroidPanel";
import MacosPanel from "./MacosPanel";
interface IPlatformSubNav {
name: string;
@@ -281,9 +282,6 @@ const PlatformWrapper = ({
hosts. For ARM, use <code>--arch=arm64</code>
</>
);
} else if (packageType === "pkg") {
packageTypeHelpText =
"Run this on your computer, then deploy the generated package to your hosts.";
} else {
packageTypeHelpText = "";
}
@@ -347,6 +345,10 @@ const PlatformWrapper = ({
return <AndroidPanel enrollSecret={enrollSecret} />;
}
if (packageType === "pkg") {
return <MacosPanel enrollSecret={enrollSecret} />;
}
if (packageType === "advanced") {
return (
<>