update UI to add support for mac ddm profiles (#17730)
relates to #17416 update UI to support new DDM profile types. this includes: - updating Custom settings page - updating the os settings modal - [x] Changes file added for user-visible changes in `changes/` or `orbit/changes/`. See [Changes files](https://fleetdm.com/docs/contributing/committing-changes#changes-files) for more information. - [x] Manual QA for all new/changed functionality
This commit is contained in:
@@ -0,0 +1 @@
|
||||
- update UI to support macos DDM profiles.
|
||||
@@ -10,7 +10,7 @@ const DEFAULT_HOST_PROFILE_MOCK: IHostMdmProfile = {
|
||||
detail: "This is verified",
|
||||
};
|
||||
|
||||
export const createMockHostMacMdmProfile = (
|
||||
export const createMockHostMdmProfile = (
|
||||
overrides?: Partial<IHostMdmProfile>
|
||||
): IHostMdmProfile => {
|
||||
return { ...DEFAULT_HOST_PROFILE_MOCK, ...overrides };
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
+14
-4
@@ -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 = ({
|
||||
<div className={`${subClass}__info`}>
|
||||
<span className={`${subClass}__title`}>{name}</span>
|
||||
<div className={`${subClass}__details`}>
|
||||
<ProfileDetails platform={platform} createdAt={created_at} />
|
||||
<ProfileDetails
|
||||
platform={platform}
|
||||
createdAt={created_at}
|
||||
isDDM={isDDMProfile(profile)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
@@ -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 (
|
||||
<MainContent className={baseClass}>
|
||||
<>
|
||||
|
||||
+31
-21
@@ -22,28 +22,35 @@ type OperationTypeOption = Record<
|
||||
|
||||
type ProfileDisplayConfig = Record<ProfileOperationType, OperationTypeOption>;
|
||||
|
||||
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 = {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
+26
-46
@@ -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<IHostMdmProfile, "status"> {
|
||||
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<IHostMdmProfile, "status"> {
|
||||
status: OsSettingsTableStatusValue;
|
||||
}
|
||||
|
||||
interface ICellProps {
|
||||
cell: {
|
||||
value: string;
|
||||
};
|
||||
row: {
|
||||
original: ITableRowOsSettings;
|
||||
};
|
||||
}
|
||||
type ITableColumnConfig = Column<IHostMdmProfileWithAddedStatus>;
|
||||
type ITableStringCellProps = IStringCellProps<IHostMdmProfileWithAddedStatus>;
|
||||
|
||||
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 => (
|
||||
<TextCell value={cellProps.cell.value} />
|
||||
),
|
||||
Cell: (cellProps: ITableStringCellProps) => {
|
||||
return <TextCell value={cellProps.cell.value} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Status",
|
||||
Header: "Status",
|
||||
disableSortBy: true,
|
||||
accessor: "statusText",
|
||||
Cell: (cellProps: ICellProps) => {
|
||||
accessor: "status",
|
||||
Cell: (cellProps: ITableStringCellProps) => {
|
||||
return (
|
||||
<OSSettingStatusCell
|
||||
status={cellProps.row.original.status}
|
||||
@@ -133,11 +117,10 @@ const tableHeaders: IDataColumn[] = [
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Error",
|
||||
Header: "Error",
|
||||
disableSortBy: true,
|
||||
accessor: "detail",
|
||||
Cell: (cellProps: ICellProps): JSX.Element => {
|
||||
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
|
||||
|
||||
+2
-1
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user