mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 09:28:54 +00:00
Fleet UI: Fix vulnfilter not supported (#31401)
--------- Co-authored-by: Ian Littman <iansltx@gmail.com>
This commit is contained in:
parent
e94d23e9db
commit
3ce6768845
6 changed files with 190 additions and 15 deletions
|
|
@ -998,6 +998,7 @@ const HostDetailsPage = ({
|
|||
onSetSelectedHostSWForInventoryVersions
|
||||
}
|
||||
hostTeamId={host.team_id || 0}
|
||||
hostMdmEnrollmentStatus={host.mdm.enrollment_status}
|
||||
/>
|
||||
{isDarwinHost && macadmins?.munki?.version && (
|
||||
<MunkiIssuesCard
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import deviceAPI, {
|
|||
} from "services/entities/device_user";
|
||||
import { IHostSoftware, ISoftware } from "interfaces/software";
|
||||
import { HostPlatform, isAndroid, isIPadOrIPhone } from "interfaces/platform";
|
||||
import { MdmEnrollmentStatus } from "interfaces/mdm";
|
||||
|
||||
import { DEFAULT_USE_QUERY_OPTIONS } from "utilities/constants";
|
||||
import { getNextLocationPath } from "utilities/helpers";
|
||||
|
|
@ -35,6 +36,7 @@ import {
|
|||
import { generateSoftwareTableHeaders as generateHostSoftwareTableConfig } from "./HostSoftwareTableConfig";
|
||||
import { generateSoftwareTableHeaders as generateDeviceSoftwareTableConfig } from "./DeviceSoftwareTableConfig";
|
||||
import HostSoftwareTable from "./HostSoftwareTable";
|
||||
import { getSoftwareSubheader } from "./helpers";
|
||||
|
||||
const baseClass = "software-card";
|
||||
|
||||
|
|
@ -59,6 +61,8 @@ interface IHostSoftwareProps {
|
|||
onShowInventoryVersions: (software: IHostSoftware) => void;
|
||||
isSoftwareEnabled?: boolean;
|
||||
isMyDevicePage?: boolean;
|
||||
/** Used to show custom Software card header */
|
||||
hostMdmEnrollmentStatus?: MdmEnrollmentStatus | null;
|
||||
}
|
||||
|
||||
const DEFAULT_SEARCH_QUERY = "";
|
||||
|
|
@ -121,6 +125,7 @@ const HostSoftware = ({
|
|||
onShowInventoryVersions,
|
||||
isSoftwareEnabled = false,
|
||||
isMyDevicePage = false,
|
||||
hostMdmEnrollmentStatus = null,
|
||||
}: IHostSoftwareProps) => {
|
||||
const { isPremiumTier } = useContext(AppContext);
|
||||
|
||||
|
|
@ -334,7 +339,11 @@ const HostSoftware = ({
|
|||
>
|
||||
<CardHeader
|
||||
header="Software"
|
||||
subheader="Software installed on your device"
|
||||
subheader={getSoftwareSubheader({
|
||||
platform,
|
||||
isMyDevicePage: true,
|
||||
hostMdmEnrollmentStatus,
|
||||
})}
|
||||
/>
|
||||
{renderHostSoftware()}
|
||||
</Card>
|
||||
|
|
@ -344,7 +353,13 @@ const HostSoftware = ({
|
|||
return (
|
||||
<div className={baseClass}>
|
||||
{!isAndroid(platform) && (
|
||||
<CardHeader subheader="Software installed on this host" />
|
||||
<CardHeader
|
||||
subheader={getSoftwareSubheader({
|
||||
platform,
|
||||
isMyDevicePage: false,
|
||||
hostMdmEnrollmentStatus,
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{renderHostSoftware()}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -65,4 +65,31 @@ describe("HostSoftwareTable", () => {
|
|||
});
|
||||
expect(screen.getByRole("button", { name: /filter/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders VulnsNotSupported when vulns filter applied and platform is iPad/iPhone", () => {
|
||||
renderWithContext({
|
||||
platform: "ipados",
|
||||
vulnFilters: { vulnerable: true },
|
||||
data: createMockGetHostSoftwareResponse({
|
||||
count: 0,
|
||||
software: [],
|
||||
}),
|
||||
});
|
||||
expect(
|
||||
screen.getByText(/vulnerabilities are not supported/i)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// This includes empty state for BYOD iphone/ipads
|
||||
it("renders generic empty state when no filters are applied and platform is iPad/iPhone", () => {
|
||||
renderWithContext({
|
||||
platform: "ipados",
|
||||
data: createMockGetHostSoftwareResponse({
|
||||
count: 0,
|
||||
software: [],
|
||||
}),
|
||||
});
|
||||
|
||||
expect(screen.getByText(/no software detected/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -42,6 +42,27 @@ const baseClass = "host-software-table";
|
|||
interface IHostSoftwareRowProps extends Row {
|
||||
original: IHostSoftware;
|
||||
}
|
||||
|
||||
interface IEmptyComponentProps {
|
||||
hasVulnFilters: boolean;
|
||||
platform: HostPlatform;
|
||||
searchQuery: string;
|
||||
}
|
||||
|
||||
const EmptyComponent = React.memo(
|
||||
({ hasVulnFilters, platform, searchQuery }: IEmptyComponentProps) => {
|
||||
const vulnFilterAndNotSupported =
|
||||
hasVulnFilters && isIPadOrIPhone(platform);
|
||||
return vulnFilterAndNotSupported ? (
|
||||
<VulnsNotSupported
|
||||
platformText={APPLE_PLATFORM_DISPLAY_NAMES[platform as ApplePlatform]}
|
||||
/>
|
||||
) : (
|
||||
<EmptySoftwareTable noSearchQuery={searchQuery === ""} />
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
interface IHostSoftwareTableProps {
|
||||
tableConfig: any; // TODO: type
|
||||
data?: IGetHostSoftwareResponse | IGetDeviceSoftwareResponse;
|
||||
|
|
@ -149,17 +170,6 @@ const HostSoftwareTable = ({
|
|||
return <TableCount name="items" count={count} />;
|
||||
}, [count, isSoftwareNotDetected]);
|
||||
|
||||
const memoizedEmptyComponent = useCallback(() => {
|
||||
const vulnFilterAndNotSupported = isIPadOrIPhone(platform);
|
||||
return vulnFilterAndNotSupported ? (
|
||||
<VulnsNotSupported
|
||||
platformText={APPLE_PLATFORM_DISPLAY_NAMES[platform as ApplePlatform]}
|
||||
/>
|
||||
) : (
|
||||
<EmptySoftwareTable noSearchQuery={searchQuery === ""} />
|
||||
);
|
||||
}, [platform, searchQuery]);
|
||||
|
||||
// Determines if a user should be able to filter or search in the table
|
||||
const hasData = data && data.software.length > 0;
|
||||
const hasQuery = searchQuery !== "";
|
||||
|
|
@ -223,7 +233,13 @@ const HostSoftwareTable = ({
|
|||
pageSize={DEFAULT_PAGE_SIZE}
|
||||
inputPlaceHolder="Search by name or vulnerability (CVE)"
|
||||
onQueryChange={onQueryChange}
|
||||
emptyComponent={memoizedEmptyComponent}
|
||||
emptyComponent={() => (
|
||||
<EmptyComponent
|
||||
hasVulnFilters={hasVulnFilters}
|
||||
platform={platform}
|
||||
searchQuery={searchQuery}
|
||||
/>
|
||||
)}
|
||||
customFiltersButton={
|
||||
showFilterHeaders ? renderCustomFiltersButton : undefined
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import {
|
|||
createMockHostSoftware,
|
||||
createMockHostSoftwarePackage,
|
||||
} from "__mocks__/hostMock";
|
||||
import { compareVersions, getUiStatus } from "./helpers";
|
||||
import { compareVersions, getUiStatus, getSoftwareSubheader } from "./helpers";
|
||||
|
||||
describe("compareVersions", () => {
|
||||
it("correctly compares patch increments", () => {
|
||||
|
|
@ -212,3 +212,85 @@ describe("getUiStatus", () => {
|
|||
expect(getUiStatus(sw, true)).toBe("uninstalled");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getSoftwareSubheader", () => {
|
||||
test("iOS device, MDM status 'On (personal)', my device page", () => {
|
||||
const result = getSoftwareSubheader({
|
||||
platform: "ios",
|
||||
hostMdmEnrollmentStatus: "On (personal)",
|
||||
isMyDevicePage: true,
|
||||
});
|
||||
expect(result).toBe(
|
||||
"Software installed on your work profile (Managed Apple Account)."
|
||||
);
|
||||
});
|
||||
|
||||
test("iOS device, MDM status 'On (personal)', NOT my device page", () => {
|
||||
const result = getSoftwareSubheader({
|
||||
platform: "ios",
|
||||
hostMdmEnrollmentStatus: "On (personal)",
|
||||
isMyDevicePage: false,
|
||||
});
|
||||
expect(result).toBe(
|
||||
"Software installed on work profile (Managed Apple Account)."
|
||||
);
|
||||
});
|
||||
|
||||
test("iOS device, MDM status 'On (manual)', my device page", () => {
|
||||
const result = getSoftwareSubheader({
|
||||
platform: "ios",
|
||||
hostMdmEnrollmentStatus: "On (manual)",
|
||||
isMyDevicePage: true,
|
||||
});
|
||||
expect(result).toBe(
|
||||
"Software installed on your device. Built-in apps (e.g. Calculator) aren't included."
|
||||
);
|
||||
});
|
||||
|
||||
test("iOS device, MDM status 'On (manual)', NOT my device page", () => {
|
||||
const result = getSoftwareSubheader({
|
||||
platform: "ios",
|
||||
hostMdmEnrollmentStatus: "On (manual)",
|
||||
isMyDevicePage: false,
|
||||
});
|
||||
expect(result).toBe(
|
||||
"Software installed on this host. Built-in apps (e.g. Calculator) aren't included."
|
||||
);
|
||||
});
|
||||
|
||||
test("iOS device, MDM status not special, my device page", () => {
|
||||
const result = getSoftwareSubheader({
|
||||
platform: "ios",
|
||||
hostMdmEnrollmentStatus: "Off",
|
||||
isMyDevicePage: true,
|
||||
});
|
||||
expect(result).toBe("Software installed on your device.");
|
||||
});
|
||||
|
||||
test("iOS device, MDM status not special, NOT my device page", () => {
|
||||
const result = getSoftwareSubheader({
|
||||
platform: "ios",
|
||||
hostMdmEnrollmentStatus: "Off",
|
||||
isMyDevicePage: false,
|
||||
});
|
||||
expect(result).toBe("Software installed on this host.");
|
||||
});
|
||||
|
||||
test("default (NOT iOS device) my device page", () => {
|
||||
const result = getSoftwareSubheader({
|
||||
platform: "windows",
|
||||
hostMdmEnrollmentStatus: "Off",
|
||||
isMyDevicePage: true,
|
||||
});
|
||||
expect(result).toBe("Software installed on your device.");
|
||||
});
|
||||
|
||||
test("default (NOT iOS device) NOT my device page", () => {
|
||||
const result = getSoftwareSubheader({
|
||||
platform: "windows",
|
||||
hostMdmEnrollmentStatus: "Off",
|
||||
isMyDevicePage: false,
|
||||
});
|
||||
expect(result).toBe("Software installed on this host.");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import { QueryParams } from "utilities/url";
|
||||
import { Row } from "react-table";
|
||||
import { flatMap } from "lodash";
|
||||
import { HostPlatform, isIPadOrIPhone } from "interfaces/platform";
|
||||
import { MdmEnrollmentStatus } from "interfaces/mdm";
|
||||
import {
|
||||
IHostSoftware,
|
||||
IHostSoftwareUiStatus,
|
||||
|
|
@ -341,3 +343,35 @@ export const installStatusSortType = (
|
|||
if (safeIndexA > safeIndexB) return 1;
|
||||
return 0;
|
||||
};
|
||||
|
||||
interface IGetSoftwareSubheader {
|
||||
platform: HostPlatform;
|
||||
hostMdmEnrollmentStatus: MdmEnrollmentStatus | null;
|
||||
isMyDevicePage?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a subheader string for the software page based on platform and MDM enrollment status.
|
||||
* Handles iOS-specific cases for personal and manual MDM enrollment.
|
||||
*/
|
||||
export const getSoftwareSubheader = ({
|
||||
platform,
|
||||
hostMdmEnrollmentStatus,
|
||||
isMyDevicePage,
|
||||
}: IGetSoftwareSubheader): string => {
|
||||
if (isIPadOrIPhone(platform)) {
|
||||
if (hostMdmEnrollmentStatus === "On (personal)") {
|
||||
return isMyDevicePage
|
||||
? "Software installed on your work profile (Managed Apple Account)."
|
||||
: "Software installed on work profile (Managed Apple Account).";
|
||||
}
|
||||
if (hostMdmEnrollmentStatus === "On (manual)") {
|
||||
return isMyDevicePage
|
||||
? "Software installed on your device. Built-in apps (e.g. Calculator) aren't included."
|
||||
: "Software installed on this host. Built-in apps (e.g. Calculator) aren't included.";
|
||||
}
|
||||
}
|
||||
return isMyDevicePage
|
||||
? "Software installed on your device."
|
||||
: "Software installed on this host.";
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue