diff --git a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx index 8750049b9af..45934c3c395 100644 --- a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx +++ b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx @@ -313,6 +313,8 @@ const ManageHostsPage = ({ queryParams?.[HOSTS_QUERY_PARAMS.SCRIPT_BATCH_EXECUTION_STATUS] ?? (scriptBatchExecutionId ? "ran" : undefined); const depProfileError = queryParams?.dep_profile_error; + /** URL converts to lowercase but API and UI requires uppercase */ + const depAssignProfileResponse = queryParams?.dep_assign_profile_response.toUpperCase(); // ========= routeParams const { active_label: activeLabel, label_id: labelID } = routeParams; @@ -354,7 +356,7 @@ const ManageHostsPage = ({ // configProfileStatus || // configProfileUUID // depProfileError - + // depAssignProfileResponse const runScriptBatchFilterNotSupported = !!( // all above, except acceptable filters ( @@ -392,7 +394,8 @@ const ManageHostsPage = ({ scriptBatchExecutionStatus || configProfileStatus || configProfileUUID || - depProfileError + depProfileError || + depAssignProfileResponse ) ); @@ -577,6 +580,7 @@ const ManageHostsPage = ({ scriptBatchExecutionStatus, scriptBatchExecutionId, depProfileError: strToBool(depProfileError), + depAssignProfileResponse, }, ], ({ queryKey }) => hostsAPI.loadHosts(queryKey[0]), @@ -1105,6 +1109,8 @@ const ManageHostsPage = ({ ] = scriptBatchExecutionId; } else if (depProfileError) { newQueryParams.dep_profile_error = depProfileError; + } else if (depAssignProfileResponse) { + newQueryParams.dep_assign_profile_response = depAssignProfileResponse; } router.replace( @@ -1150,6 +1156,8 @@ const ManageHostsPage = ({ routeTemplate, routeParams, softwareStatus, + depProfileError, + depAssignProfileResponse, ] ); @@ -1345,6 +1353,7 @@ const ManageHostsPage = ({ diskEncryptionStatus, vulnerability, depProfileError, + depAssignProfileResponse, }) : hostsAPI.transferToTeam(teamId, selectedHostIds); @@ -1849,7 +1858,8 @@ const ManageHostsPage = ({ osSettingsStatus || diskEncryptionStatus || vulnerability || - depProfileError + depProfileError || + depAssignProfileResponse ); return ( @@ -1997,6 +2007,7 @@ const ManageHostsPage = ({ scriptBatchRanAt: scriptBatchSummary?.created_at || null, scriptBatchScriptName: scriptBatchSummary?.script_name || null, depProfileError, + depAssignProfileResponse, }} selectedLabel={selectedLabel} isOnlyObserver={isOnlyObserver} diff --git a/frontend/pages/hosts/ManageHostsPage/components/HostsFilterBlock/HostsFilterBlock.tsx b/frontend/pages/hosts/ManageHostsPage/components/HostsFilterBlock/HostsFilterBlock.tsx index 86af432f59a..708320837a0 100644 --- a/frontend/pages/hosts/ManageHostsPage/components/HostsFilterBlock/HostsFilterBlock.tsx +++ b/frontend/pages/hosts/ManageHostsPage/components/HostsFilterBlock/HostsFilterBlock.tsx @@ -24,6 +24,7 @@ import { SoftwareAggregateStatus } from "interfaces/software"; import { HOSTS_QUERY_PARAMS, MacSettingsStatusQueryParam, + DepAssignProfileResponse, } from "services/entities/hosts"; import { ScriptBatchHostCountV1 } from "services/entities/scripts"; @@ -94,6 +95,7 @@ interface IHostsFilterBlockProps { scriptBatchRanAt: string | null; scriptBatchScriptName: string | null; depProfileError: string; // string "true" as we don't handle booleans + depAssignProfileResponse?: DepAssignProfileResponse; }; selectedLabel?: ILabel; isOnlyObserver?: boolean; @@ -156,6 +158,7 @@ const HostsFilterBlock = ({ scriptBatchRanAt, scriptBatchScriptName, depProfileError, + depAssignProfileResponse, }, selectedLabel, isOnlyObserver, @@ -618,6 +621,65 @@ const HostsFilterBlock = ({ ); }; + const renderDepAssignProfileResponse = () => { + const renderLabel = () => { + switch (depAssignProfileResponse) { + case "SUCCESS": + return "Apple Business Manager (ABM) profile assignment successful"; + case "FAILED": + return "Apple Business Manager (ABM) issue: Failed"; + case "THROTTLED": + return "Apple Business Manager (ABM) issue: Throttled"; + case "NOT_ACCESSIBLE": + return "Apple Business Manager (ABM) issue: Not accessible"; + default: + return "Apple Business Manager (ABM) issues"; + } + }; + + const renderTooltip = () => { + switch (depAssignProfileResponse) { + case "SUCCESS": + return "Hosts that had a successful response from Apple Business Manager (ABM) for profile assignment."; + case "FAILED": + return ( + <> + Migration or new Mac setup won't work. Apple's servers + rejected the request to assign a profile to these hosts. Fleet + will try again every hour. + + ); + case "THROTTLED": + return ( + <> + Migration or new Mac setup won't work. Fleet hit Apple's + API rate limit when preparing the macOS Setup Assistant for these + hosts. Fleet will try again every hour. + + ); + case "NOT_ACCESSIBLE": + return ( + <> + Migration or new Mac setup won't work. Details are not + accessible from Apple Business Manager (ABM). Verify these hosts + are assigned to your MDM server and Fleet has access permissions. + + ); + default: + return abmIssueTooltip(); + } + }; + + return ( + handleClearFilter(["dep_assign_profile_response"])} + /> + ); + }; + const showSelectedLabel = selectedLabel && selectedLabel.type !== "all" && @@ -643,7 +705,8 @@ const HostsFilterBlock = ({ vulnerability || (configProfileStatus && configProfileUUID && configProfile) || (scriptBatchExecutionStatus && scriptBatchExecutionId) || - depProfileError + depProfileError || + depAssignProfileResponse ) { const renderFilterPill = () => { switch (true) { @@ -719,6 +782,8 @@ const HostsFilterBlock = ({ return renderScriptBatchExecutionBlock(); case !!depProfileError: return renderDepProfileError(); + case !!depAssignProfileResponse: + return renderDepAssignProfileResponse(); default: return null; } diff --git a/frontend/services/entities/hosts.ts b/frontend/services/entities/hosts.ts index 0ad95f625e8..369c0bca3b9 100644 --- a/frontend/services/entities/hosts.ts +++ b/frontend/services/entities/hosts.ts @@ -46,6 +46,41 @@ export interface ILoadHostsResponse { mobile_device_management_solution: IMdmSolution; } +export type DepAssignProfileResponse = + | "SUCCESS" + | "FAILED" + | "THROTTLED" + | "NOT_ACCESSIBLE"; + +export interface IDepAssignmentHostResponse { + id: number; + dep_device: { + asset_tag: string; + color: string; + description: string; + device_assigned_by: string; + device_assigned_date: string; + device_family: string; + os: string; + profile_status: string; + profile_assign_time: string; + profile_push_time: string; + profile_uuid: string; + mdm_migration_deadline: string | null; + serial_number: string; + }; + host_dep_assignment: { + assign_profile_response: DepAssignProfileResponse; + profile_uuid: string; + response_updated_at: string; + added_at: string; + deleted_at: string | null; + abm_token_id: number; + mdm_migration_deadline: string; + mdm_migration_completed: string; + }; +} + export type IUnlockHostResponse = | { host_id: number; @@ -104,6 +139,7 @@ export interface ILoadHostsOptions { scriptBatchExecutionStatus?: ScriptBatchHostCountV1; scriptBatchExecutionId?: string; depProfileError?: boolean; + depAssignProfileResponse?: DepAssignProfileResponse; } export interface IExportHostsOptions { @@ -141,6 +177,7 @@ export interface IExportHostsOptions { scriptBatchExecutionStatus?: ScriptBatchHostCountV1; scriptBatchExecutionId?: string; depProfileError?: boolean; + depAssignProfileResponse?: DepAssignProfileResponse; } export interface IActionByFilter { @@ -170,6 +207,7 @@ export interface IActionByFilter { scriptBatchExecutionStatus?: ScriptBatchHostCountV1; scriptBatchExecutionId?: string; depProfileError?: boolean; + depAssignProfileResponse?: DepAssignProfileResponse; } export interface IGetHostSoftwareResponse { @@ -364,6 +402,7 @@ export default { const scriptBatchExecutionStatus = options?.scriptBatchExecutionStatus; const scriptBatchExecutionId = options?.scriptBatchExecutionId; const depProfileError = options?.depProfileError; + const depAssignProfileResponse = options?.depAssignProfileResponse; if (!sortBy.length) { throw Error("sortBy is a required field."); @@ -403,6 +442,7 @@ export default { scriptBatchExecutionStatus, scriptBatchExecutionId, depProfileError, + depAssignProfileResponse, }), status, label_id: label, @@ -449,6 +489,7 @@ export default { scriptBatchExecutionStatus, scriptBatchExecutionId, depProfileError, + depAssignProfileResponse, }: ILoadHostsOptions): Promise => { const label = getLabel(selectedLabels); const sortParams = getSortParams(sortBy); @@ -492,6 +533,7 @@ export default { scriptBatchExecutionStatus, scriptBatchExecutionId, depProfileError, + depAssignProfileResponse, }), }; @@ -563,6 +605,7 @@ export default { diskEncryptionStatus, vulnerability, depProfileError, + depAssignProfileResponse, }: IActionByFilter) => { const { HOSTS_TRANSFER_BY_FILTER } = endpoints; return sendRequest("POST", HOSTS_TRANSFER_BY_FILTER, { @@ -591,6 +634,7 @@ export default { os_settings_disk_encryption: diskEncryptionStatus, vulnerability, dep_profile_error: depProfileError, + dep_assing_profile_response: depAssignProfileResponse, }, }); }, @@ -700,4 +744,8 @@ export default { const path = endpoints.HOST_DEVICE_MAPPING_IDP(hostId); return sendRequest("DELETE", path); }, + getDepAssignment: (id: number) => { + const { HOST_DEP_ASSIGNMENT } = endpoints; + return sendRequest("GET", HOST_DEP_ASSIGNMENT(id)); + }, }; diff --git a/frontend/utilities/endpoints.ts b/frontend/utilities/endpoints.ts index 0189ec19974..264012a60a4 100644 --- a/frontend/utilities/endpoints.ts +++ b/frontend/utilities/endpoints.ts @@ -100,6 +100,8 @@ export default { `/${API_VERSION}/fleet/hosts/${id}/device_mapping`, HOST_DEVICE_MAPPING_IDP: (id: number) => `/${API_VERSION}/fleet/hosts/${id}/device_mapping/idp`, + HOST_DEP_ASSIGNMENT: (id: number) => + `/${API_VERSION}/fleet/hosts/${id}/dep_assignment`, INVITES: `/${API_VERSION}/fleet/invites`, INVITE_VERIFY: (token: string) => `/${API_VERSION}/fleet/invites/${token}`, diff --git a/frontend/utilities/url/index.ts b/frontend/utilities/url/index.ts index 2f1074fd0aa..cbaea6d2760 100644 --- a/frontend/utilities/url/index.ts +++ b/frontend/utilities/url/index.ts @@ -6,6 +6,7 @@ import { MdmProfileStatus, } from "interfaces/mdm"; import { + DepAssignProfileResponse, HOSTS_QUERY_PARAMS, MacSettingsStatusQueryParam, } from "services/entities/hosts"; @@ -51,6 +52,7 @@ interface IMutuallyExclusiveHostParams { scriptBatchExecutionStatus?: string; scriptBatchExecutionId?: string; depProfileError?: boolean; + depAssignProfileResponse?: DepAssignProfileResponse; } export const parseQueryValueToNumberOrUndefined = ( @@ -226,6 +228,7 @@ export const reconcileMutuallyExclusiveHostParams = ({ scriptBatchExecutionStatus, scriptBatchExecutionId, depProfileError, + depAssignProfileResponse, }: IMutuallyExclusiveHostParams): Record => { if (label) { // backend api now allows (label + low disk space) OR (label + mdm id) OR @@ -301,6 +304,8 @@ export const reconcileMutuallyExclusiveHostParams = ({ }; case !!depProfileError: return { dep_profile_error: true }; + case !!depAssignProfileResponse: + return { dep_assign_profile_response: depAssignProfileResponse }; default: return {}; }