mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 17:08:53 +00:00
Feat UI host filter by custom profiles (#29038)
For #28759 This is the UI work for being able to filter hosts by a configuration profile status. There are also added tests in this PR.  - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. - [x] Added/updated automated tests - [x] Manual QA for all new/changed functionality
This commit is contained in:
parent
e637e7e1a7
commit
5815dc4e54
16 changed files with 275 additions and 47 deletions
|
|
@ -0,0 +1 @@
|
|||
- add UI to filter hosts by config profile status.
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
import React from "react";
|
||||
import { screen, within } from "@testing-library/react";
|
||||
import { noop } from "lodash";
|
||||
|
||||
import { createCustomRenderer } from "test/test-utils";
|
||||
import mockServer from "test/mock-server";
|
||||
import { defaultConfigProfileStatusHandler } from "test/handlers/config-profiles";
|
||||
|
||||
import ConfigProfileStatusModal from "./ConfigProfileStatusModal";
|
||||
|
||||
describe("ConfigProfileStatusModal", () => {
|
||||
const render = createCustomRenderer({
|
||||
withBackendMock: true,
|
||||
});
|
||||
|
||||
it("renders the correct number of hosts for each status", async () => {
|
||||
mockServer.use(defaultConfigProfileStatusHandler);
|
||||
render(
|
||||
<ConfigProfileStatusModal
|
||||
name="Test profile"
|
||||
uuid="123-abc"
|
||||
teamId={0}
|
||||
onClickResend={noop}
|
||||
onExit={noop}
|
||||
/>
|
||||
);
|
||||
|
||||
await screen.findByText("Verified");
|
||||
|
||||
// get all rows in the table and skip header row
|
||||
const rows = screen.getAllByRole("row").slice(1);
|
||||
|
||||
const verifiedRow = within(rows[0]).getAllByRole("cell");
|
||||
expect(verifiedRow[0]).toHaveTextContent("Verified");
|
||||
expect(verifiedRow[1]).toHaveTextContent("---");
|
||||
|
||||
const verifiyingRow = within(rows[1]).getAllByRole("cell");
|
||||
expect(verifiyingRow[0]).toHaveTextContent("Verifying");
|
||||
expect(verifiyingRow[1]).toHaveTextContent("1");
|
||||
|
||||
const pendingRow = within(rows[2]).getAllByRole("cell");
|
||||
expect(pendingRow[0]).toHaveTextContent("Pending");
|
||||
expect(pendingRow[1]).toHaveTextContent("2");
|
||||
|
||||
const failedRow = within(rows[3]).getAllByRole("cell");
|
||||
expect(failedRow[0]).toHaveTextContent("Failed");
|
||||
expect(failedRow[1]).toHaveTextContent("3");
|
||||
});
|
||||
|
||||
it("shows the resend button for a failed row on hover", async () => {
|
||||
mockServer.use(defaultConfigProfileStatusHandler);
|
||||
const { user } = render(
|
||||
<ConfigProfileStatusModal
|
||||
name="Test profile"
|
||||
uuid="123-abc"
|
||||
teamId={0}
|
||||
onClickResend={noop}
|
||||
onExit={noop}
|
||||
/>
|
||||
);
|
||||
|
||||
await screen.findByText("Verified");
|
||||
|
||||
const failedRow = screen.getByText("Failed").closest("tr");
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
user.hover(failedRow!);
|
||||
|
||||
const resendButton = screen.getByRole("button", { name: "Resend" });
|
||||
expect(resendButton).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,15 +1,17 @@
|
|||
import React from "react";
|
||||
import { Column } from "react-table";
|
||||
|
||||
import StatusIndicatorWithIcon from "components/StatusIndicatorWithIcon";
|
||||
import {
|
||||
INumberCellProps,
|
||||
IStringCellProps,
|
||||
} from "interfaces/datatable_config";
|
||||
import { MdmProfileStatus } from "interfaces/mdm";
|
||||
import { IGetConfigProfileStatusResponse } from "services/entities/config_profiles";
|
||||
import ConfigProfileHostCountCell from "../ConfigProfileHostCountCell";
|
||||
|
||||
type IConfigProfileStatus = "verified" | "verifying" | "pending" | "failed";
|
||||
import StatusIndicatorWithIcon from "components/StatusIndicatorWithIcon";
|
||||
import { IndicatorStatus } from "components/StatusIndicatorWithIcon/StatusIndicatorWithIcon";
|
||||
|
||||
import ConfigProfileHostCountCell from "../ConfigProfileHostCountCell";
|
||||
|
||||
interface IConfigProfileRowData {
|
||||
status: string;
|
||||
|
|
@ -22,11 +24,10 @@ const STAUTS_ORDER = ["verified", "verifying", "pending", "failed"];
|
|||
|
||||
export interface IStatusCellValue {
|
||||
displayName: string;
|
||||
statusName: IConfigProfileStatus;
|
||||
value: IConfigProfileStatus;
|
||||
statusName: IndicatorStatus;
|
||||
}
|
||||
|
||||
const STATUS_DISPLAY_OPTIONS = {
|
||||
const STATUS_DISPLAY_OPTIONS: Record<MdmProfileStatus, IStatusCellValue> = {
|
||||
verified: {
|
||||
displayName: "Verified",
|
||||
statusName: "success",
|
||||
|
|
@ -43,7 +44,7 @@ const STATUS_DISPLAY_OPTIONS = {
|
|||
displayName: "Failed",
|
||||
statusName: "error",
|
||||
},
|
||||
} as const;
|
||||
};
|
||||
|
||||
type IConfigProfileStatusColumnConfig = Column<IConfigProfileRowData>;
|
||||
type IStatusCellProps = IStringCellProps<IConfigProfileRowData>;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ const generateMessageSuffix = (isPremiumTier?: boolean, teamId?: number) => {
|
|||
if (!isPremiumTier) {
|
||||
return "";
|
||||
}
|
||||
return teamId ? " assigned to this team" : " with no team";
|
||||
return teamId ? "assigned to this team" : "with no team";
|
||||
};
|
||||
|
||||
const DeleteProfileModal = ({
|
||||
|
|
@ -42,11 +42,13 @@ const DeleteProfileModal = ({
|
|||
width="large"
|
||||
>
|
||||
<>
|
||||
<p>
|
||||
This action will delete configuration profile{" "}
|
||||
<span className={`${baseClass}__profile-name`}>{profileName}</span>{" "}
|
||||
from all hosts{messageSuffix}.
|
||||
</p>
|
||||
<div className={`${baseClass}__content`}>
|
||||
<p>
|
||||
This action will remove the <b>{profileName}</b> configuration
|
||||
profile from all hosts {messageSuffix}.
|
||||
</p>
|
||||
<p>Pending profiles will be canceled.</p>
|
||||
</div>
|
||||
<div className="modal-cta-wrap">
|
||||
<Button
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,14 @@
|
|||
.delete-profile-modal {
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $pad-large;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__profile-name {
|
||||
font-weight: $bold;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ const ResendConfigProfileModal = ({
|
|||
Resent the <b>{name}</b> configuration profile.
|
||||
</>
|
||||
);
|
||||
onExit();
|
||||
} catch (error) {
|
||||
renderFlash(
|
||||
"error",
|
||||
|
|
@ -43,7 +44,6 @@ const ResendConfigProfileModal = ({
|
|||
);
|
||||
}
|
||||
setIsResending(false);
|
||||
onExit();
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ import hostCountAPI, {
|
|||
IHostsCountQueryKey,
|
||||
IHostsCountResponse,
|
||||
} from "services/entities/host_count";
|
||||
import configProfileAPI, {
|
||||
IGetConfigProfileResponse,
|
||||
} from "services/entities/config_profiles";
|
||||
|
||||
import {
|
||||
getOSVersions,
|
||||
|
|
@ -281,6 +284,8 @@ const ManageHostsPage = ({
|
|||
queryParams?.[PARAMS.DISK_ENCRYPTION];
|
||||
const bootstrapPackageStatus: BootstrapPackageStatus | undefined =
|
||||
queryParams?.bootstrap_package;
|
||||
const configProfileStatus = queryParams?.profile_status;
|
||||
const configProfileUUID = queryParams?.profile_uuid;
|
||||
|
||||
// ========= routeParams
|
||||
const { active_label: activeLabel, label_id: labelID } = routeParams;
|
||||
|
|
@ -368,6 +373,19 @@ const ManageHostsPage = ({
|
|||
}
|
||||
);
|
||||
|
||||
const {
|
||||
data: configProfile,
|
||||
isLoading: isLoadingConfigProfile,
|
||||
error: errorConfigProfile,
|
||||
} = useQuery<IGetConfigProfileResponse, Error>(
|
||||
["config-profile", configProfileUUID],
|
||||
() => configProfileAPI.getConfigProfile(configProfileUUID),
|
||||
{
|
||||
enabled: isRouteOk && !!configProfileUUID,
|
||||
// select: (data) => data.policy,
|
||||
}
|
||||
);
|
||||
|
||||
const { data: osVersions } = useQuery<
|
||||
IOSVersionsResponse,
|
||||
Error,
|
||||
|
|
@ -422,6 +440,8 @@ const ManageHostsPage = ({
|
|||
diskEncryptionStatus,
|
||||
bootstrapPackageStatus,
|
||||
macSettingsStatus,
|
||||
configProfileStatus,
|
||||
configProfileUUID,
|
||||
},
|
||||
],
|
||||
({ queryKey }) => hostsAPI.loadHosts(queryKey[0]),
|
||||
|
|
@ -463,6 +483,8 @@ const ManageHostsPage = ({
|
|||
diskEncryptionStatus,
|
||||
bootstrapPackageStatus,
|
||||
macSettingsStatus,
|
||||
configProfileStatus,
|
||||
configProfileUUID,
|
||||
},
|
||||
],
|
||||
({ queryKey }) => hostCountAPI.load(queryKey[0]),
|
||||
|
|
@ -502,7 +524,8 @@ const ManageHostsPage = ({
|
|||
refetchHostsCountAPI();
|
||||
};
|
||||
|
||||
const hasErrors = !!errorHosts || !!errorHostsCount || !!errorPolicy;
|
||||
const hasErrors =
|
||||
!!errorHosts || !!errorHostsCount || !!errorPolicy || !!errorConfigProfile;
|
||||
|
||||
const toggleDeleteSecretModal = () => {
|
||||
// open and closes delete modal
|
||||
|
|
@ -757,6 +780,22 @@ const ManageHostsPage = ({
|
|||
);
|
||||
};
|
||||
|
||||
const handleConfigProfileStatusChange = (newStatus: string) => {
|
||||
router.replace(
|
||||
getNextLocationPath({
|
||||
pathPrefix: PATHS.MANAGE_HOSTS,
|
||||
routeTemplate,
|
||||
routeParams,
|
||||
queryParams: {
|
||||
...queryParams,
|
||||
profile_status: newStatus,
|
||||
profile_uuid: configProfileUUID,
|
||||
page: 0, // resets page index
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleRowSelect = (row: IRowProps) => {
|
||||
if (row.original.id) {
|
||||
const path = PATHS.HOST_DETAILS(row.original.id);
|
||||
|
|
@ -894,6 +933,9 @@ const ManageHostsPage = ({
|
|||
newQueryParams[PARAMS.DISK_ENCRYPTION] = diskEncryptionStatus;
|
||||
} else if (bootstrapPackageStatus && isPremiumTier) {
|
||||
newQueryParams.bootstrap_package = bootstrapPackageStatus;
|
||||
} else if (configProfileStatus && configProfileUUID) {
|
||||
newQueryParams.profile_status = configProfileStatus;
|
||||
newQueryParams.profile_uuid = configProfileUUID;
|
||||
}
|
||||
|
||||
router.replace(
|
||||
|
|
@ -918,7 +960,6 @@ const ManageHostsPage = ({
|
|||
softwareId,
|
||||
softwareVersionId,
|
||||
softwareTitleId,
|
||||
softwareStatus,
|
||||
mdmId,
|
||||
mdmEnrollmentStatus,
|
||||
munkiIssueId,
|
||||
|
|
@ -928,13 +969,16 @@ const ManageHostsPage = ({
|
|||
osVersionId,
|
||||
osName,
|
||||
osVersion,
|
||||
router,
|
||||
routeTemplate,
|
||||
routeParams,
|
||||
vulnerability,
|
||||
osSettingsStatus,
|
||||
diskEncryptionStatus,
|
||||
bootstrapPackageStatus,
|
||||
vulnerability,
|
||||
configProfileStatus,
|
||||
configProfileUUID,
|
||||
router,
|
||||
routeTemplate,
|
||||
routeParams,
|
||||
softwareStatus,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
@ -1610,7 +1654,12 @@ const ManageHostsPage = ({
|
|||
resultsTitle="hosts"
|
||||
columnConfigs={tableColumns}
|
||||
data={hostsData?.hosts || []}
|
||||
isLoading={isLoadingHosts || isLoadingHostsCount || isLoadingPolicy}
|
||||
isLoading={
|
||||
isLoadingHosts ||
|
||||
isLoadingHostsCount ||
|
||||
isLoadingPolicy ||
|
||||
isLoadingConfigProfile
|
||||
}
|
||||
manualSortBy
|
||||
defaultSortHeader={(sortBy[0] && sortBy[0].key) || DEFAULT_SORT_HEADER}
|
||||
defaultSortDirection={
|
||||
|
|
@ -1746,6 +1795,9 @@ const ManageHostsPage = ({
|
|||
diskEncryptionStatus,
|
||||
bootstrapPackageStatus,
|
||||
vulnerability,
|
||||
configProfileStatus,
|
||||
configProfileUUID,
|
||||
configProfile,
|
||||
}}
|
||||
selectedLabel={selectedLabel}
|
||||
isOnlyObserver={isOnlyObserver}
|
||||
|
|
@ -1763,6 +1815,7 @@ const ManageHostsPage = ({
|
|||
onChangeSoftwareInstallStatusFilter={
|
||||
handleSoftwareInstallStatusChange
|
||||
}
|
||||
onChangeConfigProfileStatusFilter={handleConfigProfileStatusChange}
|
||||
onClickEditLabel={onEditLabelClick}
|
||||
onClickDeleteLabel={toggleDeleteLabelModal}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import React, { ReactNode } from "react";
|
||||
import React, { ReactNode, useRef } from "react";
|
||||
import ReactTooltip from "react-tooltip";
|
||||
import classnames from "classnames";
|
||||
|
||||
import Button from "components/buttons/Button";
|
||||
import { useCheckTruncatedElement } from "hooks/useCheckTruncatedElement";
|
||||
import { COLORS } from "styles/var/colors";
|
||||
|
||||
import Button from "components/buttons/Button";
|
||||
import Icon from "components/Icon";
|
||||
import { IconNames } from "components/icons";
|
||||
import { COLORS } from "styles/var/colors";
|
||||
|
||||
interface IFilterPillProps {
|
||||
label: string;
|
||||
|
|
@ -30,6 +31,9 @@ const FilterPill = ({
|
|||
tooltip: tooltipDescription !== undefined && tooltipDescription !== "",
|
||||
});
|
||||
|
||||
const pillText = useRef(null);
|
||||
const isTuncated = useCheckTruncatedElement(pillText);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={baseClasses}
|
||||
|
|
@ -43,6 +47,8 @@ const FilterPill = ({
|
|||
<span
|
||||
data-tip={tooltipDescription}
|
||||
data-for={`filter-pill-tooltip-${label}`}
|
||||
className={`${baseClass}__tooltip-text`}
|
||||
ref={pillText}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
|
|
@ -59,11 +65,12 @@ const FilterPill = ({
|
|||
{tooltipDescription && (
|
||||
<ReactTooltip
|
||||
role="tooltip"
|
||||
place="bottom"
|
||||
place="top"
|
||||
effect="solid"
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
id={`filter-pill-tooltip-${label}`}
|
||||
data-html
|
||||
disable={!isTuncated}
|
||||
>
|
||||
<span>{tooltipDescription}</span>
|
||||
</ReactTooltip>
|
||||
|
|
|
|||
|
|
@ -23,4 +23,8 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__tooltip-text {
|
||||
@include ellipse-text(166px);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
IMdmSolution,
|
||||
MDM_ENROLLMENT_STATUS,
|
||||
MdmProfileStatus,
|
||||
IMdmProfile,
|
||||
} from "interfaces/mdm";
|
||||
import { IMunkiIssuesAggregate } from "interfaces/macadmins";
|
||||
import { IPolicy } from "interfaces/policy";
|
||||
|
|
@ -77,6 +78,9 @@ interface IHostsFilterBlockProps {
|
|||
diskEncryptionStatus?: DiskEncryptionStatus;
|
||||
bootstrapPackageStatus?: BootstrapPackageStatus;
|
||||
softwareStatus?: SoftwareAggregateStatus;
|
||||
configProfileStatus?: string;
|
||||
configProfileUUID?: string;
|
||||
configProfile?: IMdmProfile;
|
||||
};
|
||||
selectedLabel?: ILabel;
|
||||
isOnlyObserver?: boolean;
|
||||
|
|
@ -94,6 +98,7 @@ interface IHostsFilterBlockProps {
|
|||
onChangeSoftwareInstallStatusFilter: (
|
||||
newStatus: SoftwareAggregateStatus
|
||||
) => void;
|
||||
onChangeConfigProfileStatusFilter: (newStatus: string) => void;
|
||||
onClickEditLabel: (evt: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
onClickDeleteLabel: () => void;
|
||||
}
|
||||
|
|
@ -127,6 +132,9 @@ const HostsFilterBlock = ({
|
|||
diskEncryptionStatus,
|
||||
bootstrapPackageStatus,
|
||||
softwareStatus,
|
||||
configProfileStatus,
|
||||
configProfileUUID,
|
||||
configProfile,
|
||||
},
|
||||
selectedLabel,
|
||||
isOnlyObserver,
|
||||
|
|
@ -138,6 +146,7 @@ const HostsFilterBlock = ({
|
|||
onChangeBootstrapPackageStatusFilter,
|
||||
onChangeMacSettingsFilter,
|
||||
onChangeSoftwareInstallStatusFilter,
|
||||
onChangeConfigProfileStatusFilter,
|
||||
onClickEditLabel,
|
||||
onClickDeleteLabel,
|
||||
}: IHostsFilterBlockProps) => {
|
||||
|
|
@ -308,19 +317,10 @@ const HostsFilterBlock = ({
|
|||
clearParams.push(...additionalClearParams);
|
||||
}
|
||||
|
||||
// const TooltipDescription = (
|
||||
// <span>
|
||||
// Hosts with {name || "Unknown software"},
|
||||
// <br />
|
||||
// {version || "version unknown"} installed
|
||||
// </span>
|
||||
// );
|
||||
|
||||
return (
|
||||
<FilterPill
|
||||
label={label}
|
||||
onClear={() => handleClearFilter(clearParams)}
|
||||
// tooltipDescription={TooltipDescription}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
@ -511,6 +511,38 @@ const HostsFilterBlock = ({
|
|||
);
|
||||
};
|
||||
|
||||
const renderConfigProfileStatusBlock = () => {
|
||||
const OPTIONS = [
|
||||
{ value: "verified", label: "Verified" },
|
||||
{ value: "verifying", label: "Verifying" },
|
||||
{ value: "pending", label: "Pending" },
|
||||
{ value: "failed", label: "Failed" },
|
||||
];
|
||||
return (
|
||||
<>
|
||||
<Dropdown
|
||||
value={configProfileStatus}
|
||||
className={`${baseClass}__config-profile-status-dropdown`}
|
||||
options={OPTIONS}
|
||||
searchable={false}
|
||||
onChange={onChangeConfigProfileStatusFilter}
|
||||
iconName="filter-alt"
|
||||
/>
|
||||
<FilterPill
|
||||
label={`OS settings: ${configProfile?.name}`}
|
||||
onClear={() => handleClearFilter(["profile_status", "profile_uuid"])}
|
||||
tooltipDescription={
|
||||
<>
|
||||
OS settings:
|
||||
<br />
|
||||
{configProfile?.name}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const showSelectedLabel = selectedLabel && selectedLabel.type !== "all";
|
||||
|
||||
if (
|
||||
|
|
@ -530,7 +562,8 @@ const HostsFilterBlock = ({
|
|||
osSettingsStatus ||
|
||||
diskEncryptionStatus ||
|
||||
bootstrapPackageStatus ||
|
||||
vulnerability
|
||||
vulnerability ||
|
||||
(configProfileStatus && configProfileUUID && configProfile)
|
||||
) {
|
||||
const renderFilterPill = () => {
|
||||
switch (true) {
|
||||
|
|
@ -583,6 +616,8 @@ const HostsFilterBlock = ({
|
|||
return renderDiskEncryptionStatusBlock();
|
||||
case !!bootstrapPackageStatus:
|
||||
return renderBootstrapPackageStatusBlock();
|
||||
case !!configProfileStatus && !!configProfileUUID && !!configProfile:
|
||||
return renderConfigProfileStatusBlock();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { getConfig } from "@testing-library/react";
|
||||
import { profile } from "console";
|
||||
import { IMdmProfile } from "interfaces/mdm";
|
||||
import sendRequest from "services";
|
||||
import endpoints from "utilities/endpoints";
|
||||
|
||||
export type IGetConfigProfileResponse = IMdmProfile;
|
||||
|
||||
export interface IGetConfigProfileStatusResponse {
|
||||
verified: number;
|
||||
verifying: number;
|
||||
|
|
@ -11,20 +12,16 @@ export interface IGetConfigProfileStatusResponse {
|
|||
}
|
||||
|
||||
export default {
|
||||
getConfigProfile: (uuid: string): Promise<IGetConfigProfileResponse> => {
|
||||
const { CONFIG_PROFILE } = endpoints;
|
||||
return sendRequest("GET", CONFIG_PROFILE(uuid));
|
||||
},
|
||||
|
||||
getConfigProfileStatus: (
|
||||
uuid: string
|
||||
): Promise<IGetConfigProfileStatusResponse> => {
|
||||
const { CONFIG_PROFILE_STATUS } = endpoints;
|
||||
// return sendRequest("GET", CONFIG_PROFILE_STATUS(uuid));
|
||||
|
||||
return new Promise((resolve) => {
|
||||
resolve({
|
||||
verified: 0,
|
||||
verifying: 1,
|
||||
failed: 2,
|
||||
pending: 3,
|
||||
});
|
||||
});
|
||||
return sendRequest("GET", CONFIG_PROFILE_STATUS(uuid));
|
||||
},
|
||||
|
||||
batchResendConfigProfile: (uuid: string): Promise<void> => {
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@ export interface IHostCountLoadOptions {
|
|||
vulnerability?: string;
|
||||
diskEncryptionStatus?: DiskEncryptionStatus;
|
||||
bootstrapPackageStatus?: BootstrapPackageStatus;
|
||||
configProfileStatus?: string;
|
||||
configProfileUUID?: string;
|
||||
}
|
||||
|
||||
export default {
|
||||
|
|
@ -83,6 +85,8 @@ export default {
|
|||
const vulnerability = options?.vulnerability;
|
||||
const diskEncryptionStatus = options?.diskEncryptionStatus;
|
||||
const bootstrapPackageStatus = options?.bootstrapPackageStatus;
|
||||
const configProfileStatus = options?.configProfileStatus;
|
||||
const configProfileUUID = options?.configProfileUUID;
|
||||
|
||||
const queryParams = {
|
||||
query: globalFilter,
|
||||
|
|
@ -113,6 +117,8 @@ export default {
|
|||
vulnerability,
|
||||
diskEncryptionStatus,
|
||||
bootstrapPackageStatus,
|
||||
configProfileStatus,
|
||||
configProfileUUID,
|
||||
}),
|
||||
label_id: label,
|
||||
status,
|
||||
|
|
|
|||
|
|
@ -90,6 +90,8 @@ export interface ILoadHostsOptions {
|
|||
osSettings?: MdmProfileStatus;
|
||||
diskEncryptionStatus?: DiskEncryptionStatus;
|
||||
bootstrapPackageStatus?: BootstrapPackageStatus;
|
||||
configProfileStatus?: string;
|
||||
configProfileUUID?: string;
|
||||
}
|
||||
|
||||
export interface IExportHostsOptions {
|
||||
|
|
@ -411,6 +413,8 @@ export default {
|
|||
osSettings,
|
||||
diskEncryptionStatus,
|
||||
bootstrapPackageStatus,
|
||||
configProfileStatus,
|
||||
configProfileUUID,
|
||||
}: ILoadHostsOptions): Promise<ILoadHostsResponse> => {
|
||||
const label = getLabel(selectedLabels);
|
||||
const sortParams = getSortParams(sortBy);
|
||||
|
|
@ -449,6 +453,8 @@ export default {
|
|||
diskEncryptionStatus,
|
||||
osSettings,
|
||||
bootstrapPackageStatus,
|
||||
configProfileStatus,
|
||||
configProfileUUID,
|
||||
}),
|
||||
};
|
||||
|
||||
|
|
|
|||
24
frontend/test/handlers/config-profiles.ts
Normal file
24
frontend/test/handlers/config-profiles.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import createMockConfig from "__mocks__/configMock";
|
||||
import { IConfig } from "interfaces/config";
|
||||
import { http, HttpResponse } from "msw";
|
||||
import { baseUrl } from "test/test-utils";
|
||||
|
||||
const configProfileURL = baseUrl("/configuration_profiles");
|
||||
|
||||
export const createGetConfigHandler = (overrides?: Partial<IConfig>) => {
|
||||
return http.get(configProfileURL, () => {
|
||||
return HttpResponse.json(createMockConfig({ ...overrides }));
|
||||
});
|
||||
};
|
||||
|
||||
export const defaultConfigProfileStatusHandler = http.get(
|
||||
`${configProfileURL}/:uuid/status`,
|
||||
() => {
|
||||
return HttpResponse.json({
|
||||
verified: 0,
|
||||
verifying: 1,
|
||||
pending: 2,
|
||||
failed: 3,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
@ -274,6 +274,8 @@ export default {
|
|||
SCIM_DETAILS: `/${API_VERSION}/fleet/scim/details`,
|
||||
|
||||
// configuration profile endpoints
|
||||
CONFIG_PROFILE: (uuid: string) =>
|
||||
`/${API_VERSION}/fleet/configuration_profiles/${uuid}`,
|
||||
CONFIG_PROFILE_STATUS: (uuid: string) =>
|
||||
`/${API_VERSION}/fleet/configuration_profiles/${uuid}/status`,
|
||||
CONFIG_PROFILE_BATCH_RESEND: `/${API_VERSION}/fleet/configuration_profiles/batch/resend`,
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ interface IMutuallyExclusiveHostParams {
|
|||
osSettings?: MdmProfileStatus;
|
||||
diskEncryptionStatus?: DiskEncryptionStatus;
|
||||
bootstrapPackageStatus?: BootstrapPackageStatus;
|
||||
configProfileStatus?: string;
|
||||
configProfileUUID?: string;
|
||||
}
|
||||
|
||||
export const parseQueryValueToNumberOrUndefined = (
|
||||
|
|
@ -216,6 +218,8 @@ export const reconcileMutuallyExclusiveHostParams = ({
|
|||
vulnerability,
|
||||
diskEncryptionStatus,
|
||||
bootstrapPackageStatus,
|
||||
configProfileStatus,
|
||||
configProfileUUID,
|
||||
}: IMutuallyExclusiveHostParams): Record<string, unknown> => {
|
||||
if (label) {
|
||||
// backend api now allows (label + low disk space) OR (label + mdm id) OR
|
||||
|
|
@ -270,6 +274,11 @@ export const reconcileMutuallyExclusiveHostParams = ({
|
|||
return { [HOSTS_QUERY_PARAMS.DISK_ENCRYPTION]: diskEncryptionStatus };
|
||||
case !!bootstrapPackageStatus:
|
||||
return { bootstrap_package: bootstrapPackageStatus };
|
||||
case !!configProfileUUID:
|
||||
return {
|
||||
profile_status: configProfileStatus,
|
||||
profile_uuid: configProfileUUID,
|
||||
};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue