diff --git a/changes/issue-17416-update-ui-to-support-ddm b/changes/issue-17416-update-ui-to-support-ddm new file mode 100644 index 000000000000..3bbe4eaaa930 --- /dev/null +++ b/changes/issue-17416-update-ui-to-support-ddm @@ -0,0 +1 @@ +- update UI to support macos DDM profiles. diff --git a/frontend/__mocks__/hostMock.ts b/frontend/__mocks__/hostMock.ts index ebd2bb37eb09..5912ef451c5f 100644 --- a/frontend/__mocks__/hostMock.ts +++ b/frontend/__mocks__/hostMock.ts @@ -10,7 +10,7 @@ const DEFAULT_HOST_PROFILE_MOCK: IHostMdmProfile = { detail: "This is verified", }; -export const createMockHostMacMdmProfile = ( +export const createMockHostMdmProfile = ( overrides?: Partial ): IHostMdmProfile => { return { ...DEFAULT_HOST_PROFILE_MOCK, ...overrides }; diff --git a/frontend/interfaces/mdm.ts b/frontend/interfaces/mdm.ts index c7b4071fac08..10d87ed75fb2 100644 --- a/frontend/interfaces/mdm.ts +++ b/frontend/interfaces/mdm.ts @@ -81,6 +81,11 @@ export interface IMdmProfile { } export type MdmProfileStatus = "verified" | "verifying" | "pending" | "failed"; +export type MdmDDMProfileStatus = + | "success" + | "pending" + | "failed" + | "acknowledged"; export type ProfileOperationType = "remove" | "install"; @@ -89,7 +94,7 @@ export interface IHostMdmProfile { name: string; operation_type: ProfileOperationType | null; platform: ProfilePlatform; - status: MdmProfileStatus; + status: MdmProfileStatus | MdmDDMProfileStatus; detail: string; } diff --git a/frontend/pages/ManageControlsPage/OSSettings/ProfileStatusAggregate/ProfileStatusAggregateOptions.ts b/frontend/pages/ManageControlsPage/OSSettings/ProfileStatusAggregate/ProfileStatusAggregateOptions.ts index 8dbe94abf8fe..c2a917fb59ff 100644 --- a/frontend/pages/ManageControlsPage/OSSettings/ProfileStatusAggregate/ProfileStatusAggregateOptions.ts +++ b/frontend/pages/ManageControlsPage/OSSettings/ProfileStatusAggregate/ProfileStatusAggregateOptions.ts @@ -14,7 +14,8 @@ const AGGREGATE_STATUS_DISPLAY_OPTIONS: IAggregateDisplayOption[] = [ text: "Verified", iconName: "success", tooltipText: - "These hosts applied all OS settings. Fleet verified with osquery.", + "These hosts applied all OS settings. Fleet verified with osquery. " + + "Declaration profiles are verified with DDM.", }, { value: "verifying", diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/_styles.scss b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/_styles.scss index 3c869da34554..9f6c3eef81fe 100644 --- a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/_styles.scss +++ b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/_styles.scss @@ -186,7 +186,7 @@ flex-direction: column; border-radius: $border-radius; border: 1px solid $ui-fleet-black-10; - overflow-y: scroll; + overflow-y: auto; .loading-spinner { margin: 69.5px auto; diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileListItem/ProfileListItem.tsx b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileListItem/ProfileListItem.tsx index 15b76767010a..51c185c5218b 100644 --- a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileListItem/ProfileListItem.tsx +++ b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileListItem/ProfileListItem.tsx @@ -4,7 +4,7 @@ import FileSaver from "file-saver"; import classnames from "classnames"; import { IMdmProfile } from "interfaces/mdm"; -import mdmAPI from "services/entities/mdm"; +import mdmAPI, { isDDMProfile } from "services/entities/mdm"; import Button from "components/buttons/Button"; import Graphic from "components/Graphic"; @@ -29,11 +29,17 @@ const LabelCount = ({ interface IProfileDetailsProps { platform: string; createdAt: string; + isDDM?: boolean; } -const ProfileDetails = ({ platform, createdAt }: IProfileDetailsProps) => { +const ProfileDetails = ({ + platform, + createdAt, + isDDM, +}: IProfileDetailsProps) => { const getPlatformName = () => { - return platform === "darwin" ? "macOS" : "Windows"; + if (platform === "windows") return "Windows"; + return isDDM ? "macOS (declaration)" : "macOS"; }; return ( @@ -81,7 +87,11 @@ const ProfileListItem = ({
{name}
- +
diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileGraphic.tsx b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileGraphic.tsx index c0460e83dd5f..c75193dcb6cd 100644 --- a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileGraphic.tsx +++ b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileGraphic.tsx @@ -3,7 +3,7 @@ import React from "react"; import Graphic from "components/Graphic"; const ALLOWED_FILE_TYPES_MESSAGE = - "Configuration profile (.mobileconfig for macOS or .xml for Windows)"; + "Configuration profile (.mobileconfig and .json for macOS or .xml for Windows)"; const ProfileGraphic = ({ baseClass, diff --git a/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx b/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx index 99faef93dda8..11b2178bcf50 100644 --- a/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx +++ b/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx @@ -13,7 +13,6 @@ import { QueryContext } from "context/query"; import { NotificationContext } from "context/notification"; import activitiesAPI, { - IActivitiesResponse, IPastActivitiesResponse, IUpcomingActivitiesResponse, } from "services/entities/activities"; @@ -53,6 +52,7 @@ import { HOST_ABOUT_DATA, HOST_OSQUERY_DATA, } from "utilities/constants"; +import { createMockHostMdmProfile } from "__mocks__/hostMock"; import Spinner from "components/Spinner"; import TabsWrapper from "components/TabsWrapper"; @@ -753,6 +753,30 @@ const HostDetailsPage = ({ name: host?.mdm.macos_setup?.bootstrap_package_name, }; + // TODO: Remove this when API is ready + if (!host.mdm.profiles) { + host.mdm.profiles = []; + } else { + host.mdm.profiles = [ + createMockHostMdmProfile({ + name: "test.json", + status: "success", + }), + createMockHostMdmProfile({ + name: "test2.json", + status: "pending", + }), + createMockHostMdmProfile({ + name: "test3.json", + status: "failed", + }), + createMockHostMdmProfile({ + name: "test4.json", + status: "acknowledged", + }), + ]; + } + return ( <> diff --git a/frontend/pages/hosts/details/OSSettingsModal/OSSettingsTable/OSSettingStatusCell/helpers.ts b/frontend/pages/hosts/details/OSSettingsModal/OSSettingsTable/OSSettingStatusCell/helpers.ts index da989801ef48..8f5dd764ee34 100644 --- a/frontend/pages/hosts/details/OSSettingsModal/OSSettingsTable/OSSettingStatusCell/helpers.ts +++ b/frontend/pages/hosts/details/OSSettingsModal/OSSettingsTable/OSSettingStatusCell/helpers.ts @@ -22,28 +22,35 @@ type OperationTypeOption = Record< type ProfileDisplayConfig = Record; +const MAC_PROFILE_VERIFIED_DISPLAY_CONFIG: ProfileDisplayOption = { + statusText: "Verified", + iconName: "success", + tooltip: (innerProps) => + innerProps.isDiskEncryptionProfile + ? "The host turned disk encryption on and sent the key to Fleet. " + + "Fleet verified with osquery." + : "The host applied the setting. Fleet verified with osquery. " + + "Declaration profiles are verified with DDM.", +} as const; + +const MAC_PROFILE_VERIFYING_DISPLAY_CONFIG: ProfileDisplayOption = { + statusText: "Verifying", + iconName: "success-outline", + tooltip: (innerProps) => + innerProps.isDiskEncryptionProfile + ? "The host acknowledged the MDM command to turn on disk encryption. " + + "Fleet is verifying with osquery and retrieving the disk encryption key. " + + "This may take up to one hour." + : "The host acknowledged the MDM command to apply the setting. Fleet is " + + "verifying with osquery.", +} as const; + export const PROFILE_DISPLAY_CONFIG: ProfileDisplayConfig = { install: { - verified: { - statusText: "Verified", - iconName: "success", - tooltip: (innerProps) => - innerProps.isDiskEncryptionProfile - ? "The host turned disk encryption on and sent the key to Fleet. " + - "Fleet verified with osquery." - : "The host applied the setting. Fleet verified with osquery.", - }, - verifying: { - statusText: "Verifying", - iconName: "success-outline", - tooltip: (innerProps) => - innerProps.isDiskEncryptionProfile - ? "The host acknowledged the MDM command to turn on disk encryption. " + - "Fleet is verifying with osquery and retrieving the disk encryption key. " + - "This may take up to one hour." - : "The host acknowledged the MDM command to apply the setting. Fleet is " + - "verifying with osquery.", - }, + verified: MAC_PROFILE_VERIFIED_DISPLAY_CONFIG, + success: MAC_PROFILE_VERIFIED_DISPLAY_CONFIG, + verifying: MAC_PROFILE_VERIFYING_DISPLAY_CONFIG, + acknowledged: MAC_PROFILE_VERIFYING_DISPLAY_CONFIG, pending: { statusText: "Enforcing (pending)", iconName: "pending-outline", @@ -79,6 +86,8 @@ export const PROFILE_DISPLAY_CONFIG: ProfileDisplayConfig = { action_required: null, // should not be reached verified: null, // should not be reached verifying: null, // should not be reached + success: null, // should not be reached + acknowledged: null, // should not be reached failed: { statusText: "Failed", iconName: "error", @@ -89,7 +98,8 @@ export const PROFILE_DISPLAY_CONFIG: ProfileDisplayConfig = { type WindowsDiskEncryptionDisplayConfig = Omit< OperationTypeOption, - "action_required" + // windows disk encryption does not have these states + "action_required" | "success" | "acknowledged" >; export const WINDOWS_DISK_ENCRYPTION_DISPLAY_CONFIG: WindowsDiskEncryptionDisplayConfig = { diff --git a/frontend/pages/hosts/details/OSSettingsModal/OSSettingsTable/OSSettingsTable.tsx b/frontend/pages/hosts/details/OSSettingsModal/OSSettingsTable/OSSettingsTable.tsx index d96606e3576e..7aea4d34ad81 100644 --- a/frontend/pages/hosts/details/OSSettingsModal/OSSettingsTable/OSSettingsTable.tsx +++ b/frontend/pages/hosts/details/OSSettingsModal/OSSettingsTable/OSSettingsTable.tsx @@ -1,12 +1,14 @@ import React from "react"; import TableContainer from "components/TableContainer"; -import tableHeaders, { ITableRowOsSettings } from "./OSSettingsTableConfig"; +import tableHeaders, { + IHostMdmProfileWithAddedStatus, +} from "./OSSettingsTableConfig"; const baseClass = "os-settings-table"; interface IOSSettingsTableProps { - tableData?: ITableRowOsSettings[]; + tableData?: IHostMdmProfileWithAddedStatus[]; } const OSSettingsTable = ({ tableData }: IOSSettingsTableProps) => { diff --git a/frontend/pages/hosts/details/OSSettingsModal/OSSettingsTable/OSSettingsTableConfig.tsx b/frontend/pages/hosts/details/OSSettingsModal/OSSettingsTable/OSSettingsTableConfig.tsx index 005a8e7e726a..5c94da95ab37 100644 --- a/frontend/pages/hosts/details/OSSettingsModal/OSSettingsTable/OSSettingsTableConfig.tsx +++ b/frontend/pages/hosts/details/OSSettingsModal/OSSettingsTable/OSSettingsTableConfig.tsx @@ -1,58 +1,44 @@ -import TextCell from "components/TableContainer/DataTable/TextCell"; import React from "react"; +import { Column } from "react-table"; +import { IStringCellProps } from "interfaces/datatable_config"; import { IHostMdmData } from "interfaces/host"; import { FLEET_FILEVAULT_PROFILE_DISPLAY_NAME, // FLEET_FILEVAULT_PROFILE_IDENTIFIER, IHostMdmProfile, + MdmDDMProfileStatus, MdmProfileStatus, ProfilePlatform, isWindowsDiskEncryptionStatus, } from "interfaces/mdm"; import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants"; + +import TextCell from "components/TableContainer/DataTable/TextCell"; import TooltipTruncatedTextCell from "components/TableContainer/DataTable/TooltipTruncatedTextCell"; + import OSSettingStatusCell from "./OSSettingStatusCell"; import { generateWinDiskEncryptionProfile } from "../../helpers"; -export interface ITableRowOsSettings extends Omit { - status: OsSettingsTableStatusValue; -} - -export type OsSettingsTableStatusValue = MdmProfileStatus | "action_required"; - export const isMdmProfileStatus = ( status: string ): status is MdmProfileStatus => { return status !== "action_required"; }; -interface IHeaderProps { - column: { - title: string; - isSortedDesc: boolean; - }; +export interface IHostMdmProfileWithAddedStatus + extends Omit { + status: OsSettingsTableStatusValue; } -interface ICellProps { - cell: { - value: string; - }; - row: { - original: ITableRowOsSettings; - }; -} +type ITableColumnConfig = Column; +type ITableStringCellProps = IStringCellProps; -interface IDataColumn { - Header: ((props: IHeaderProps) => JSX.Element) | string; - Cell: (props: ICellProps) => JSX.Element; - id?: string; - title?: string; - accessor?: string; - disableHidden?: boolean; - disableSortBy?: boolean; - sortType?: string; -} +export type INonDDMProfileStatus = MdmProfileStatus | "action_required"; + +export type OsSettingsTableStatusValue = + | MdmDDMProfileStatus + | INonDDMProfileStatus; /** * generates the formatted tooltip for the error column. @@ -107,22 +93,20 @@ const generateErrorTooltip = ( return generateFormattedTooltip(detail); }; -const tableHeaders: IDataColumn[] = [ +const tableHeaders: ITableColumnConfig[] = [ { - title: "Name", Header: "Name", disableSortBy: true, accessor: "name", - Cell: (cellProps: ICellProps): JSX.Element => ( - - ), + Cell: (cellProps: ITableStringCellProps) => { + return ; + }, }, { - title: "Status", Header: "Status", disableSortBy: true, - accessor: "statusText", - Cell: (cellProps: ICellProps) => { + accessor: "status", + Cell: (cellProps: ITableStringCellProps) => { return ( { + Cell: (cellProps: ITableStringCellProps): JSX.Element => { const profile = cellProps.row.original; const value = @@ -165,7 +148,7 @@ const tableHeaders: IDataColumn[] = [ ]; const makeWindowsRows = ({ profiles, os_settings }: IHostMdmData) => { - const rows: ITableRowOsSettings[] = []; + const rows: IHostMdmProfileWithAddedStatus[] = []; if (profiles) { rows.push(...profiles); @@ -190,15 +173,12 @@ const makeWindowsRows = ({ profiles, os_settings }: IHostMdmData) => { return rows; }; -const makeDarwinRows = ({ - profiles, - macos_settings, -}: IHostMdmData): ITableRowOsSettings[] | null => { +const makeDarwinRows = ({ profiles, macos_settings }: IHostMdmData) => { if (!profiles) { return null; } - let rows: ITableRowOsSettings[] = profiles; + let rows: IHostMdmProfileWithAddedStatus[] = profiles; if (macos_settings?.disk_encryption === "action_required") { rows = profiles.map((p) => { // TODO: this is a brittle check for the filevault profile diff --git a/frontend/pages/hosts/details/cards/HostSummary/OSSettingsIndicator/OSSettingsIndicator.tsx b/frontend/pages/hosts/details/cards/HostSummary/OSSettingsIndicator/OSSettingsIndicator.tsx index 6f90c372ab2d..68043e52a7f2 100644 --- a/frontend/pages/hosts/details/cards/HostSummary/OSSettingsIndicator/OSSettingsIndicator.tsx +++ b/frontend/pages/hosts/details/cards/HostSummary/OSSettingsIndicator/OSSettingsIndicator.tsx @@ -32,7 +32,8 @@ const STATUS_DISPLAY_OPTIONS: StatusDisplayOptions = { Verified: { iconName: "success", tooltipText: - "The host applied all OS settings. Fleet verified with osquery.", + "The host applied all OS settings. Fleet verified with osquery. " + + "Declaration profiles are verified with DDM.", }, Verifying: { iconName: "success-outline", diff --git a/frontend/services/entities/mdm.ts b/frontend/services/entities/mdm.ts index 29a8e5fab908..6f9a2503b817 100644 --- a/frontend/services/entities/mdm.ts +++ b/frontend/services/entities/mdm.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { DiskEncryptionStatus, + IHostMdmProfile, IMdmProfile, MdmProfileStatus, } from "interfaces/mdm"; @@ -46,6 +47,10 @@ export interface IUploadProfileApiParams { labels?: string[]; } +export const isDDMProfile = (profile: IMdmProfile | IHostMdmProfile) => { + return profile.profile_uuid.startsWith("d"); +}; + const mdmService = { downloadDeviceUserEnrollmentProfile: (token: string) => { const { DEVICE_USER_MDM_ENROLLMENT_PROFILE } = endpoints;