mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 08:55:24 +00:00
Feat UI add verifying status to mdm (#11311)
This commit is contained in:
parent
4d1beef728
commit
4866bccb3f
1
changes/issue-11181-add-verifying-status-to-profiles
Normal file
1
changes/issue-11181-add-verifying-status-to-profiles
Normal file
@ -0,0 +1 @@
|
||||
- add verifying status for mdm profiles
|
@ -9,10 +9,15 @@ import { COLORS } from "styles/var/colors";
|
||||
|
||||
const baseClass = "status-indicator-with-icon";
|
||||
|
||||
type Status = "success" | "pending" | "error";
|
||||
export type IndicatorStatus =
|
||||
| "success"
|
||||
| "successPartial"
|
||||
| "pending"
|
||||
| "pendingPartial"
|
||||
| "error";
|
||||
|
||||
interface IStatusIndicatorWithIconProps {
|
||||
status: Status;
|
||||
status: IndicatorStatus;
|
||||
value: string;
|
||||
tooltip?: {
|
||||
tooltipText: string | JSX.Element;
|
||||
@ -21,9 +26,11 @@ interface IStatusIndicatorWithIconProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const statusIconNameMapping: Record<Status, IconNames> = {
|
||||
const statusIconNameMapping: Record<IndicatorStatus, IconNames> = {
|
||||
success: "success",
|
||||
successPartial: "success-partial",
|
||||
pending: "pending",
|
||||
pendingPartial: "pending-partial",
|
||||
error: "error",
|
||||
};
|
||||
|
||||
|
20
frontend/components/icons/PendingPartial.tsx
Normal file
20
frontend/components/icons/PendingPartial.tsx
Normal 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;
|
24
frontend/components/icons/SuccessPartial.tsx
Normal file
24
frontend/components/icons/SuccessPartial.tsx
Normal 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;
|
@ -16,7 +16,6 @@ import EmptyTeams from "./EmptyTeams";
|
||||
import ExternalLink from "./ExternalLink";
|
||||
import Issue from "./Issue";
|
||||
import Plus from "./Plus";
|
||||
import Pending from "./Pending";
|
||||
import PremiumFeature from "./PremiumFeature";
|
||||
|
||||
import LowDiskSpaceHosts from "./LowDiskSpaceHosts";
|
||||
@ -37,8 +36,12 @@ import ApplePurple from "./ApplePurple";
|
||||
import LinuxGreen from "./LinuxGreen";
|
||||
import WindowsBlue from "./WindowsBlue";
|
||||
|
||||
import Error from "./Error";
|
||||
// Status Icons
|
||||
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 Eye from "./Eye";
|
||||
@ -79,9 +82,11 @@ export const ICON_MAP = {
|
||||
clipboard: Clipboard,
|
||||
eye: Eye,
|
||||
pencil: Pencil,
|
||||
pending: Pending,
|
||||
trash: TrashCan,
|
||||
success: Success,
|
||||
"success-partial": SuccessPartial,
|
||||
pending: Pending,
|
||||
"pending-partial": PendingPartial,
|
||||
error: Error,
|
||||
darwin: Apple,
|
||||
macOS: Apple,
|
||||
|
@ -7,7 +7,7 @@ import softwareInterface, { ISoftware } from "./software";
|
||||
import hostQueryResult from "./campaign";
|
||||
import queryStatsInterface, { IQueryStats } from "./query_stats";
|
||||
import { ILicense, IDeviceGlobalConfig } from "./config";
|
||||
import { IMacSettings, MdmEnrollmentStatus } from "./mdm";
|
||||
import { IHostMacMdmProfile, MdmEnrollmentStatus } from "./mdm";
|
||||
|
||||
export default PropTypes.shape({
|
||||
created_at: PropTypes.string,
|
||||
@ -107,7 +107,7 @@ export interface IHostMdmData {
|
||||
name?: string;
|
||||
server_url: string;
|
||||
id?: number;
|
||||
profiles: IMacSettings;
|
||||
profiles: IHostMacMdmProfile[];
|
||||
macos_settings: IMdmMacOsSettings;
|
||||
}
|
||||
|
||||
|
@ -68,33 +68,38 @@ export interface IMdmProfilesResponse {
|
||||
profiles: IMdmProfile[] | null;
|
||||
}
|
||||
|
||||
export type MacMdmProfileStatus = "applied" | "pending" | "failed";
|
||||
export enum MdmProfileStatus {
|
||||
VERIFYING = "verifying",
|
||||
PENDING = "pending",
|
||||
FAILED = "failed",
|
||||
}
|
||||
|
||||
export type MacMdmProfileOperationType = "remove" | "install";
|
||||
|
||||
export interface IHostMacMdmProfile {
|
||||
profile_id: number;
|
||||
name: string;
|
||||
operation_type: MacMdmProfileOperationType;
|
||||
status: MacMdmProfileStatus;
|
||||
status: MdmProfileStatus;
|
||||
detail: string;
|
||||
}
|
||||
export type IMacSettings = IHostMacMdmProfile[];
|
||||
export type MacSettingsStatus = "Failing" | "Latest" | "Pending";
|
||||
|
||||
export interface IAggregateMacSettingsStatus {
|
||||
latest: number;
|
||||
pending: number;
|
||||
failing: number;
|
||||
}
|
||||
|
||||
export interface IDiskEncryptionStatusAggregate {
|
||||
applied: number;
|
||||
export interface IFileVaultSummaryResponse {
|
||||
verifying: number;
|
||||
action_required: number;
|
||||
enforcing: number;
|
||||
failed: 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
|
||||
export interface IMdmScript {
|
||||
id: number;
|
||||
|
@ -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 React from "react";
|
||||
import { useQuery } from "react-query";
|
||||
@ -8,6 +9,39 @@ import { buildQueryStringFromParams } from "utilities/url";
|
||||
|
||||
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 {
|
||||
teamId: number;
|
||||
}
|
||||
@ -15,52 +49,22 @@ interface AggregateMacSettingsIndicatorsProps {
|
||||
const AggregateMacSettingsIndicators = ({
|
||||
teamId,
|
||||
}: 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 haven’t 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 {
|
||||
data: aggregateProfileStatusesResponse,
|
||||
} = useQuery<IAggregateMacSettingsStatus>(
|
||||
} = useQuery<ProfileSummaryResponse>(
|
||||
["aggregateProfileStatuses", teamId],
|
||||
() => mdmAPI.getAggregateProfileStatuses(teamId),
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
|
||||
const DISPLAY_ORDER = ["latest", "pending", "failing"] as const;
|
||||
const orderedResponseKVArr: [
|
||||
keyof IAggregateMacSettingsStatus,
|
||||
number
|
||||
][] = aggregateProfileStatusesResponse
|
||||
? DISPLAY_ORDER.map((key) => {
|
||||
return [key, aggregateProfileStatusesResponse[key]];
|
||||
})
|
||||
: [];
|
||||
if (!aggregateProfileStatusesResponse) return null;
|
||||
|
||||
const indicators = orderedResponseKVArr.map(([status, count]) => {
|
||||
const { text, iconName, tooltipText } = AGGREGATE_STATUS_DISPLAY_OPTIONS[
|
||||
status
|
||||
];
|
||||
const indicators = AGGREGATE_STATUS_DISPLAY_OPTIONS.map((status) => {
|
||||
const { value, text, iconName, tooltipText } = status;
|
||||
const count = aggregateProfileStatusesResponse[value];
|
||||
|
||||
return (
|
||||
<div className="aggregate-mac-settings-indicator">
|
||||
{/* NOTE - below will be renamed as a general component and moved into the components dir by Gabe */}
|
||||
<MacSettingsIndicator
|
||||
indicatorText={text}
|
||||
iconName={iconName}
|
||||
@ -69,7 +73,7 @@ const AggregateMacSettingsIndicators = ({
|
||||
<a
|
||||
href={`${paths.MANAGE_HOSTS}?${buildQueryStringFromParams({
|
||||
team_id: teamId,
|
||||
macos_settings: status,
|
||||
macos_settings: value,
|
||||
})}`}
|
||||
>
|
||||
{count} hosts
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { useQuery } from "react-query";
|
||||
|
||||
import { IDiskEncryptionStatusAggregate } from "interfaces/mdm";
|
||||
import { IFileVaultSummaryResponse } from "interfaces/mdm";
|
||||
import mdmAPI from "services/entities/mdm";
|
||||
|
||||
import TableContainer from "components/TableContainer";
|
||||
@ -24,9 +24,9 @@ const DEFAULT_SORT_DIRECTION = "asc";
|
||||
|
||||
const DiskEncryptionTable = ({ currentTeamId }: IDiskEncryptionTableProps) => {
|
||||
const { data, error } = useQuery<
|
||||
IDiskEncryptionStatusAggregate,
|
||||
IFileVaultSummaryResponse,
|
||||
Error,
|
||||
IDiskEncryptionStatusAggregate
|
||||
IFileVaultSummaryResponse
|
||||
>(
|
||||
["disk-encryption-summary", currentTeamId],
|
||||
() => mdmAPI.getDiskEncryptionAggregate(currentTeamId),
|
||||
|
@ -1,17 +1,20 @@
|
||||
import React from "react";
|
||||
|
||||
import { IDiskEncryptionStatusAggregate } from "interfaces/mdm";
|
||||
import { DiskEncryptionStatus } from "utilities/constants";
|
||||
import {
|
||||
FileVaultProfileStatus,
|
||||
IFileVaultSummaryResponse,
|
||||
} from "interfaces/mdm";
|
||||
|
||||
import TextCell from "components/TableContainer/DataTable/TextCell";
|
||||
import HeaderCell from "components/TableContainer/DataTable/HeaderCell";
|
||||
import StatusIndicatorWithIcon from "components/StatusIndicatorWithIcon";
|
||||
import ViewAllHostsLink from "components/ViewAllHostsLink";
|
||||
import { IndicatorStatus } from "components/StatusIndicatorWithIcon/StatusIndicatorWithIcon";
|
||||
|
||||
interface IStatusCellValue {
|
||||
displayName: string;
|
||||
statusName: "success" | "pending" | "error";
|
||||
value: DiskEncryptionStatus;
|
||||
statusName: IndicatorStatus;
|
||||
value: FileVaultProfileStatus;
|
||||
tooltip?: string | JSX.Element;
|
||||
}
|
||||
|
||||
@ -100,7 +103,7 @@ const defaultTableHeaders: IDataColumn[] = [
|
||||
},
|
||||
];
|
||||
|
||||
type StatusNames = keyof IDiskEncryptionStatusAggregate;
|
||||
type StatusNames = keyof IFileVaultSummaryResponse;
|
||||
|
||||
type StatusEntry = [StatusNames, number];
|
||||
|
||||
@ -108,17 +111,17 @@ export const generateTableHeaders = (): IDataColumn[] => {
|
||||
return defaultTableHeaders;
|
||||
};
|
||||
|
||||
const STATUS_CELL_VALUES: Record<StatusNames, IStatusCellValue> = {
|
||||
applied: {
|
||||
displayName: "Applied",
|
||||
statusName: "success",
|
||||
value: DiskEncryptionStatus.APPLIED,
|
||||
tooltip: "Disk encryption on and key stored in Fleet.",
|
||||
const STATUS_CELL_VALUES: Record<FileVaultProfileStatus, IStatusCellValue> = {
|
||||
verifying: {
|
||||
displayName: "Verifying",
|
||||
statusName: "successPartial",
|
||||
value: FileVaultProfileStatus.VERIFYING,
|
||||
tooltip: "Disk encryption on and key stored in Fleet. Fleet will verify.",
|
||||
},
|
||||
action_required: {
|
||||
displayName: "Action required (pending)",
|
||||
statusName: "pending",
|
||||
value: DiskEncryptionStatus.ACTION_REQUIRED,
|
||||
statusName: "pendingPartial",
|
||||
value: FileVaultProfileStatus.ACTION_REQUIRED,
|
||||
tooltip: (
|
||||
<>
|
||||
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: {
|
||||
displayName: "Enforcing (pending)",
|
||||
statusName: "pending",
|
||||
value: DiskEncryptionStatus.ENFORCING,
|
||||
statusName: "pendingPartial",
|
||||
value: FileVaultProfileStatus.ENFORCING,
|
||||
tooltip: "Setting will be enforced when the hosts come online.",
|
||||
},
|
||||
failed: {
|
||||
displayName: "Failed",
|
||||
statusName: "error",
|
||||
value: DiskEncryptionStatus.FAILED,
|
||||
value: FileVaultProfileStatus.FAILED,
|
||||
},
|
||||
removing_enforcement: {
|
||||
displayName: "Removing enforcement (pending)",
|
||||
statusName: "pending",
|
||||
value: DiskEncryptionStatus.REMOVING_ENFORCEMENT,
|
||||
statusName: "pendingPartial",
|
||||
value: FileVaultProfileStatus.REMOVING_ENFORCEMENT,
|
||||
tooltip: "Enforcement will be removed when the hosts come online.",
|
||||
},
|
||||
};
|
||||
|
||||
export const generateTableData = (
|
||||
data?: IDiskEncryptionStatusAggregate,
|
||||
data?: IFileVaultSummaryResponse,
|
||||
currentTeamId?: number
|
||||
) => {
|
||||
if (!data) return [];
|
||||
|
@ -48,10 +48,10 @@ import { IOperatingSystemVersion } from "interfaces/operating_system";
|
||||
import { IPolicy, IStoredPolicyResponse } from "interfaces/policy";
|
||||
import { ITeam } from "interfaces/team";
|
||||
import { IEmptyTableProps } from "interfaces/empty_table";
|
||||
import { FileVaultProfileStatus } from "interfaces/mdm";
|
||||
|
||||
import sortUtils from "utilities/sort";
|
||||
import {
|
||||
DiskEncryptionStatus,
|
||||
HOSTS_SEARCH_BOX_PLACEHOLDER,
|
||||
HOSTS_SEARCH_BOX_TOOLTIP,
|
||||
PolicyResponse,
|
||||
@ -245,7 +245,7 @@ const ManageHostsPage = ({
|
||||
? parseInt(queryParams.low_disk_space, 10)
|
||||
: undefined;
|
||||
const missingHosts = queryParams?.status === "missing";
|
||||
const diskEncryptionStatus: DiskEncryptionStatus | undefined =
|
||||
const diskEncryptionStatus: FileVaultProfileStatus | undefined =
|
||||
queryParams?.macos_settings_disk_encryption;
|
||||
|
||||
// ========= routeParams
|
||||
@ -562,7 +562,7 @@ const ManageHostsPage = ({
|
||||
};
|
||||
|
||||
const handleChangeDiskEncryptionStatusFilter = (
|
||||
newStatus: DiskEncryptionStatus
|
||||
newStatus: FileVaultProfileStatus
|
||||
) => {
|
||||
handleResetPageIndex();
|
||||
|
||||
|
@ -1,44 +1,44 @@
|
||||
import React from "react";
|
||||
|
||||
import { IDropdownOption } from "interfaces/dropdownOption";
|
||||
import { DiskEncryptionStatus } from "utilities/constants";
|
||||
|
||||
// @ts-ignore
|
||||
import Dropdown from "components/forms/fields/Dropdown";
|
||||
import { FileVaultProfileStatus } from "interfaces/mdm";
|
||||
|
||||
const baseClass = "disk-encryption-status-filter";
|
||||
|
||||
const DISK_ENCRYPTION_STATUS_OPTIONS: IDropdownOption[] = [
|
||||
{
|
||||
disabled: false,
|
||||
label: "Applied",
|
||||
value: DiskEncryptionStatus.APPLIED,
|
||||
label: "Verifying",
|
||||
value: FileVaultProfileStatus.VERIFYING,
|
||||
},
|
||||
{
|
||||
disabled: false,
|
||||
label: "Action required",
|
||||
value: DiskEncryptionStatus.ACTION_REQUIRED,
|
||||
value: FileVaultProfileStatus.ACTION_REQUIRED,
|
||||
},
|
||||
{
|
||||
disabled: false,
|
||||
label: "Enforcing",
|
||||
value: DiskEncryptionStatus.ENFORCING,
|
||||
value: FileVaultProfileStatus.ENFORCING,
|
||||
},
|
||||
{
|
||||
disabled: false,
|
||||
label: "Failed",
|
||||
value: DiskEncryptionStatus.FAILED,
|
||||
value: FileVaultProfileStatus.FAILED,
|
||||
},
|
||||
{
|
||||
disabled: false,
|
||||
label: "Removing enforcement",
|
||||
value: DiskEncryptionStatus.REMOVING_ENFORCEMENT,
|
||||
value: FileVaultProfileStatus.REMOVING_ENFORCEMENT,
|
||||
},
|
||||
];
|
||||
|
||||
interface IDiskEncryptionStatusFilterProps {
|
||||
diskEncryptionStatus: DiskEncryptionStatus;
|
||||
onChange: (value: DiskEncryptionStatus) => void;
|
||||
diskEncryptionStatus: FileVaultProfileStatus;
|
||||
onChange: (value: FileVaultProfileStatus) => void;
|
||||
}
|
||||
|
||||
const DiskEncryptionStatusFilter = ({
|
||||
|
@ -6,16 +6,17 @@ import {
|
||||
formatOperatingSystemDisplayName,
|
||||
IOperatingSystemVersion,
|
||||
} 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 { ISoftware } from "interfaces/software";
|
||||
import { IPolicy } from "interfaces/policy";
|
||||
|
||||
// TODO: should this be in interfaces hosts?
|
||||
import { MacSettingsStatusQueryParam } from "services/entities/hosts";
|
||||
|
||||
import {
|
||||
DiskEncryptionStatus,
|
||||
PLATFORM_LABEL_DISPLAY_NAMES,
|
||||
PolicyResponse,
|
||||
} from "utilities/constants";
|
||||
@ -60,14 +61,16 @@ interface IHostsFilterBlockProps {
|
||||
osVersions?: IOperatingSystemVersion[];
|
||||
softwareDetails: ISoftware | null;
|
||||
mdmSolutionDetails: IMdmSolution | null;
|
||||
diskEncryptionStatus?: DiskEncryptionStatus;
|
||||
diskEncryptionStatus?: FileVaultProfileStatus;
|
||||
};
|
||||
selectedLabel?: ILabel;
|
||||
isOnlyObserver?: boolean;
|
||||
handleClearRouteParam: () => void;
|
||||
handleClearFilter: (omitParams: string[]) => void;
|
||||
onChangePoliciesFilter: (response: PolicyResponse) => void;
|
||||
onChangeDiskEncryptionStatusFilter: (response: DiskEncryptionStatus) => void;
|
||||
onChangeDiskEncryptionStatusFilter: (
|
||||
response: FileVaultProfileStatus
|
||||
) => void;
|
||||
onChangeMacSettingsFilter: (
|
||||
newMacSettingsStatus: MacSettingsStatusQueryParam
|
||||
) => void;
|
||||
@ -265,7 +268,6 @@ const HostsFilterBlock = ({
|
||||
if (!mdmEnrollmentStatus) return null;
|
||||
|
||||
const label = `MDM status: ${
|
||||
// TODO: move MDM_ENROLLMENT_STATUS to util file
|
||||
invert(MDM_ENROLLMENT_STATUS)[mdmEnrollmentStatus]
|
||||
}`;
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { MdmProfileStatus } from "interfaces/mdm";
|
||||
|
||||
export const LABEL_SLUG_PREFIX = "labels/";
|
||||
|
||||
export const DEFAULT_SORT_HEADER = "display_name";
|
||||
@ -41,17 +43,17 @@ export const HOST_SELECT_STATUSES = [
|
||||
export const MAC_SETTINGS_FILTER_OPTIONS = [
|
||||
{
|
||||
disabled: false,
|
||||
label: "Latest",
|
||||
value: "latest",
|
||||
label: "Verifying",
|
||||
value: MdmProfileStatus.VERIFYING,
|
||||
},
|
||||
{
|
||||
disabled: false,
|
||||
label: "Pending",
|
||||
value: "pending",
|
||||
value: MdmProfileStatus.PENDING,
|
||||
},
|
||||
{
|
||||
disabled: false,
|
||||
label: "Failing",
|
||||
value: "failing",
|
||||
label: "Failed",
|
||||
value: MdmProfileStatus.FAILED,
|
||||
},
|
||||
];
|
||||
|
@ -1,11 +1,11 @@
|
||||
import React from "react";
|
||||
import Button from "components/buttons/Button";
|
||||
import Modal from "components/Modal";
|
||||
import { IMacSettings } from "interfaces/mdm";
|
||||
import { IHostMacMdmProfile } from "interfaces/mdm";
|
||||
import MacSettingsTable from "./MacSettingsTable";
|
||||
|
||||
interface IMacSettingsModalProps {
|
||||
hostMacSettings?: IMacSettings;
|
||||
hostMacSettings?: IHostMacMdmProfile[];
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
|
@ -1,26 +1,23 @@
|
||||
import React from "react";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { createCustomRenderer } from "test/test-utils";
|
||||
import {
|
||||
MacMdmProfileOperationType,
|
||||
MacMdmProfileStatus,
|
||||
} from "interfaces/mdm";
|
||||
import { MacMdmProfileOperationType, MdmProfileStatus } from "interfaces/mdm";
|
||||
import MacSettingStatusCell from "./MacSettingStatusCell";
|
||||
|
||||
describe("Mac setting status cell", () => {
|
||||
it("Correctly displays the status text of a profile", () => {
|
||||
const status: MacMdmProfileStatus = "applied";
|
||||
const status = MdmProfileStatus.VERIFYING;
|
||||
const operationType: MacMdmProfileOperationType = "install";
|
||||
|
||||
render(
|
||||
<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 () => {
|
||||
const status: MacMdmProfileStatus = "applied";
|
||||
const status = MdmProfileStatus.VERIFYING;
|
||||
const operationType: MacMdmProfileOperationType = "install";
|
||||
|
||||
const customRender = createCustomRenderer();
|
||||
@ -29,7 +26,7 @@ describe("Mac setting status cell", () => {
|
||||
<MacSettingStatusCell status={status} operationType={operationType} />
|
||||
);
|
||||
|
||||
const statusText = screen.getByText("Applied");
|
||||
const statusText = screen.getByText("Verifying");
|
||||
|
||||
await user.hover(statusText);
|
||||
|
||||
|
@ -1,33 +1,35 @@
|
||||
import Icon from "components/Icon";
|
||||
import TextCell from "components/TableContainer/DataTable/TextCell";
|
||||
import {
|
||||
MacMdmProfileOperationType,
|
||||
MacMdmProfileStatus,
|
||||
} from "interfaces/mdm";
|
||||
import { IconNames } from "components/icons";
|
||||
import { MacMdmProfileOperationType, MdmProfileStatus } from "interfaces/mdm";
|
||||
import _ from "lodash";
|
||||
import React from "react";
|
||||
import ReactTooltip from "react-tooltip";
|
||||
|
||||
const baseClass = "mac-setting-status-cell";
|
||||
|
||||
interface IMacSettingStatusCellProps {
|
||||
status: MacMdmProfileStatus;
|
||||
operationType: MacMdmProfileOperationType;
|
||||
}
|
||||
const MacSettingStatusCell = ({
|
||||
status,
|
||||
operationType,
|
||||
}: IMacSettingStatusCellProps): JSX.Element => {
|
||||
const PROFILE_DISPLAY_CONFIG = {
|
||||
type ProfileDisplayOption = {
|
||||
statusText: string;
|
||||
iconName: IconNames;
|
||||
tooltipText?: string;
|
||||
} | null;
|
||||
|
||||
type OperationTypeOption = Record<MdmProfileStatus, ProfileDisplayOption>;
|
||||
type ProfileDisplayConfig = Record<
|
||||
MacMdmProfileOperationType,
|
||||
OperationTypeOption
|
||||
>;
|
||||
|
||||
const PROFILE_DISPLAY_CONFIG: ProfileDisplayConfig = {
|
||||
install: {
|
||||
pending: {
|
||||
statusText: "Enforcing (pending)",
|
||||
iconName: "pending",
|
||||
iconName: "pending-partial",
|
||||
tooltipText: "Setting will be enforced when the host comes online.",
|
||||
},
|
||||
applied: {
|
||||
statusText: "Applied",
|
||||
iconName: "success",
|
||||
verifying: {
|
||||
statusText: "Verifying",
|
||||
iconName: "success-partial",
|
||||
tooltipText: "Host applied the setting.",
|
||||
},
|
||||
failed: {
|
||||
@ -39,18 +41,26 @@ const MacSettingStatusCell = ({
|
||||
remove: {
|
||||
pending: {
|
||||
statusText: "Removing enforcement (pending)",
|
||||
iconName: "pending",
|
||||
iconName: "pending-partial",
|
||||
tooltipText: "Enforcement will be removed when the host comes online.",
|
||||
},
|
||||
applied: null, // should not be reached
|
||||
verifying: null, // should not be reached
|
||||
failed: {
|
||||
statusText: "Failed",
|
||||
iconName: "error",
|
||||
tooltipText: undefined,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
};
|
||||
|
||||
interface IMacSettingStatusCellProps {
|
||||
status: MdmProfileStatus;
|
||||
operationType: MacMdmProfileOperationType;
|
||||
}
|
||||
const MacSettingStatusCell = ({
|
||||
status,
|
||||
operationType,
|
||||
}: IMacSettingStatusCellProps): JSX.Element => {
|
||||
const options = PROFILE_DISPLAY_CONFIG[operationType]?.[status];
|
||||
|
||||
if (options) {
|
||||
|
@ -1,13 +1,13 @@
|
||||
import React from "react";
|
||||
import TableContainer from "components/TableContainer";
|
||||
import { IMacSettings } from "interfaces/mdm";
|
||||
import { IHostMacMdmProfile } from "interfaces/mdm";
|
||||
|
||||
import tableHeaders from "./MacSettingsTableConfig";
|
||||
|
||||
const baseClass = "macsettings-table";
|
||||
|
||||
interface IMacSettingsTableProps {
|
||||
hostMacSettings?: IMacSettings;
|
||||
hostMacSettings?: IHostMacMdmProfile[];
|
||||
}
|
||||
|
||||
const MacSettingsTable = ({ hostMacSettings }: IMacSettingsTableProps) => {
|
||||
|
@ -9,7 +9,7 @@ import HumanTimeDiffWithDateTip from "components/HumanTimeDiffWithDateTip";
|
||||
import { humanHostMemory, wrapFleetHelper } from "utilities/helpers";
|
||||
import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants";
|
||||
import StatusIndicator from "components/StatusIndicator";
|
||||
import { IMacSettings } from "interfaces/mdm";
|
||||
import { IHostMacMdmProfile } from "interfaces/mdm";
|
||||
import getHostStatusTooltipText from "pages/hosts/helpers";
|
||||
import IssueIcon from "../../../../../../assets/images/icon-issue-fleet-black-50-16x16@2x.png";
|
||||
import MacSettingsIndicator from "./MacSettingsIndicator";
|
||||
@ -30,7 +30,7 @@ interface IHostSummaryProps {
|
||||
isOnlyObserver?: boolean;
|
||||
toggleOSPolicyModal?: () => void;
|
||||
toggleMacSettingsModal?: () => void;
|
||||
hostMacSettings?: IMacSettings;
|
||||
hostMacSettings?: IHostMacMdmProfile[];
|
||||
mdmName?: string;
|
||||
showRefetchSpinner: boolean;
|
||||
onRefetchHost: (
|
||||
|
@ -1,26 +1,32 @@
|
||||
import React from "react";
|
||||
import ReactTooltip from "react-tooltip";
|
||||
|
||||
import { IHostMacMdmProfile, MdmProfileStatus } from "interfaces/mdm";
|
||||
|
||||
import Icon from "components/Icon";
|
||||
import Button from "components/buttons/Button";
|
||||
import { IMacSettings, MacSettingsStatus } from "interfaces/mdm";
|
||||
import { IconNames } from "components/icons";
|
||||
|
||||
const baseClass = "mac-settings-indicator";
|
||||
|
||||
interface IMacSettingsIndicatorProps {
|
||||
profiles: IMacSettings;
|
||||
onClick?: () => void;
|
||||
type MacSettingsStatus = "Failing" | "Verifying" | "Pending";
|
||||
|
||||
interface IStatusDisplayOption {
|
||||
iconName: Extract<
|
||||
IconNames,
|
||||
"success" | "success-partial" | "pending" | "pending-partial" | "error"
|
||||
>;
|
||||
tooltipText: string;
|
||||
}
|
||||
const MacSettingsIndicator = ({
|
||||
profiles,
|
||||
onClick,
|
||||
}: IMacSettingsIndicatorProps): JSX.Element => {
|
||||
const STATUS_DISPLAY_OPTIONS = {
|
||||
Latest: {
|
||||
iconName: "success",
|
||||
type StatusDisplayOptions = Record<MacSettingsStatus, IStatusDisplayOption>;
|
||||
|
||||
const STATUS_DISPLAY_OPTIONS: StatusDisplayOptions = {
|
||||
Verifying: {
|
||||
iconName: "success-partial",
|
||||
tooltipText: "Host applied the latest settings",
|
||||
},
|
||||
Pending: {
|
||||
iconName: "pending",
|
||||
iconName: "pending-partial",
|
||||
tooltipText: "Host will apply the latest settings when it comes online",
|
||||
},
|
||||
Failing: {
|
||||
@ -28,21 +34,29 @@ const MacSettingsIndicator = ({
|
||||
tooltipText:
|
||||
"Host failed to apply the latest settings. Click to view error(s).",
|
||||
},
|
||||
} as const;
|
||||
};
|
||||
|
||||
const getMacSettingsStatus = (
|
||||
hostMacSettings: IMacSettings | undefined
|
||||
): MacSettingsStatus => {
|
||||
const getMacSettingsStatus = (
|
||||
hostMacSettings?: IHostMacMdmProfile[]
|
||||
): MacSettingsStatus => {
|
||||
const statuses = hostMacSettings?.map((setting) => setting.status);
|
||||
if (statuses?.includes("failed")) {
|
||||
if (statuses?.includes(MdmProfileStatus.FAILED)) {
|
||||
return "Failing";
|
||||
}
|
||||
if (statuses?.includes("pending")) {
|
||||
if (statuses?.includes(MdmProfileStatus.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 iconName = STATUS_DISPLAY_OPTIONS[macSettingsStatus].iconName;
|
||||
|
@ -2,13 +2,14 @@
|
||||
import sendRequest from "services";
|
||||
import endpoints from "utilities/endpoints";
|
||||
import { HostStatus } from "interfaces/host";
|
||||
import { FileVaultProfileStatus } from "interfaces/mdm";
|
||||
import {
|
||||
buildQueryStringFromParams,
|
||||
getLabelParam,
|
||||
reconcileMutuallyExclusiveHostParams,
|
||||
reconcileMutuallyInclusiveHostParams,
|
||||
} from "utilities/url";
|
||||
import { DiskEncryptionStatus } from "utilities/constants";
|
||||
|
||||
import { MacSettingsStatusQueryParam } from "./hosts";
|
||||
|
||||
export interface ISortOption {
|
||||
@ -42,7 +43,7 @@ export interface IHostCountLoadOptions {
|
||||
osId?: number;
|
||||
osName?: string;
|
||||
osVersion?: string;
|
||||
diskEncryptionStatus?: DiskEncryptionStatus;
|
||||
diskEncryptionStatus?: FileVaultProfileStatus;
|
||||
}
|
||||
|
||||
export default {
|
||||
|
@ -9,9 +9,8 @@ import {
|
||||
reconcileMutuallyInclusiveHostParams,
|
||||
} from "utilities/url";
|
||||
import { ISelectedPlatform } from "interfaces/platform";
|
||||
import { DiskEncryptionStatus } from "utilities/constants";
|
||||
import { ISoftware } from "interfaces/software";
|
||||
import { IMdmSolution } from "interfaces/mdm";
|
||||
import { FileVaultProfileStatus, IMdmSolution } from "interfaces/mdm";
|
||||
import { IMunkiIssuesAggregate } from "interfaces/macadmins";
|
||||
|
||||
export interface ISortOption {
|
||||
@ -54,7 +53,7 @@ export interface ILoadHostsOptions {
|
||||
device_mapping?: boolean;
|
||||
columns?: string;
|
||||
visibleColumns?: string;
|
||||
diskEncryptionStatus?: DiskEncryptionStatus;
|
||||
diskEncryptionStatus?: FileVaultProfileStatus;
|
||||
}
|
||||
|
||||
export interface IExportHostsOptions {
|
||||
@ -79,7 +78,7 @@ export interface IExportHostsOptions {
|
||||
device_mapping?: boolean;
|
||||
columns?: string;
|
||||
visibleColumns?: string;
|
||||
diskEncryptionStatus?: DiskEncryptionStatus;
|
||||
diskEncryptionStatus?: FileVaultProfileStatus;
|
||||
}
|
||||
|
||||
export interface IActionByFilter {
|
||||
|
@ -10,14 +10,6 @@ export enum PolicyResponse {
|
||||
FAILING = "failing",
|
||||
}
|
||||
|
||||
export enum DiskEncryptionStatus {
|
||||
APPLIED = "applied",
|
||||
ACTION_REQUIRED = "action_required",
|
||||
ENFORCING = "enforcing",
|
||||
FAILED = "failed",
|
||||
REMOVING_ENFORCEMENT = "removing_enforcement",
|
||||
}
|
||||
|
||||
export const DEFAULT_GRAVATAR_LINK =
|
||||
"https://fleetdm.com/images/permanent/icon-avatar-default-transparent-64x64%402x.png";
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { FileVaultProfileStatus } from "interfaces/mdm";
|
||||
import { isEmpty, reduce, omitBy, Dictionary } from "lodash";
|
||||
import { MacSettingsStatusQueryParam } from "services/entities/hosts";
|
||||
import { DiskEncryptionStatus } from "utilities/constants";
|
||||
|
||||
type QueryValues = string | number | boolean | undefined | null;
|
||||
export type QueryParams = Record<string, QueryValues>;
|
||||
@ -24,7 +24,7 @@ interface IMutuallyExclusiveHostParams {
|
||||
osId?: number;
|
||||
osName?: string;
|
||||
osVersion?: string;
|
||||
diskEncryptionStatus?: DiskEncryptionStatus;
|
||||
diskEncryptionStatus?: FileVaultProfileStatus;
|
||||
}
|
||||
|
||||
const reduceQueryParams = (
|
||||
|
@ -384,7 +384,7 @@ type MDMAppleConfigProfilesSummary struct {
|
||||
Pending uint `json:"pending" db:"pending"`
|
||||
// Failed includes each host that has failed to apply one or more of the profiles currently
|
||||
// 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
|
||||
|
Loading…
Reference in New Issue
Block a user