(["keys"], () => mdmAppleBmAPI.loadKeys(), {
+ enabled: isPremiumTier,
+ refetchOnWindowFocus: false,
+ });
+
+ const toggleRequestModal = () => {
+ setShowRequestModal(!showRequestModal);
+ };
+
+ const toggleEditTeamModal = () => {
+ setShowEditTeamModal(!showEditTeamModal);
+ };
+
+ const onDownloadKeys = (evt: React.MouseEvent) => {
+ evt.preventDefault();
+
+ // MDM TODO: Confirm error flash message
+ if (isFetchingKeys || fetchKeysError) {
+ renderFlash(
+ "error",
+ "Your MDM business manager keys could not be downloaded. Please try again."
+ );
+ return false;
+ }
+
+ if (keys) {
+ // MDM TODO: Validate keys like we validate certificates?
+ // if (keys && isValidKeys(keys)) {
+ const filename = "fleet.pem";
+ const file = new global.window.File([keys], filename, {
+ type: "application/x-pem-file",
+ });
+
+ FileSaver.saveAs(file);
+ } else {
+ renderFlash(
+ "error",
+ "Your MDM business manager keys could not be downloaded. Please TODO ACTION."
+ );
+ }
+ return false;
+ };
+
+ const renderMdmAppleSection = () => {
+ if (errorMdmApple) {
+ return ;
+ }
+
+ if (!mdmApple) {
+ return (
+ <>
+
+ Connect Fleet to Apple Push Certificates Portal to change settings
+ and install software on your macOS hosts.
+
+
+
+ 1. Request a certificate signing request (CSR) and key for Apple
+ Push Notification Service (APNs) and a certificate and key for
+ Simple Certificate Enrollment Protocol (SCEP).
+
+
+ Request
+
+
2. Go to your email to download your CSR.
+
+ 3.{" "}
+
+
+ If you don’t have an Apple ID, select Create yours now .
+
+
+ 4. In Apple Push Certificates Portal, select{" "}
+ Create a Certificate , upload your CSR, and download your
+ APNs certificate.
+
+
+ 5. Deploy Fleet with mdm configuration.{" "}
+
+
+
+ >
+ );
+ }
+
+ return (
+ <>
+
+ To change settings and install software on your macOS hosts, Apple
+ Inc. requires an Apple Push Notification service (APNs) certificate.
+
+
+
Common name (CN)
+
{mdmApple.common_name}
+
Serial number
+
{mdmApple.serial_number}
+
Issuer
+
{mdmApple.issuer}
+
Renew date
+
{readableDate(mdmApple.renew_date)}
+
+ >
+ );
+ };
+
+ const renderMdmAppleBm = () => {
+ if (errorMdmAppleBm) {
+ return ;
+ }
+
+ if (!mdmAppleBm) {
+ return (
+ <>
+
+ Connect Fleet to your Apple Business Manager account to
+ automatically enroll macOS hosts to Fleet when they’re first
+ unboxed.
+
+
+
1. Download your public and private keys.
+
+ Download
+
+
+ 2. Sign in to{" "}
+
+
+ If your organization doesn’t have an account, select{" "}
+ Enroll now .
+
+
+ 3. In Apple Business Manager, upload your public key and download
+ your server token.
+
+
+ 4. Deploy Fleet with mdm configuration.{" "}
+
+
+
+ >
+ );
+ }
+
+ return (
+ <>
+
+ To use automatically enroll macOS hosts to Fleet when they’re first
+ unboxed, Apple Inc. requires a server token.
+
+
+
+
+ Team
+
+
+
+ {mdmAppleBm.default_team || "No team"}{" "}
+
+ Edit
+
+
+
Apple ID
+
{mdmAppleBm.apple_id}
+
Organization name
+
{mdmAppleBm.organization_name}
+
MDM Server URL
+
{mdmAppleBm.mdm_server_url}
+
Renew date
+
{readableDate(mdmAppleBm.renew_date)}
+
+ >
+ );
+ };
+
+ return (
+
+
+
Apple Push Certificates Portal
+ {isLoadingMdmApple ? : renderMdmAppleSection()}
+
+ {isPremiumTier && (
+
+
Apple Business Manager
+ {isLoadingMdmAppleBm ? : renderMdmAppleBm()}
+
+ )}
+ {showRequestModal && (
+
+ )}
+ {showEditTeamModal && (
+
+ )}
+
+ );
+};
+
+export default Mdm;
diff --git a/frontend/pages/admin/IntegrationsPage/cards/Mdm/_styles.scss b/frontend/pages/admin/IntegrationsPage/cards/Mdm/_styles.scss
new file mode 100644
index 0000000000..ffaba6093c
--- /dev/null
+++ b/frontend/pages/admin/IntegrationsPage/cards/Mdm/_styles.scss
@@ -0,0 +1,54 @@
+.mdm-integrations {
+ display: flex;
+ flex-direction: column;
+ width: 65%;
+ gap: 80px;
+
+ &__section {
+ margin: 0 0 $pad-large;
+
+ h2 {
+ padding-bottom: $pad-small;
+ max-width: 100%;
+ font-size: $medium;
+ font-weight: $regular;
+ color: $core-fleet-black;
+ border-bottom: solid 1px $ui-fleet-blue-15;
+ margin: 0 0 $pad-xxlarge;
+ @media (min-width: $break-990) {
+ max-width: 65%;
+ }
+ }
+
+ h4 {
+ margin-bottom: 0;
+ }
+
+ p {
+ margin: 0;
+ }
+
+ .mdm-integrations__edit-team-btn {
+ margin-left: 12px;
+
+ .children-wrapper {
+ gap: $pad-small;
+ }
+ }
+
+ .component__tooltip-wrapper__tip-text {
+ max-width: initial;
+ }
+ }
+
+ &__section-description,
+ &__section-instructions,
+ &__section-information {
+ font-size: $x-small;
+ color: $core-fleet-black;
+ width: 100%;
+ @media (min-width: $break-990) {
+ width: 60%;
+ }
+ }
+}
diff --git a/frontend/pages/admin/IntegrationsPage/cards/Mdm/components/EditTeamModal.tsx b/frontend/pages/admin/IntegrationsPage/cards/Mdm/components/EditTeamModal.tsx
new file mode 100644
index 0000000000..66f3bf10bb
--- /dev/null
+++ b/frontend/pages/admin/IntegrationsPage/cards/Mdm/components/EditTeamModal.tsx
@@ -0,0 +1,31 @@
+import React from "react";
+
+import Modal from "components/Modal";
+import Button from "components/buttons/Button";
+
+interface IEditTeamModal {
+ onCancel: () => void;
+ onEdit: () => void;
+}
+
+const baseClass = "edit-team-modal";
+
+const EditTeamModal = ({ onCancel, onEdit }: IEditTeamModal): JSX.Element => {
+ return (
+
+ <>
+ Cool beans
+
+
+ Save
+
+
+ Cancel
+
+
+ >
+
+ );
+};
+
+export default EditTeamModal;
diff --git a/frontend/pages/admin/IntegrationsPage/cards/Mdm/components/RequestModal.tsx b/frontend/pages/admin/IntegrationsPage/cards/Mdm/components/RequestModal.tsx
new file mode 100644
index 0000000000..8e009d825a
--- /dev/null
+++ b/frontend/pages/admin/IntegrationsPage/cards/Mdm/components/RequestModal.tsx
@@ -0,0 +1,31 @@
+import React from "react";
+
+import Modal from "components/Modal";
+import Button from "components/buttons/Button";
+
+interface IRequestModal {
+ onCancel: () => void;
+ onRequest: () => void;
+}
+
+const baseClass = "request-modal";
+
+const RequestModal = ({ onCancel, onRequest }: IRequestModal): JSX.Element => {
+ return (
+
+ <>
+ Cool beans
+
+
+ Request
+
+
+ Cancel
+
+
+ >
+
+ );
+};
+
+export default RequestModal;
diff --git a/frontend/services/entities/mdm_apple.ts b/frontend/services/entities/mdm_apple.ts
new file mode 100644
index 0000000000..18c54e6f33
--- /dev/null
+++ b/frontend/services/entities/mdm_apple.ts
@@ -0,0 +1,12 @@
+/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
+import { sendRequest } from "services/mock_service/service/service"; // MDM TODO: Replace when backend is merged
+// import sendRequest from "services";
+import endpoints from "utilities/endpoints";
+
+export default {
+ getAppleAPNInfo: () => {
+ const { MDM_APPLE } = endpoints;
+ const path = MDM_APPLE;
+ return sendRequest("GET", path);
+ },
+};
diff --git a/frontend/services/entities/mdm_apple_bm.ts b/frontend/services/entities/mdm_apple_bm.ts
new file mode 100644
index 0000000000..16e053375d
--- /dev/null
+++ b/frontend/services/entities/mdm_apple_bm.ts
@@ -0,0 +1,31 @@
+/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
+import { sendRequest } from "services/mock_service/service/service"; // MDM TODO: Replace when backend is merged
+// import sendRequest from "services";
+import endpoints from "utilities/endpoints";
+
+export default {
+ getAppleBMInfo: () => {
+ const { MDM_APPLE_BM } = endpoints;
+ const path = MDM_APPLE_BM;
+ return sendRequest("GET", path);
+ },
+ loadKeys: () => {
+ const { MDM_APPLE_BM_KEYS } = endpoints;
+ const path = MDM_APPLE_BM_KEYS;
+
+ // MDM TODO: Originally written for certificate_chain for certificate, refactor for keys when backend is merged
+ return sendRequest("GET", path).then(({ certificate_chain }) => {
+ let decodedKeys;
+ try {
+ decodedKeys = global.window.atob(certificate_chain);
+ } catch (err) {
+ return Promise.reject(`Unable to decode keys: ${err}`);
+ }
+ if (!decodedKeys) {
+ return Promise.reject("Missing or undefined keys.");
+ }
+
+ return Promise.resolve(decodedKeys);
+ });
+ },
+};
diff --git a/frontend/services/mock_service/mocks/config.ts b/frontend/services/mock_service/mocks/config.ts
index ab7c5583ec..47d838ebda 100644
--- a/frontend/services/mock_service/mocks/config.ts
+++ b/frontend/services/mock_service/mocks/config.ts
@@ -22,6 +22,8 @@ const REQUEST_RESPONSE_MAPPINGS: IResponses = {
// request query string is hostname, uuid, or mac address; response is host detail excluding any
// expensive data operations
"targets?query={*}": RESPONSES.hosts,
+ "mdm/apple": RESPONSES.mdmApple,
+ "mdm/apple_bm": RESPONSES.mdmAppleBm,
},
POST: {
// request body is ISelectedTargets
diff --git a/frontend/services/mock_service/mocks/responses.ts b/frontend/services/mock_service/mocks/responses.ts
index 39ccc7f329..1902928684 100644
--- a/frontend/services/mock_service/mocks/responses.ts
+++ b/frontend/services/mock_service/mocks/responses.ts
@@ -11,6 +11,24 @@ const count = {
targets_missing_in_action: 0,
};
+// MDM TODO: Remove mock when backend is merged
+const mdmApple = {
+ common_name: "Mock backend response APSP:04b46ce0-xxxx-xxxx-xxxx-xxxxxxxx",
+ serial_number: "Mock backend response 123938388712",
+ issuer:
+ "Mock backend response Apple Application Integration 2 Certification Authority",
+ renew_date: "2023-09-30T00:00:00Z",
+};
+
+// MDM TODO: Remove mock when backend is merged
+const mdmAppleBm = {
+ default_team: "Mock backend response Apples",
+ apple_id: "Mock backend response rachel@fleetdm.com",
+ organization_name: "Mock backend response Fleet Device Management",
+ mdm_server_url: "Mock backend response https://fleet.organization.com/mdm",
+ renew_date: "2023-09-30T00:00:00Z",
+};
+
const hosts = {
hosts: [
{
@@ -368,4 +386,6 @@ export default {
count,
hosts,
labels,
+ mdmApple,
+ mdmAppleBm,
};
diff --git a/frontend/utilities/endpoints.ts b/frontend/utilities/endpoints.ts
index cbb585dcaf..8e32968d47 100644
--- a/frontend/utilities/endpoints.ts
+++ b/frontend/utilities/endpoints.ts
@@ -31,6 +31,9 @@ export default {
LOGIN: `/${API_VERSION}/fleet/login`,
LOGOUT: `/${API_VERSION}/fleet/logout`,
MACADMINS: `/${API_VERSION}/fleet/macadmins`,
+ MDM_APPLE: `/${API_VERSION}/fleet/mdm/apple`,
+ MDM_APPLE_BM: `/${API_VERSION}/fleet/mdm/apple_bm`,
+ MDM_APPLE_BM_KEYS: `/${API_VERSION}/fleet/mdm/apple_bm/keys`,
MDM_SUMMARY: `/${API_VERSION}/fleet/hosts/summary/mdm`,
HOST_MDM: (id: number) => `/${API_VERSION}/fleet/hosts/${id}/mdm`,
ME: `/${API_VERSION}/fleet/me`,
diff --git a/frontend/utilities/permissions/permissions.ts b/frontend/utilities/permissions/permissions.ts
index 0569dc667f..3175030665 100644
--- a/frontend/utilities/permissions/permissions.ts
+++ b/frontend/utilities/permissions/permissions.ts
@@ -13,6 +13,11 @@ export const isPremiumTier = (config: IConfig): boolean => {
return config.license.tier === "premium";
};
+// MDM TODO: Ensure we grabbed the correct config key when backend is merged
+export const isMdmEnabled = (config: IConfig): boolean => {
+ return config.mdm_enabled === true;
+};
+
export const isGlobalAdmin = (user: IUser): boolean => {
return user.global_role === "admin";
};
@@ -106,6 +111,7 @@ export default {
isSandboxMode,
isFreeTier,
isPremiumTier,
+ isMdmEnabled,
isGlobalAdmin,
isGlobalMaintainer,
isGlobalObserver,