Feat UI add verifying status to mdm (#11311)

This commit is contained in:
Gabriel Hernandez 2023-04-26 19:31:38 +01:00 committed by GitHub
parent 4d1beef728
commit 4866bccb3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 289 additions and 203 deletions

View File

@ -0,0 +1 @@
- add verifying status for mdm profiles

View File

@ -9,10 +9,15 @@ import { COLORS } from "styles/var/colors";
const baseClass = "status-indicator-with-icon"; const baseClass = "status-indicator-with-icon";
type Status = "success" | "pending" | "error"; export type IndicatorStatus =
| "success"
| "successPartial"
| "pending"
| "pendingPartial"
| "error";
interface IStatusIndicatorWithIconProps { interface IStatusIndicatorWithIconProps {
status: Status; status: IndicatorStatus;
value: string; value: string;
tooltip?: { tooltip?: {
tooltipText: string | JSX.Element; tooltipText: string | JSX.Element;
@ -21,9 +26,11 @@ interface IStatusIndicatorWithIconProps {
className?: string; className?: string;
} }
const statusIconNameMapping: Record<Status, IconNames> = { const statusIconNameMapping: Record<IndicatorStatus, IconNames> = {
success: "success", success: "success",
successPartial: "success-partial",
pending: "pending", pending: "pending",
pendingPartial: "pending-partial",
error: "error", error: "error",
}; };

View File

@ -0,0 +1,20 @@
import React from "react";
const PendingPartial = () => {
return (
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="9" cy="9" r="8" stroke="#8B8FA2" strokeWidth="2" />
<circle cx="5.6665" cy="9" r="1" fill="#8B8FA2" />
<circle cx="8.6665" cy="9" r="1" fill="#8B8FA2" />
<circle cx="11.6665" cy="9" r="1" fill="#8B8FA2" />
</svg>
);
};
export default PendingPartial;

View File

@ -0,0 +1,24 @@
import React from "react";
const SuccessPartial = () => {
return (
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<ellipse cx="9" cy="9" rx="8" ry="8" stroke="#3DB67B" strokeWidth="2" />
<path
d="M6 10L8 12L12 6"
stroke="#3DB67B"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
export default SuccessPartial;

View File

@ -16,7 +16,6 @@ import EmptyTeams from "./EmptyTeams";
import ExternalLink from "./ExternalLink"; import ExternalLink from "./ExternalLink";
import Issue from "./Issue"; import Issue from "./Issue";
import Plus from "./Plus"; import Plus from "./Plus";
import Pending from "./Pending";
import PremiumFeature from "./PremiumFeature"; import PremiumFeature from "./PremiumFeature";
import LowDiskSpaceHosts from "./LowDiskSpaceHosts"; import LowDiskSpaceHosts from "./LowDiskSpaceHosts";
@ -37,8 +36,12 @@ import ApplePurple from "./ApplePurple";
import LinuxGreen from "./LinuxGreen"; import LinuxGreen from "./LinuxGreen";
import WindowsBlue from "./WindowsBlue"; import WindowsBlue from "./WindowsBlue";
import Error from "./Error"; // Status Icons
import Success from "./Success"; import Success from "./Success";
import SuccessPartial from "./SuccessPartial";
import Pending from "./Pending";
import PendingPartial from "./PendingPartial";
import Error from "./Error";
import Clipboard from "./Clipboard"; import Clipboard from "./Clipboard";
import Eye from "./Eye"; import Eye from "./Eye";
@ -79,9 +82,11 @@ export const ICON_MAP = {
clipboard: Clipboard, clipboard: Clipboard,
eye: Eye, eye: Eye,
pencil: Pencil, pencil: Pencil,
pending: Pending,
trash: TrashCan, trash: TrashCan,
success: Success, success: Success,
"success-partial": SuccessPartial,
pending: Pending,
"pending-partial": PendingPartial,
error: Error, error: Error,
darwin: Apple, darwin: Apple,
macOS: Apple, macOS: Apple,

View File

@ -7,7 +7,7 @@ import softwareInterface, { ISoftware } from "./software";
import hostQueryResult from "./campaign"; import hostQueryResult from "./campaign";
import queryStatsInterface, { IQueryStats } from "./query_stats"; import queryStatsInterface, { IQueryStats } from "./query_stats";
import { ILicense, IDeviceGlobalConfig } from "./config"; import { ILicense, IDeviceGlobalConfig } from "./config";
import { IMacSettings, MdmEnrollmentStatus } from "./mdm"; import { IHostMacMdmProfile, MdmEnrollmentStatus } from "./mdm";
export default PropTypes.shape({ export default PropTypes.shape({
created_at: PropTypes.string, created_at: PropTypes.string,
@ -107,7 +107,7 @@ export interface IHostMdmData {
name?: string; name?: string;
server_url: string; server_url: string;
id?: number; id?: number;
profiles: IMacSettings; profiles: IHostMacMdmProfile[];
macos_settings: IMdmMacOsSettings; macos_settings: IMdmMacOsSettings;
} }

View File

@ -68,33 +68,38 @@ export interface IMdmProfilesResponse {
profiles: IMdmProfile[] | null; profiles: IMdmProfile[] | null;
} }
export type MacMdmProfileStatus = "applied" | "pending" | "failed"; export enum MdmProfileStatus {
VERIFYING = "verifying",
PENDING = "pending",
FAILED = "failed",
}
export type MacMdmProfileOperationType = "remove" | "install"; export type MacMdmProfileOperationType = "remove" | "install";
export interface IHostMacMdmProfile { export interface IHostMacMdmProfile {
profile_id: number; profile_id: number;
name: string; name: string;
operation_type: MacMdmProfileOperationType; operation_type: MacMdmProfileOperationType;
status: MacMdmProfileStatus; status: MdmProfileStatus;
detail: string; detail: string;
} }
export type IMacSettings = IHostMacMdmProfile[];
export type MacSettingsStatus = "Failing" | "Latest" | "Pending";
export interface IAggregateMacSettingsStatus { export interface IFileVaultSummaryResponse {
latest: number; verifying: number;
pending: number;
failing: number;
}
export interface IDiskEncryptionStatusAggregate {
applied: number;
action_required: number; action_required: number;
enforcing: number; enforcing: number;
failed: number; failed: number;
removing_enforcement: number; removing_enforcement: number;
} }
export enum FileVaultProfileStatus {
VERIFYING = "verifying",
ACTION_REQUIRED = "action_required",
ENFORCING = "enforcing",
FAILED = "failed",
REMOVING_ENFORCEMENT = "removing_enforcement",
}
// TODO: update when we have API // TODO: update when we have API
export interface IMdmScript { export interface IMdmScript {
id: number; id: number;

View File

@ -1,4 +1,5 @@
import { IAggregateMacSettingsStatus } from "interfaces/mdm"; import { IconNames } from "components/icons";
import { MdmProfileStatus } from "interfaces/mdm";
import MacSettingsIndicator from "pages/hosts/details/MacSettingsIndicator"; import MacSettingsIndicator from "pages/hosts/details/MacSettingsIndicator";
import React from "react"; import React from "react";
import { useQuery } from "react-query"; import { useQuery } from "react-query";
@ -8,6 +9,39 @@ import { buildQueryStringFromParams } from "utilities/url";
const baseClass = "aggregate-mac-settings-indicators"; const baseClass = "aggregate-mac-settings-indicators";
interface IAggregateDisplayOption {
value: MdmProfileStatus;
text: string;
iconName: IconNames;
tooltipText: string;
}
const AGGREGATE_STATUS_DISPLAY_OPTIONS: IAggregateDisplayOption[] = [
{
value: MdmProfileStatus.VERIFYING,
text: "Verifying",
iconName: "success-partial",
tooltipText:
"Hosts that told Fleet all settings are enforced. Fleet is verifying.",
},
{
value: MdmProfileStatus.PENDING,
text: "Pending",
iconName: "pending-partial",
tooltipText:
"Hosts that will have settings enforced when the hosts come online.",
},
{
value: MdmProfileStatus.FAILED,
text: "Failed",
iconName: "error",
tooltipText:
"Hosts that failed to apply settings. Click on a host to view error(s).",
},
];
type ProfileSummaryResponse = Record<MdmProfileStatus, number>;
interface AggregateMacSettingsIndicatorsProps { interface AggregateMacSettingsIndicatorsProps {
teamId: number; teamId: number;
} }
@ -15,52 +49,22 @@ interface AggregateMacSettingsIndicatorsProps {
const AggregateMacSettingsIndicators = ({ const AggregateMacSettingsIndicators = ({
teamId, teamId,
}: AggregateMacSettingsIndicatorsProps) => { }: AggregateMacSettingsIndicatorsProps) => {
const AGGREGATE_STATUS_DISPLAY_OPTIONS = {
latest: {
text: "Latest",
iconName: "success",
tooltipText: "Hosts that applied the latest settings.",
},
pending: {
text: "Pending",
iconName: "pending",
tooltipText:
"Hosts that havent applied the latest settings because they are asleep, disconnected from the internet, or require action.",
},
failing: {
text: "Failing",
iconName: "error",
tooltipText:
"Hosts that failed to apply the latest settings. View hosts to see errors.",
},
} as const;
const { const {
data: aggregateProfileStatusesResponse, data: aggregateProfileStatusesResponse,
} = useQuery<IAggregateMacSettingsStatus>( } = useQuery<ProfileSummaryResponse>(
["aggregateProfileStatuses", teamId], ["aggregateProfileStatuses", teamId],
() => mdmAPI.getAggregateProfileStatuses(teamId), () => mdmAPI.getAggregateProfileStatuses(teamId),
{ refetchOnWindowFocus: false } { refetchOnWindowFocus: false }
); );
const DISPLAY_ORDER = ["latest", "pending", "failing"] as const; if (!aggregateProfileStatusesResponse) return null;
const orderedResponseKVArr: [
keyof IAggregateMacSettingsStatus,
number
][] = aggregateProfileStatusesResponse
? DISPLAY_ORDER.map((key) => {
return [key, aggregateProfileStatusesResponse[key]];
})
: [];
const indicators = orderedResponseKVArr.map(([status, count]) => { const indicators = AGGREGATE_STATUS_DISPLAY_OPTIONS.map((status) => {
const { text, iconName, tooltipText } = AGGREGATE_STATUS_DISPLAY_OPTIONS[ const { value, text, iconName, tooltipText } = status;
status const count = aggregateProfileStatusesResponse[value];
];
return ( return (
<div className="aggregate-mac-settings-indicator"> <div className="aggregate-mac-settings-indicator">
{/* NOTE - below will be renamed as a general component and moved into the components dir by Gabe */}
<MacSettingsIndicator <MacSettingsIndicator
indicatorText={text} indicatorText={text}
iconName={iconName} iconName={iconName}
@ -69,7 +73,7 @@ const AggregateMacSettingsIndicators = ({
<a <a
href={`${paths.MANAGE_HOSTS}?${buildQueryStringFromParams({ href={`${paths.MANAGE_HOSTS}?${buildQueryStringFromParams({
team_id: teamId, team_id: teamId,
macos_settings: status, macos_settings: value,
})}`} })}`}
> >
{count} hosts {count} hosts

View File

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import { useQuery } from "react-query"; import { useQuery } from "react-query";
import { IDiskEncryptionStatusAggregate } from "interfaces/mdm"; import { IFileVaultSummaryResponse } from "interfaces/mdm";
import mdmAPI from "services/entities/mdm"; import mdmAPI from "services/entities/mdm";
import TableContainer from "components/TableContainer"; import TableContainer from "components/TableContainer";
@ -24,9 +24,9 @@ const DEFAULT_SORT_DIRECTION = "asc";
const DiskEncryptionTable = ({ currentTeamId }: IDiskEncryptionTableProps) => { const DiskEncryptionTable = ({ currentTeamId }: IDiskEncryptionTableProps) => {
const { data, error } = useQuery< const { data, error } = useQuery<
IDiskEncryptionStatusAggregate, IFileVaultSummaryResponse,
Error, Error,
IDiskEncryptionStatusAggregate IFileVaultSummaryResponse
>( >(
["disk-encryption-summary", currentTeamId], ["disk-encryption-summary", currentTeamId],
() => mdmAPI.getDiskEncryptionAggregate(currentTeamId), () => mdmAPI.getDiskEncryptionAggregate(currentTeamId),

View File

@ -1,17 +1,20 @@
import React from "react"; import React from "react";
import { IDiskEncryptionStatusAggregate } from "interfaces/mdm"; import {
import { DiskEncryptionStatus } from "utilities/constants"; FileVaultProfileStatus,
IFileVaultSummaryResponse,
} from "interfaces/mdm";
import TextCell from "components/TableContainer/DataTable/TextCell"; import TextCell from "components/TableContainer/DataTable/TextCell";
import HeaderCell from "components/TableContainer/DataTable/HeaderCell"; import HeaderCell from "components/TableContainer/DataTable/HeaderCell";
import StatusIndicatorWithIcon from "components/StatusIndicatorWithIcon"; import StatusIndicatorWithIcon from "components/StatusIndicatorWithIcon";
import ViewAllHostsLink from "components/ViewAllHostsLink"; import ViewAllHostsLink from "components/ViewAllHostsLink";
import { IndicatorStatus } from "components/StatusIndicatorWithIcon/StatusIndicatorWithIcon";
interface IStatusCellValue { interface IStatusCellValue {
displayName: string; displayName: string;
statusName: "success" | "pending" | "error"; statusName: IndicatorStatus;
value: DiskEncryptionStatus; value: FileVaultProfileStatus;
tooltip?: string | JSX.Element; tooltip?: string | JSX.Element;
} }
@ -100,7 +103,7 @@ const defaultTableHeaders: IDataColumn[] = [
}, },
]; ];
type StatusNames = keyof IDiskEncryptionStatusAggregate; type StatusNames = keyof IFileVaultSummaryResponse;
type StatusEntry = [StatusNames, number]; type StatusEntry = [StatusNames, number];
@ -108,17 +111,17 @@ export const generateTableHeaders = (): IDataColumn[] => {
return defaultTableHeaders; return defaultTableHeaders;
}; };
const STATUS_CELL_VALUES: Record<StatusNames, IStatusCellValue> = { const STATUS_CELL_VALUES: Record<FileVaultProfileStatus, IStatusCellValue> = {
applied: { verifying: {
displayName: "Applied", displayName: "Verifying",
statusName: "success", statusName: "successPartial",
value: DiskEncryptionStatus.APPLIED, value: FileVaultProfileStatus.VERIFYING,
tooltip: "Disk encryption on and key stored in Fleet.", tooltip: "Disk encryption on and key stored in Fleet. Fleet will verify.",
}, },
action_required: { action_required: {
displayName: "Action required (pending)", displayName: "Action required (pending)",
statusName: "pending", statusName: "pendingPartial",
value: DiskEncryptionStatus.ACTION_REQUIRED, value: FileVaultProfileStatus.ACTION_REQUIRED,
tooltip: ( tooltip: (
<> <>
Ask the end user to follow <b>Disk encryption</b> instructions on their{" "} Ask the end user to follow <b>Disk encryption</b> instructions on their{" "}
@ -128,25 +131,25 @@ const STATUS_CELL_VALUES: Record<StatusNames, IStatusCellValue> = {
}, },
enforcing: { enforcing: {
displayName: "Enforcing (pending)", displayName: "Enforcing (pending)",
statusName: "pending", statusName: "pendingPartial",
value: DiskEncryptionStatus.ENFORCING, value: FileVaultProfileStatus.ENFORCING,
tooltip: "Setting will be enforced when the hosts come online.", tooltip: "Setting will be enforced when the hosts come online.",
}, },
failed: { failed: {
displayName: "Failed", displayName: "Failed",
statusName: "error", statusName: "error",
value: DiskEncryptionStatus.FAILED, value: FileVaultProfileStatus.FAILED,
}, },
removing_enforcement: { removing_enforcement: {
displayName: "Removing enforcement (pending)", displayName: "Removing enforcement (pending)",
statusName: "pending", statusName: "pendingPartial",
value: DiskEncryptionStatus.REMOVING_ENFORCEMENT, value: FileVaultProfileStatus.REMOVING_ENFORCEMENT,
tooltip: "Enforcement will be removed when the hosts come online.", tooltip: "Enforcement will be removed when the hosts come online.",
}, },
}; };
export const generateTableData = ( export const generateTableData = (
data?: IDiskEncryptionStatusAggregate, data?: IFileVaultSummaryResponse,
currentTeamId?: number currentTeamId?: number
) => { ) => {
if (!data) return []; if (!data) return [];

View File

@ -48,10 +48,10 @@ import { IOperatingSystemVersion } from "interfaces/operating_system";
import { IPolicy, IStoredPolicyResponse } from "interfaces/policy"; import { IPolicy, IStoredPolicyResponse } from "interfaces/policy";
import { ITeam } from "interfaces/team"; import { ITeam } from "interfaces/team";
import { IEmptyTableProps } from "interfaces/empty_table"; import { IEmptyTableProps } from "interfaces/empty_table";
import { FileVaultProfileStatus } from "interfaces/mdm";
import sortUtils from "utilities/sort"; import sortUtils from "utilities/sort";
import { import {
DiskEncryptionStatus,
HOSTS_SEARCH_BOX_PLACEHOLDER, HOSTS_SEARCH_BOX_PLACEHOLDER,
HOSTS_SEARCH_BOX_TOOLTIP, HOSTS_SEARCH_BOX_TOOLTIP,
PolicyResponse, PolicyResponse,
@ -245,7 +245,7 @@ const ManageHostsPage = ({
? parseInt(queryParams.low_disk_space, 10) ? parseInt(queryParams.low_disk_space, 10)
: undefined; : undefined;
const missingHosts = queryParams?.status === "missing"; const missingHosts = queryParams?.status === "missing";
const diskEncryptionStatus: DiskEncryptionStatus | undefined = const diskEncryptionStatus: FileVaultProfileStatus | undefined =
queryParams?.macos_settings_disk_encryption; queryParams?.macos_settings_disk_encryption;
// ========= routeParams // ========= routeParams
@ -562,7 +562,7 @@ const ManageHostsPage = ({
}; };
const handleChangeDiskEncryptionStatusFilter = ( const handleChangeDiskEncryptionStatusFilter = (
newStatus: DiskEncryptionStatus newStatus: FileVaultProfileStatus
) => { ) => {
handleResetPageIndex(); handleResetPageIndex();

View File

@ -1,44 +1,44 @@
import React from "react"; import React from "react";
import { IDropdownOption } from "interfaces/dropdownOption"; import { IDropdownOption } from "interfaces/dropdownOption";
import { DiskEncryptionStatus } from "utilities/constants";
// @ts-ignore // @ts-ignore
import Dropdown from "components/forms/fields/Dropdown"; import Dropdown from "components/forms/fields/Dropdown";
import { FileVaultProfileStatus } from "interfaces/mdm";
const baseClass = "disk-encryption-status-filter"; const baseClass = "disk-encryption-status-filter";
const DISK_ENCRYPTION_STATUS_OPTIONS: IDropdownOption[] = [ const DISK_ENCRYPTION_STATUS_OPTIONS: IDropdownOption[] = [
{ {
disabled: false, disabled: false,
label: "Applied", label: "Verifying",
value: DiskEncryptionStatus.APPLIED, value: FileVaultProfileStatus.VERIFYING,
}, },
{ {
disabled: false, disabled: false,
label: "Action required", label: "Action required",
value: DiskEncryptionStatus.ACTION_REQUIRED, value: FileVaultProfileStatus.ACTION_REQUIRED,
}, },
{ {
disabled: false, disabled: false,
label: "Enforcing", label: "Enforcing",
value: DiskEncryptionStatus.ENFORCING, value: FileVaultProfileStatus.ENFORCING,
}, },
{ {
disabled: false, disabled: false,
label: "Failed", label: "Failed",
value: DiskEncryptionStatus.FAILED, value: FileVaultProfileStatus.FAILED,
}, },
{ {
disabled: false, disabled: false,
label: "Removing enforcement", label: "Removing enforcement",
value: DiskEncryptionStatus.REMOVING_ENFORCEMENT, value: FileVaultProfileStatus.REMOVING_ENFORCEMENT,
}, },
]; ];
interface IDiskEncryptionStatusFilterProps { interface IDiskEncryptionStatusFilterProps {
diskEncryptionStatus: DiskEncryptionStatus; diskEncryptionStatus: FileVaultProfileStatus;
onChange: (value: DiskEncryptionStatus) => void; onChange: (value: FileVaultProfileStatus) => void;
} }
const DiskEncryptionStatusFilter = ({ const DiskEncryptionStatusFilter = ({

View File

@ -6,16 +6,17 @@ import {
formatOperatingSystemDisplayName, formatOperatingSystemDisplayName,
IOperatingSystemVersion, IOperatingSystemVersion,
} from "interfaces/operating_system"; } from "interfaces/operating_system";
import { IMdmSolution, MDM_ENROLLMENT_STATUS } from "interfaces/mdm"; import {
FileVaultProfileStatus,
IMdmSolution,
MDM_ENROLLMENT_STATUS,
} from "interfaces/mdm";
import { IMunkiIssuesAggregate } from "interfaces/macadmins"; import { IMunkiIssuesAggregate } from "interfaces/macadmins";
import { ISoftware } from "interfaces/software"; import { ISoftware } from "interfaces/software";
import { IPolicy } from "interfaces/policy"; import { IPolicy } from "interfaces/policy";
// TODO: should this be in interfaces hosts?
import { MacSettingsStatusQueryParam } from "services/entities/hosts"; import { MacSettingsStatusQueryParam } from "services/entities/hosts";
import { import {
DiskEncryptionStatus,
PLATFORM_LABEL_DISPLAY_NAMES, PLATFORM_LABEL_DISPLAY_NAMES,
PolicyResponse, PolicyResponse,
} from "utilities/constants"; } from "utilities/constants";
@ -60,14 +61,16 @@ interface IHostsFilterBlockProps {
osVersions?: IOperatingSystemVersion[]; osVersions?: IOperatingSystemVersion[];
softwareDetails: ISoftware | null; softwareDetails: ISoftware | null;
mdmSolutionDetails: IMdmSolution | null; mdmSolutionDetails: IMdmSolution | null;
diskEncryptionStatus?: DiskEncryptionStatus; diskEncryptionStatus?: FileVaultProfileStatus;
}; };
selectedLabel?: ILabel; selectedLabel?: ILabel;
isOnlyObserver?: boolean; isOnlyObserver?: boolean;
handleClearRouteParam: () => void; handleClearRouteParam: () => void;
handleClearFilter: (omitParams: string[]) => void; handleClearFilter: (omitParams: string[]) => void;
onChangePoliciesFilter: (response: PolicyResponse) => void; onChangePoliciesFilter: (response: PolicyResponse) => void;
onChangeDiskEncryptionStatusFilter: (response: DiskEncryptionStatus) => void; onChangeDiskEncryptionStatusFilter: (
response: FileVaultProfileStatus
) => void;
onChangeMacSettingsFilter: ( onChangeMacSettingsFilter: (
newMacSettingsStatus: MacSettingsStatusQueryParam newMacSettingsStatus: MacSettingsStatusQueryParam
) => void; ) => void;
@ -265,7 +268,6 @@ const HostsFilterBlock = ({
if (!mdmEnrollmentStatus) return null; if (!mdmEnrollmentStatus) return null;
const label = `MDM status: ${ const label = `MDM status: ${
// TODO: move MDM_ENROLLMENT_STATUS to util file
invert(MDM_ENROLLMENT_STATUS)[mdmEnrollmentStatus] invert(MDM_ENROLLMENT_STATUS)[mdmEnrollmentStatus]
}`; }`;

View File

@ -1,3 +1,5 @@
import { MdmProfileStatus } from "interfaces/mdm";
export const LABEL_SLUG_PREFIX = "labels/"; export const LABEL_SLUG_PREFIX = "labels/";
export const DEFAULT_SORT_HEADER = "display_name"; export const DEFAULT_SORT_HEADER = "display_name";
@ -41,17 +43,17 @@ export const HOST_SELECT_STATUSES = [
export const MAC_SETTINGS_FILTER_OPTIONS = [ export const MAC_SETTINGS_FILTER_OPTIONS = [
{ {
disabled: false, disabled: false,
label: "Latest", label: "Verifying",
value: "latest", value: MdmProfileStatus.VERIFYING,
}, },
{ {
disabled: false, disabled: false,
label: "Pending", label: "Pending",
value: "pending", value: MdmProfileStatus.PENDING,
}, },
{ {
disabled: false, disabled: false,
label: "Failing", label: "Failed",
value: "failing", value: MdmProfileStatus.FAILED,
}, },
]; ];

View File

@ -1,11 +1,11 @@
import React from "react"; import React from "react";
import Button from "components/buttons/Button"; import Button from "components/buttons/Button";
import Modal from "components/Modal"; import Modal from "components/Modal";
import { IMacSettings } from "interfaces/mdm"; import { IHostMacMdmProfile } from "interfaces/mdm";
import MacSettingsTable from "./MacSettingsTable"; import MacSettingsTable from "./MacSettingsTable";
interface IMacSettingsModalProps { interface IMacSettingsModalProps {
hostMacSettings?: IMacSettings; hostMacSettings?: IHostMacMdmProfile[];
onClose: () => void; onClose: () => void;
} }

View File

@ -1,26 +1,23 @@
import React from "react"; import React from "react";
import { render, screen } from "@testing-library/react"; import { render, screen } from "@testing-library/react";
import { createCustomRenderer } from "test/test-utils"; import { createCustomRenderer } from "test/test-utils";
import { import { MacMdmProfileOperationType, MdmProfileStatus } from "interfaces/mdm";
MacMdmProfileOperationType,
MacMdmProfileStatus,
} from "interfaces/mdm";
import MacSettingStatusCell from "./MacSettingStatusCell"; import MacSettingStatusCell from "./MacSettingStatusCell";
describe("Mac setting status cell", () => { describe("Mac setting status cell", () => {
it("Correctly displays the status text of a profile", () => { it("Correctly displays the status text of a profile", () => {
const status: MacMdmProfileStatus = "applied"; const status = MdmProfileStatus.VERIFYING;
const operationType: MacMdmProfileOperationType = "install"; const operationType: MacMdmProfileOperationType = "install";
render( render(
<MacSettingStatusCell status={status} operationType={operationType} /> <MacSettingStatusCell status={status} operationType={operationType} />
); );
expect(screen.getByText("Applied")).toBeInTheDocument(); expect(screen.getByText("Verifying")).toBeInTheDocument();
}); });
it("Correctly displays the tooltip text for a profile", async () => { it("Correctly displays the tooltip text for a profile", async () => {
const status: MacMdmProfileStatus = "applied"; const status = MdmProfileStatus.VERIFYING;
const operationType: MacMdmProfileOperationType = "install"; const operationType: MacMdmProfileOperationType = "install";
const customRender = createCustomRenderer(); const customRender = createCustomRenderer();
@ -29,7 +26,7 @@ describe("Mac setting status cell", () => {
<MacSettingStatusCell status={status} operationType={operationType} /> <MacSettingStatusCell status={status} operationType={operationType} />
); );
const statusText = screen.getByText("Applied"); const statusText = screen.getByText("Verifying");
await user.hover(statusText); await user.hover(statusText);

View File

@ -1,33 +1,35 @@
import Icon from "components/Icon"; import Icon from "components/Icon";
import TextCell from "components/TableContainer/DataTable/TextCell"; import TextCell from "components/TableContainer/DataTable/TextCell";
import { import { IconNames } from "components/icons";
MacMdmProfileOperationType, import { MacMdmProfileOperationType, MdmProfileStatus } from "interfaces/mdm";
MacMdmProfileStatus,
} from "interfaces/mdm";
import _ from "lodash"; import _ from "lodash";
import React from "react"; import React from "react";
import ReactTooltip from "react-tooltip"; import ReactTooltip from "react-tooltip";
const baseClass = "mac-setting-status-cell"; const baseClass = "mac-setting-status-cell";
interface IMacSettingStatusCellProps { type ProfileDisplayOption = {
status: MacMdmProfileStatus; statusText: string;
operationType: MacMdmProfileOperationType; iconName: IconNames;
} tooltipText?: string;
const MacSettingStatusCell = ({ } | null;
status,
operationType, type OperationTypeOption = Record<MdmProfileStatus, ProfileDisplayOption>;
}: IMacSettingStatusCellProps): JSX.Element => { type ProfileDisplayConfig = Record<
const PROFILE_DISPLAY_CONFIG = { MacMdmProfileOperationType,
OperationTypeOption
>;
const PROFILE_DISPLAY_CONFIG: ProfileDisplayConfig = {
install: { install: {
pending: { pending: {
statusText: "Enforcing (pending)", statusText: "Enforcing (pending)",
iconName: "pending", iconName: "pending-partial",
tooltipText: "Setting will be enforced when the host comes online.", tooltipText: "Setting will be enforced when the host comes online.",
}, },
applied: { verifying: {
statusText: "Applied", statusText: "Verifying",
iconName: "success", iconName: "success-partial",
tooltipText: "Host applied the setting.", tooltipText: "Host applied the setting.",
}, },
failed: { failed: {
@ -39,18 +41,26 @@ const MacSettingStatusCell = ({
remove: { remove: {
pending: { pending: {
statusText: "Removing enforcement (pending)", statusText: "Removing enforcement (pending)",
iconName: "pending", iconName: "pending-partial",
tooltipText: "Enforcement will be removed when the host comes online.", tooltipText: "Enforcement will be removed when the host comes online.",
}, },
applied: null, // should not be reached verifying: null, // should not be reached
failed: { failed: {
statusText: "Failed", statusText: "Failed",
iconName: "error", iconName: "error",
tooltipText: undefined, tooltipText: undefined,
}, },
}, },
} as const; };
interface IMacSettingStatusCellProps {
status: MdmProfileStatus;
operationType: MacMdmProfileOperationType;
}
const MacSettingStatusCell = ({
status,
operationType,
}: IMacSettingStatusCellProps): JSX.Element => {
const options = PROFILE_DISPLAY_CONFIG[operationType]?.[status]; const options = PROFILE_DISPLAY_CONFIG[operationType]?.[status];
if (options) { if (options) {

View File

@ -1,13 +1,13 @@
import React from "react"; import React from "react";
import TableContainer from "components/TableContainer"; import TableContainer from "components/TableContainer";
import { IMacSettings } from "interfaces/mdm"; import { IHostMacMdmProfile } from "interfaces/mdm";
import tableHeaders from "./MacSettingsTableConfig"; import tableHeaders from "./MacSettingsTableConfig";
const baseClass = "macsettings-table"; const baseClass = "macsettings-table";
interface IMacSettingsTableProps { interface IMacSettingsTableProps {
hostMacSettings?: IMacSettings; hostMacSettings?: IHostMacMdmProfile[];
} }
const MacSettingsTable = ({ hostMacSettings }: IMacSettingsTableProps) => { const MacSettingsTable = ({ hostMacSettings }: IMacSettingsTableProps) => {

View File

@ -9,7 +9,7 @@ import HumanTimeDiffWithDateTip from "components/HumanTimeDiffWithDateTip";
import { humanHostMemory, wrapFleetHelper } from "utilities/helpers"; import { humanHostMemory, wrapFleetHelper } from "utilities/helpers";
import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants"; import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants";
import StatusIndicator from "components/StatusIndicator"; import StatusIndicator from "components/StatusIndicator";
import { IMacSettings } from "interfaces/mdm"; import { IHostMacMdmProfile } from "interfaces/mdm";
import getHostStatusTooltipText from "pages/hosts/helpers"; import getHostStatusTooltipText from "pages/hosts/helpers";
import IssueIcon from "../../../../../../assets/images/icon-issue-fleet-black-50-16x16@2x.png"; import IssueIcon from "../../../../../../assets/images/icon-issue-fleet-black-50-16x16@2x.png";
import MacSettingsIndicator from "./MacSettingsIndicator"; import MacSettingsIndicator from "./MacSettingsIndicator";
@ -30,7 +30,7 @@ interface IHostSummaryProps {
isOnlyObserver?: boolean; isOnlyObserver?: boolean;
toggleOSPolicyModal?: () => void; toggleOSPolicyModal?: () => void;
toggleMacSettingsModal?: () => void; toggleMacSettingsModal?: () => void;
hostMacSettings?: IMacSettings; hostMacSettings?: IHostMacMdmProfile[];
mdmName?: string; mdmName?: string;
showRefetchSpinner: boolean; showRefetchSpinner: boolean;
onRefetchHost: ( onRefetchHost: (

View File

@ -1,26 +1,32 @@
import React from "react"; import React from "react";
import ReactTooltip from "react-tooltip"; import ReactTooltip from "react-tooltip";
import { IHostMacMdmProfile, MdmProfileStatus } from "interfaces/mdm";
import Icon from "components/Icon"; import Icon from "components/Icon";
import Button from "components/buttons/Button"; import Button from "components/buttons/Button";
import { IMacSettings, MacSettingsStatus } from "interfaces/mdm"; import { IconNames } from "components/icons";
const baseClass = "mac-settings-indicator"; const baseClass = "mac-settings-indicator";
interface IMacSettingsIndicatorProps { type MacSettingsStatus = "Failing" | "Verifying" | "Pending";
profiles: IMacSettings;
onClick?: () => void; interface IStatusDisplayOption {
iconName: Extract<
IconNames,
"success" | "success-partial" | "pending" | "pending-partial" | "error"
>;
tooltipText: string;
} }
const MacSettingsIndicator = ({ type StatusDisplayOptions = Record<MacSettingsStatus, IStatusDisplayOption>;
profiles,
onClick, const STATUS_DISPLAY_OPTIONS: StatusDisplayOptions = {
}: IMacSettingsIndicatorProps): JSX.Element => { Verifying: {
const STATUS_DISPLAY_OPTIONS = { iconName: "success-partial",
Latest: {
iconName: "success",
tooltipText: "Host applied the latest settings", tooltipText: "Host applied the latest settings",
}, },
Pending: { Pending: {
iconName: "pending", iconName: "pending-partial",
tooltipText: "Host will apply the latest settings when it comes online", tooltipText: "Host will apply the latest settings when it comes online",
}, },
Failing: { Failing: {
@ -28,21 +34,29 @@ const MacSettingsIndicator = ({
tooltipText: tooltipText:
"Host failed to apply the latest settings. Click to view error(s).", "Host failed to apply the latest settings. Click to view error(s).",
}, },
} as const; };
const getMacSettingsStatus = ( const getMacSettingsStatus = (
hostMacSettings: IMacSettings | undefined hostMacSettings?: IHostMacMdmProfile[]
): MacSettingsStatus => { ): MacSettingsStatus => {
const statuses = hostMacSettings?.map((setting) => setting.status); const statuses = hostMacSettings?.map((setting) => setting.status);
if (statuses?.includes("failed")) { if (statuses?.includes(MdmProfileStatus.FAILED)) {
return "Failing"; return "Failing";
} }
if (statuses?.includes("pending")) { if (statuses?.includes(MdmProfileStatus.PENDING)) {
return "Pending"; return "Pending";
} }
return "Latest"; return "Verifying";
}; };
interface IMacSettingsIndicatorProps {
profiles: IHostMacMdmProfile[];
onClick?: () => void;
}
const MacSettingsIndicator = ({
profiles,
onClick,
}: IMacSettingsIndicatorProps): JSX.Element => {
const macSettingsStatus = getMacSettingsStatus(profiles); const macSettingsStatus = getMacSettingsStatus(profiles);
const iconName = STATUS_DISPLAY_OPTIONS[macSettingsStatus].iconName; const iconName = STATUS_DISPLAY_OPTIONS[macSettingsStatus].iconName;

View File

@ -2,13 +2,14 @@
import sendRequest from "services"; import sendRequest from "services";
import endpoints from "utilities/endpoints"; import endpoints from "utilities/endpoints";
import { HostStatus } from "interfaces/host"; import { HostStatus } from "interfaces/host";
import { FileVaultProfileStatus } from "interfaces/mdm";
import { import {
buildQueryStringFromParams, buildQueryStringFromParams,
getLabelParam, getLabelParam,
reconcileMutuallyExclusiveHostParams, reconcileMutuallyExclusiveHostParams,
reconcileMutuallyInclusiveHostParams, reconcileMutuallyInclusiveHostParams,
} from "utilities/url"; } from "utilities/url";
import { DiskEncryptionStatus } from "utilities/constants";
import { MacSettingsStatusQueryParam } from "./hosts"; import { MacSettingsStatusQueryParam } from "./hosts";
export interface ISortOption { export interface ISortOption {
@ -42,7 +43,7 @@ export interface IHostCountLoadOptions {
osId?: number; osId?: number;
osName?: string; osName?: string;
osVersion?: string; osVersion?: string;
diskEncryptionStatus?: DiskEncryptionStatus; diskEncryptionStatus?: FileVaultProfileStatus;
} }
export default { export default {

View File

@ -9,9 +9,8 @@ import {
reconcileMutuallyInclusiveHostParams, reconcileMutuallyInclusiveHostParams,
} from "utilities/url"; } from "utilities/url";
import { ISelectedPlatform } from "interfaces/platform"; import { ISelectedPlatform } from "interfaces/platform";
import { DiskEncryptionStatus } from "utilities/constants";
import { ISoftware } from "interfaces/software"; import { ISoftware } from "interfaces/software";
import { IMdmSolution } from "interfaces/mdm"; import { FileVaultProfileStatus, IMdmSolution } from "interfaces/mdm";
import { IMunkiIssuesAggregate } from "interfaces/macadmins"; import { IMunkiIssuesAggregate } from "interfaces/macadmins";
export interface ISortOption { export interface ISortOption {
@ -54,7 +53,7 @@ export interface ILoadHostsOptions {
device_mapping?: boolean; device_mapping?: boolean;
columns?: string; columns?: string;
visibleColumns?: string; visibleColumns?: string;
diskEncryptionStatus?: DiskEncryptionStatus; diskEncryptionStatus?: FileVaultProfileStatus;
} }
export interface IExportHostsOptions { export interface IExportHostsOptions {
@ -79,7 +78,7 @@ export interface IExportHostsOptions {
device_mapping?: boolean; device_mapping?: boolean;
columns?: string; columns?: string;
visibleColumns?: string; visibleColumns?: string;
diskEncryptionStatus?: DiskEncryptionStatus; diskEncryptionStatus?: FileVaultProfileStatus;
} }
export interface IActionByFilter { export interface IActionByFilter {

View File

@ -10,14 +10,6 @@ export enum PolicyResponse {
FAILING = "failing", FAILING = "failing",
} }
export enum DiskEncryptionStatus {
APPLIED = "applied",
ACTION_REQUIRED = "action_required",
ENFORCING = "enforcing",
FAILED = "failed",
REMOVING_ENFORCEMENT = "removing_enforcement",
}
export const DEFAULT_GRAVATAR_LINK = export const DEFAULT_GRAVATAR_LINK =
"https://fleetdm.com/images/permanent/icon-avatar-default-transparent-64x64%402x.png"; "https://fleetdm.com/images/permanent/icon-avatar-default-transparent-64x64%402x.png";

View File

@ -1,6 +1,6 @@
import { FileVaultProfileStatus } from "interfaces/mdm";
import { isEmpty, reduce, omitBy, Dictionary } from "lodash"; import { isEmpty, reduce, omitBy, Dictionary } from "lodash";
import { MacSettingsStatusQueryParam } from "services/entities/hosts"; import { MacSettingsStatusQueryParam } from "services/entities/hosts";
import { DiskEncryptionStatus } from "utilities/constants";
type QueryValues = string | number | boolean | undefined | null; type QueryValues = string | number | boolean | undefined | null;
export type QueryParams = Record<string, QueryValues>; export type QueryParams = Record<string, QueryValues>;
@ -24,7 +24,7 @@ interface IMutuallyExclusiveHostParams {
osId?: number; osId?: number;
osName?: string; osName?: string;
osVersion?: string; osVersion?: string;
diskEncryptionStatus?: DiskEncryptionStatus; diskEncryptionStatus?: FileVaultProfileStatus;
} }
const reduceQueryParams = ( const reduceQueryParams = (

View File

@ -384,7 +384,7 @@ type MDMAppleConfigProfilesSummary struct {
Pending uint `json:"pending" db:"pending"` Pending uint `json:"pending" db:"pending"`
// Failed includes each host that has failed to apply one or more of the profiles currently // Failed includes each host that has failed to apply one or more of the profiles currently
// applicable to the host. // applicable to the host.
Failed uint `json:"failing" db:"failed"` Failed uint `json:"failed" db:"failed"`
} }
// MDMAppleFileVaultSummary reports the number of macOS hosts being managed with Apples disk // MDMAppleFileVaultSummary reports the number of macOS hosts being managed with Apples disk