From 19a64941ba010a49adef3eec08d472d0410f8036 Mon Sep 17 00:00:00 2001 From: jacobshandling <61553566+jacobshandling@users.noreply.github.com> Date: Tue, 30 Jul 2024 10:14:25 -0700 Subject: [PATCH] =?UTF-8?q?UI=20=E2=80=93=20Add=20VPP=20features=20for=20i?= =?UTF-8?q?PadOS=20and=20iOS=20(#20755)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Addresses #20467 – part 2 ### Aggregate software: #### Software titles sw-titles-updated #### Software versions Screenshot 2024-07-29 at 6 14 21 PM #### Host software Screenshot 2024-07-29 at 6 23 01 PM ### Add software modal (VPP) _screenshots use mocked data - UI is flexible enough to display cleanly before and after backend is in place:_ happy #### No apps: Screenshot 2024-07-29 at 6 35 03 PM #### Not enabled: Screenshot 2024-07-29 at 6 37 45 PM #### Error: Screenshot 2024-07-29 at 6 39 39 PM ### Vuln support – Not supported for now: _see above screenshots for `list` endpoints_ #### Software title detail Screenshot 2024-07-29 at 6 47 29 PM #### Software version and OS detail: Screenshot 2024-07-29 at 6 48 28 PM Screenshot 2024-07-29 at 6 50 25 PM - [x] Changes file added for user-visible changes in `changes/`, - [x] Added/updated tests - [x] Manual QA for all new/changed functionality --------- Co-authored-by: Jacob Shandling --- changes/20467-vpp-ipadios-ui | 1 + frontend/__mocks__/appleMdm.ts | 3 +- frontend/components/DataError/DataError.tsx | 30 ++ frontend/components/DataError/_styles.scss | 17 + .../DataTable/LinkCell/LinkCell.tsx | 7 +- .../forms/fields/Dropdown/_styles.scss | 4 +- frontend/components/graphics/DataError.tsx | 404 ++++++++++++++++++ frontend/components/graphics/index.ts | 2 + frontend/interfaces/activity.ts | 2 + frontend/interfaces/platform.ts | 30 +- frontend/interfaces/software.ts | 6 +- .../ActivityItem/ActivityItem.tsx | 15 +- .../OperatingSystemsTableConfig.tsx | 2 +- frontend/pages/NoAccessPage/NoAccessPage.tsx | 4 +- .../SoftwareOSDetailsPage.tsx | 59 +-- .../SoftwareTitleDetailsPage.tsx | 3 + .../SoftwareTitleDetailsTable.tsx | 10 +- .../SoftwareTitleDetailsTableConfig.tsx | 11 +- .../SoftwareTitleDetailsPage/helpers.ts | 8 +- .../SoftwareTitlesTableConfig.tsx | 29 +- .../SoftwareVersionsTableConfig.tsx | 27 +- .../SoftwareVersionDetailsPage.tsx | 26 +- .../components/AppStoreVpp/AppStoreVpp.tsx | 98 +++-- .../components/AppStoreVpp/_styles.scss | 26 +- .../EmptySoftwareTable/EmptySoftwareTable.tsx | 2 - .../SoftwareVulnerabilitiesTable.tsx | 28 +- .../icons/{MacApp.tsx => AppleApp.tsx} | 4 +- .../SoftwarePage/components/icons/index.ts | 6 +- .../IntegrationsPage/cards/Vpp/_styles.scss | 1 - .../admin/OrgSettingsPage/cards/Smtp/Smtp.tsx | 9 +- .../details/DeviceUserPage/DeviceUserPage.tsx | 1 - .../HostDetailsPage/HostDetailsPage.tsx | 49 +-- .../details/HostDetailsPage/_styles.scss | 10 +- .../Activity/PastActivityFeed/_styles.scss | 4 - .../UpcomingActivityFeed/_styles.scss | 4 - .../details/cards/Software/HostSoftware.tsx | 41 +- .../HostSoftwareTable/HostSoftwareTable.tsx | 75 ++-- .../SelfService/SelfService.tests.tsx | 2 + .../hosts/details/cards/Software/_styles.scss | 2 +- .../policies/ManagePoliciesPage/_styles.scss | 3 - .../ManageQueryAutomationsModal.tsx | 7 +- frontend/services/entities/mdm_apple.ts | 13 +- frontend/services/entities/software.ts | 2 + frontend/utilities/constants.tsx | 3 + 44 files changed, 803 insertions(+), 287 deletions(-) create mode 100644 changes/20467-vpp-ipadios-ui create mode 100644 frontend/components/graphics/DataError.tsx rename frontend/pages/SoftwarePage/components/icons/{MacApp.tsx => AppleApp.tsx} (93%) diff --git a/changes/20467-vpp-ipadios-ui b/changes/20467-vpp-ipadios-ui new file mode 100644 index 0000000000..2cc84e31cd --- /dev/null +++ b/changes/20467-vpp-ipadios-ui @@ -0,0 +1 @@ +* Add UI features for managing Apple VPP apps for iPadOS and iOS hosts \ No newline at end of file diff --git a/frontend/__mocks__/appleMdm.ts b/frontend/__mocks__/appleMdm.ts index 888588818c..c58a96f891 100644 --- a/frontend/__mocks__/appleMdm.ts +++ b/frontend/__mocks__/appleMdm.ts @@ -28,9 +28,10 @@ export const createMockVppInfo = ( const DEFAULT_MDM_APPLE_VPP_APP_MOCK: IVppApp = { name: "Test App", + bundle_identifier: "com.test.app", icon_url: "https://via.placeholder.com/512", latest_version: "1.0", - app_store_id: 1, + app_store_id: "1", added: false, platform: "darwin", }; diff --git a/frontend/components/DataError/DataError.tsx b/frontend/components/DataError/DataError.tsx index a148ac994f..49fc2a99e2 100644 --- a/frontend/components/DataError/DataError.tsx +++ b/frontend/components/DataError/DataError.tsx @@ -3,6 +3,7 @@ import classnames from "classnames"; import CustomLink from "components/CustomLink"; import Icon from "components/Icon"; +import Graphic from "components/Graphic"; const baseClass = "data-error"; @@ -14,6 +15,7 @@ interface IDataErrorProps { children?: React.ReactNode; card?: boolean; className?: string; + useNew?: boolean; } const DEFAULT_DESCRIPTION = "Refresh the page or log in again."; @@ -24,8 +26,36 @@ const DataError = ({ children, card, className, + useNew = false, }: IDataErrorProps): JSX.Element => { const classes = classnames(baseClass, className); + if (useNew) { + return ( +
+
+ +
+ Something's gone wrong. +
+ {children || ( + <> +
Refresh to try again.
+ {!excludeIssueLink && ( +
+ If this keeps happening please  + +
+ )} + + )} +
+
+ ); + } return (
diff --git a/frontend/components/DataError/_styles.scss b/frontend/components/DataError/_styles.scss index a54f5b1a85..9ea6ed3a9d 100644 --- a/frontend/components/DataError/_styles.scss +++ b/frontend/components/DataError/_styles.scss @@ -32,4 +32,21 @@ margin-top: 10px; } } + + // // // // // // // // // // // // + // new version + &__inner-new { + display: flex; + flex-direction: column; + gap: 8px; + text-align: center; + .graphic { + margin-bottom: 8px; + } + color: $core-fleet-black; + font-size: $x-small; + } + &__header { + font-weight: $bold; + } } diff --git a/frontend/components/TableContainer/DataTable/LinkCell/LinkCell.tsx b/frontend/components/TableContainer/DataTable/LinkCell/LinkCell.tsx index cd3c3ad1e8..c81794211b 100644 --- a/frontend/components/TableContainer/DataTable/LinkCell/LinkCell.tsx +++ b/frontend/components/TableContainer/DataTable/LinkCell/LinkCell.tsx @@ -4,9 +4,10 @@ import React from "react"; import { Link } from "react-router"; import classnames from "classnames"; import TooltipWrapper from "components/TooltipWrapper"; +import TextCell from "../TextCell"; interface ILinkCellProps { - value: string | JSX.Element; + value?: string | JSX.Element; path: string; className?: string; customOnClick?: (e: React.MouseEvent) => void; @@ -25,6 +26,10 @@ const LinkCell = ({ title, tooltipContent, }: ILinkCellProps): JSX.Element => { + // text cell with no value renders desired empty cell + if (!value) { + return ; + } const cellClasses = classnames(baseClass, className); const onClick = (e: React.MouseEvent): void => { diff --git a/frontend/components/forms/fields/Dropdown/_styles.scss b/frontend/components/forms/fields/Dropdown/_styles.scss index b7466ae888..c36f457e0d 100644 --- a/frontend/components/forms/fields/Dropdown/_styles.scss +++ b/frontend/components/forms/fields/Dropdown/_styles.scss @@ -51,14 +51,14 @@ &__option { display: flex; flex-direction: column; + gap: $pad-small; width: 100%; } &__help-text { - margin-top: $pad-xsmall; font-size: $xx-small; white-space: normal; - color: $core-fleet-blue; + color: $ui-fleet-black-50; font-style: italic; } } diff --git a/frontend/components/graphics/DataError.tsx b/frontend/components/graphics/DataError.tsx new file mode 100644 index 0000000000..3f504b423d --- /dev/null +++ b/frontend/components/graphics/DataError.tsx @@ -0,0 +1,404 @@ +import React from "react"; + +const DataError = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default DataError; diff --git a/frontend/components/graphics/index.ts b/frontend/components/graphics/index.ts index cfcafe4562..dfb7209263 100644 --- a/frontend/components/graphics/index.ts +++ b/frontend/components/graphics/index.ts @@ -18,6 +18,7 @@ import EmptyTeams from "./EmptyTeams"; import EmptyPacks from "./EmptyPacks"; import EmptySchedule from "./EmptySchedule"; import CollectingResults from "./CollectingResults"; +import DataError from "./DataError"; export const GRAPHIC_MAP = { // Empty state graphics @@ -43,6 +44,7 @@ export const GRAPHIC_MAP = { "file-vpp": FileVpp, // Other graphics "collecting-results": CollectingResults, + "data-error": DataError, }; export type GraphicNames = keyof typeof GRAPHIC_MAP; diff --git a/frontend/interfaces/activity.ts b/frontend/interfaces/activity.ts index ae1d2a6e58..8854af1935 100644 --- a/frontend/interfaces/activity.ts +++ b/frontend/interfaces/activity.ts @@ -1,3 +1,4 @@ +import { Platform } from "./platform"; import { IPolicy } from "./policy"; import { IQuery } from "./query"; import { ISchedulableQueryStats } from "./schedulable_query"; @@ -162,6 +163,7 @@ export interface IActivityDetails { stats?: ISchedulableQueryStats; software_title?: string; software_package?: string; + platform?: Platform; // software platform status?: string; install_uuid?: string; self_service?: boolean; diff --git a/frontend/interfaces/platform.ts b/frontend/interfaces/platform.ts index e46df4c291..0d1c153b6a 100644 --- a/frontend/interfaces/platform.ts +++ b/frontend/interfaces/platform.ts @@ -1,16 +1,25 @@ -export type AppleDisplayPlatform = "macOS" | "iOS" | "iPadOS"; -export type DisplayPlatform = - | AppleDisplayPlatform - | "Windows" - | "Linux" - | "ChromeOS"; +export const APPLE_PLATFORM_DISPLAY_NAMES = { + darwin: "macOS", + ios: "iOS", + ipados: "iPadOS", +} as const; + +export type ApplePlatform = keyof typeof APPLE_PLATFORM_DISPLAY_NAMES; +export type AppleDisplayPlatform = typeof APPLE_PLATFORM_DISPLAY_NAMES[keyof typeof APPLE_PLATFORM_DISPLAY_NAMES]; + +export const PLATFORM_DISPLAY_NAMES = { + windows: "Windows", + linux: "Linux", + chrome: "ChromeOS", + ...APPLE_PLATFORM_DISPLAY_NAMES, +} as const; + +export type Platform = keyof typeof PLATFORM_DISPLAY_NAMES; +export type DisplayPlatform = typeof PLATFORM_DISPLAY_NAMES[keyof typeof PLATFORM_DISPLAY_NAMES]; export type QueryableDisplayPlatform = Exclude< DisplayPlatform, "iOS" | "iPadOS" >; - -export type ApplePlatform = "darwin" | "ios" | "ipados"; -export type Platform = ApplePlatform | "windows" | "linux" | "chrome"; export type QueryablePlatform = Exclude; export const SUPPORTED_PLATFORMS: QueryablePlatform[] = [ @@ -20,6 +29,9 @@ export const SUPPORTED_PLATFORMS: QueryablePlatform[] = [ "chrome", ]; +// TODO - add "iOS" and "iPadOS" once we support them +export const VULN_SUPPORTED_PLATFORMS: Platform[] = ["darwin", "windows"]; + export type SelectedPlatform = QueryablePlatform | "all"; export type SelectedPlatformString = diff --git a/frontend/interfaces/software.ts b/frontend/interfaces/software.ts index 0247c0f1df..62ad068bd1 100644 --- a/frontend/interfaces/software.ts +++ b/frontend/interfaces/software.ts @@ -34,7 +34,7 @@ export interface ISoftware { name: string; // e.g., "Figma.app" version: string; // e.g., "2.1.11" bundle_identifier?: string | null; // e.g., "com.figma.Desktop" - source: string; // "apps" | "ipados" | "ios" | "programs" | ? + source: string; // "apps" | "ipados_apps" | "ios_apps" | "programs" | ? generated_cpe: string; vulnerabilities: ISoftwareVulnerability[] | null; hosts_count?: number; @@ -148,8 +148,8 @@ export const SOURCE_TYPE_CONVERSION: Record = { atom_packages: "Package (Atom)", // Atom packages were removed from software inventory. Mapping is maintained for backwards compatibility. (2023-12-04) python_packages: "Package (Python)", apps: "Application (macOS)", - ios: "Application (iOS)", - ipados: "Application (iPadOS)", + ios_apps: "Application (iOS)", + ipados_apps: "Application (iPadOS)", chrome_extensions: "Browser plugin", // chrome_extensions can include any chrome-based browser (e.g., edge), so we rely instead on the `browser` field computed by Fleet server and fallback to this value if it is not present. firefox_addons: "Browser plugin (Firefox)", safari_extensions: "Browser plugin (Safari)", diff --git a/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx b/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx index 54bce326b1..c0f81ef72d 100644 --- a/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx +++ b/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx @@ -4,7 +4,10 @@ import { formatDistanceToNowStrict } from "date-fns"; import { ActivityType, IActivity, IActivityDetails } from "interfaces/activity"; import { getInstallStatusPredicate } from "interfaces/software"; -import { AppleDisplayPlatform } from "interfaces/platform"; +import { + AppleDisplayPlatform, + PLATFORM_DISPLAY_NAMES, +} from "interfaces/platform"; import { addGravatarUrlToResource, @@ -882,10 +885,13 @@ const TAGGED_TEMPLATES = { ); }, addedAppStoreApp: (activity: IActivity) => { + const { software_title: swTitle, platform: swPlatform } = + activity.details || {}; return ( <> {" "} - added {activity.details?.software_title} to{" "} + added {swTitle}{" "} + {swPlatform ? `(${PLATFORM_DISPLAY_NAMES[swPlatform]}) ` : ""}to{" "} {activity.details?.team_name ? ( <> {" "} @@ -898,10 +904,13 @@ const TAGGED_TEMPLATES = { ); }, deletedAppStoreApp: (activity: IActivity) => { + const { software_title: swTitle, platform: swPlatform } = + activity.details || {}; return ( <> {" "} - deleted {activity.details?.software_title} from{" "} + deleted {swTitle}{" "} + {swPlatform ? `(${PLATFORM_DISPLAY_NAMES[swPlatform]}) ` : ""}from{" "} {activity.details?.team_name ? ( <> {" "} diff --git a/frontend/pages/DashboardPage/cards/OperatingSystems/OperatingSystemsTableConfig.tsx b/frontend/pages/DashboardPage/cards/OperatingSystems/OperatingSystemsTableConfig.tsx index f5a5292217..80df2741ac 100644 --- a/frontend/pages/DashboardPage/cards/OperatingSystems/OperatingSystemsTableConfig.tsx +++ b/frontend/pages/DashboardPage/cards/OperatingSystems/OperatingSystemsTableConfig.tsx @@ -108,7 +108,7 @@ const generateDefaultTableHeaders = ( Cell: (cellProps: IVulnCellProps) => { const platform = cellProps.row.original.platform; if (platform !== "darwin" && platform !== "windows") { - return ; + return ; } return ; }, diff --git a/frontend/pages/NoAccessPage/NoAccessPage.tsx b/frontend/pages/NoAccessPage/NoAccessPage.tsx index 017ea3f8b4..1c7a83dd99 100644 --- a/frontend/pages/NoAccessPage/NoAccessPage.tsx +++ b/frontend/pages/NoAccessPage/NoAccessPage.tsx @@ -4,6 +4,8 @@ import React, { useEffect } from "react"; import { InjectedRouter } from "react-router"; import PATHS from "router/paths"; +import { CONTACT_FLEET_LINK } from "utilities/constants"; + import Button from "components/buttons/Button/Button"; // @ts-ignore import StackedWhiteBoxes from "components/StackedWhiteBoxes"; @@ -48,7 +50,7 @@ const NoAccessPage = ({ router, orgContactUrl }: INoAccessPageProps) => {

To get access,{" "} . diff --git a/frontend/pages/SoftwarePage/SoftwareOSDetailsPage/SoftwareOSDetailsPage.tsx b/frontend/pages/SoftwarePage/SoftwareOSDetailsPage/SoftwareOSDetailsPage.tsx index 3a1b01adfa..125dab201a 100644 --- a/frontend/pages/SoftwarePage/SoftwareOSDetailsPage/SoftwareOSDetailsPage.tsx +++ b/frontend/pages/SoftwarePage/SoftwareOSDetailsPage/SoftwareOSDetailsPage.tsx @@ -11,64 +11,34 @@ import useTeamIdParam from "hooks/useTeamIdParam"; import { AppContext } from "context/app"; import { ignoreAxiosError } from "interfaces/errors"; +import { + isLinuxLike, + Platform, + VULN_SUPPORTED_PLATFORMS, +} from "interfaces/platform"; import osVersionsAPI, { IOSVersionResponse, IGetOsVersionQueryKey, } from "services/entities/operating_systems"; import { IOperatingSystemVersion } from "interfaces/operating_system"; -import { isLinuxLike } from "interfaces/platform"; -import { DEFAULT_USE_QUERY_OPTIONS, SUPPORT_LINK } from "utilities/constants"; +import { + DEFAULT_USE_QUERY_OPTIONS, + PLATFORM_DISPLAY_NAMES, +} from "utilities/constants"; import Spinner from "components/Spinner"; import MainContent from "components/MainContent"; -import EmptyTable from "components/EmptyTable"; -import CustomLink from "components/CustomLink"; import TeamsHeader from "components/TeamsHeader"; import Card from "components/Card"; import SoftwareDetailsSummary from "../components/SoftwareDetailsSummary"; import SoftwareVulnerabilitiesTable from "../components/SoftwareVulnerabilitiesTable"; import DetailsNoHosts from "../components/DetailsNoHosts"; +import { VulnsNotSupported } from "../components/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable"; const baseClass = "software-os-details-page"; -interface INotSupportedVulnProps { - platform: string; -} - -const platformDisplayName = (platform: string) => { - if (isLinuxLike(platform)) { - return "Linux hosts"; - } - - switch (platform) { - case "chrome": - return "Chromebooks"; - case "ios": - return "iPhones"; - case "ipados": - return "iPads"; - default: - return "this operating system"; - } -}; - -const NotSupportedVuln = ({ platform }: INotSupportedVulnProps) => { - return ( - - Interested in vulnerability management for{" "} - {platformDisplayName(platform)}?{" "} - - - } - /> - ); -}; - interface ISoftwareOSDetailsRouteParams { id: string; team_id?: string; @@ -145,10 +115,13 @@ const SoftwareOSDetailsPage = ({ } if ( - osVersionDetails.platform !== "darwin" && - osVersionDetails.platform !== "windows" + // TODO - detangle platform typing here + !VULN_SUPPORTED_PLATFORMS.includes(osVersionDetails.platform as Platform) ) { - return ; + const supportInterestText = isLinuxLike(osVersionDetails.platform) + ? "Linux" + : PLATFORM_DISPLAY_NAMES[osVersionDetails.platform]; + return ; } return ( diff --git a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareTitleDetailsPage.tsx b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareTitleDetailsPage.tsx index e721379b34..b545171ea3 100644 --- a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareTitleDetailsPage.tsx +++ b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareTitleDetailsPage.tsx @@ -202,6 +202,9 @@ const SoftwareTitleDetailsPage = ({ data={softwareTitle.versions ?? []} isLoading={isSoftwareTitleLoading} teamIdForApi={teamIdForApi} + isIPadOSOrIOSApp={["ios_apps", "ipados_apps"].includes( + softwareTitle.source + )} /> diff --git a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareTitleDetailsTable/SoftwareTitleDetailsTable.tsx b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareTitleDetailsTable/SoftwareTitleDetailsTable.tsx index 99587e2b9c..a348b501b0 100644 --- a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareTitleDetailsTable/SoftwareTitleDetailsTable.tsx +++ b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareTitleDetailsTable/SoftwareTitleDetailsTable.tsx @@ -44,6 +44,7 @@ interface ISoftwareTitleDetailsTableProps { data: ISoftwareTitleVersion[]; isLoading: boolean; teamIdForApi?: number; + isIPadOSOrIOSApp: boolean; } interface IRowProps extends Row { @@ -57,6 +58,7 @@ const SoftwareTitleDetailsTable = ({ data, isLoading, teamIdForApi, + isIPadOSOrIOSApp, }: ISoftwareTitleDetailsTableProps) => { const handleRowSelect = (row: IRowProps) => { const hostsBySoftwareParams = { @@ -74,8 +76,12 @@ const SoftwareTitleDetailsTable = ({ const softwareTableHeaders = useMemo( () => - generateSoftwareTitleDetailsTableConfig({ router, teamId: teamIdForApi }), - [router, teamIdForApi] + generateSoftwareTitleDetailsTableConfig({ + router, + teamId: teamIdForApi, + isIPadOSOrIOSApp, + }), + [router, teamIdForApi, isIPadOSOrIOSApp] ); const renderVersionsCount = () => ( diff --git a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareTitleDetailsTable/SoftwareTitleDetailsTableConfig.tsx b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareTitleDetailsTable/SoftwareTitleDetailsTableConfig.tsx index e5990b58a6..705249e14a 100644 --- a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareTitleDetailsTable/SoftwareTitleDetailsTableConfig.tsx +++ b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareTitleDetailsTable/SoftwareTitleDetailsTableConfig.tsx @@ -17,6 +17,7 @@ import VulnerabilitiesCell from "../../components/VulnerabilitiesCell"; interface ISoftwareTitleDetailsTableConfigProps { router: InjectedRouter; teamId?: number; + isIPadOSOrIOSApp: boolean; } interface ICellProps { cell: { @@ -48,6 +49,7 @@ interface IVulnCellProps extends ICellProps { const generateSoftwareTitleDetailsTableConfig = ({ router, teamId, + isIPadOSOrIOSApp, }: ISoftwareTitleDetailsTableConfigProps) => { const tableHeaders = [ { @@ -90,10 +92,13 @@ const generateSoftwareTitleDetailsTableConfig = ({ // With the versions data, we can sum up the vulnerabilities to get the // total number of vulnerabilities for the software title accessor: "vulnerabilities", - Cell: (cellProps: IVulnCellProps): JSX.Element => ( - + Cell: (cellProps: IVulnCellProps): JSX.Element => { + if (isIPadOSOrIOSApp) { + return ; + } + return ; // TODO: tooltip - ), + }, }, { title: "Hosts", diff --git a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/helpers.ts b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/helpers.ts index 9e7ece8cf7..b662f30556 100644 --- a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/helpers.ts +++ b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/helpers.ts @@ -3,6 +3,7 @@ import { ISoftwareTitleDetails, isSoftwarePackage, } from "interfaces/software"; +import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants"; /** * Generates the data needed to render the package card. @@ -18,9 +19,10 @@ export const getPackageCardInfo = (softwareTitle: ISoftwareTitleDetails) => { return { softwarePackage: isSoftwarePackage(packageData) ? packageData : undefined, name: softwareTitle.name, - version: isSoftwarePackage(packageData) - ? packageData.version - : packageData.latest_version, + version: + (isSoftwarePackage(packageData) + ? packageData.version + : packageData.latest_version) || DEFAULT_EMPTY_CELL_VALUE, uploadedAt: isSoftwarePackage(packageData) ? packageData.uploaded_at : "", status: packageData.status, isSelfService: isSoftwarePackage(packageData) diff --git a/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTable/SoftwareTitlesTableConfig.tsx b/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTable/SoftwareTitlesTableConfig.tsx index 0e303c918c..213cc1ec46 100644 --- a/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTable/SoftwareTitlesTableConfig.tsx +++ b/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTable/SoftwareTitlesTableConfig.tsx @@ -2,13 +2,7 @@ import React from "react"; import { CellProps, Column } from "react-table"; import { InjectedRouter } from "react-router"; -import { - IAppStoreApp, - ISoftware, - ISoftwarePackage, - ISoftwareTitle, - formatSoftwareType, -} from "interfaces/software"; +import { ISoftwareTitle, formatSoftwareType } from "interfaces/software"; import PATHS from "router/paths"; import { buildQueryStringFromParams } from "utilities/url"; @@ -123,14 +117,6 @@ const generateTableHeaders = ( }, sortType: "caseInsensitive", }, - { - Header: "Type", - disableSortBy: true, - accessor: "source", - Cell: (cellProps: ITableStringCellProps) => ( - - ), - }, { Header: "Version", disableSortBy: true, @@ -139,6 +125,14 @@ const generateTableHeaders = ( ), }, + { + Header: "Type", + disableSortBy: true, + accessor: "source", + Cell: (cellProps: ITableStringCellProps) => ( + + ), + }, // the "vulnerabilities" accessor is used but the data is actually coming // from the version attribute. We do this as we already have a "versions" // attribute used for the "Version" column and we cannot reuse. This is a @@ -149,6 +143,11 @@ const generateTableHeaders = ( Header: "Vulnerabilities", disableSortBy: true, Cell: (cellProps: IVulnerabilitiesCellProps) => { + if ( + ["ios_apps", "ipados_apps"].includes(cellProps.row.original.source) + ) { + return ; + } const vulnerabilities = getVulnerabilities( cellProps.row.original.versions ?? [] ); diff --git a/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTable/SoftwareVersionsTableConfig.tsx b/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTable/SoftwareVersionsTableConfig.tsx index d22aab9e86..0be57bf962 100644 --- a/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTable/SoftwareVersionsTableConfig.tsx +++ b/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTable/SoftwareVersionsTableConfig.tsx @@ -63,14 +63,6 @@ const generateTableHeaders = ( }, sortType: "caseInsensitive", }, - { - Header: "Type", - disableSortBy: true, - accessor: "source", - Cell: (cellProps: ITableStringCellProps) => ( - - ), - }, { Header: "Version", disableSortBy: true, @@ -79,13 +71,26 @@ const generateTableHeaders = ( ), }, + { + Header: "Type", + disableSortBy: true, + accessor: "source", + Cell: (cellProps: ITableStringCellProps) => ( + + ), + }, { Header: "Vulnerabilities", disableSortBy: true, accessor: "vulnerabilities", - Cell: (cellProps: IVulnerabilitiesCellProps) => ( - - ), + Cell: (cellProps: IVulnerabilitiesCellProps) => { + if ( + ["ipados_apps", "ios_apps"].includes(cellProps.row.original.source) + ) { + return ; + } + return ; + }, }, { Header: (cellProps: ITableHeaderProps) => ( diff --git a/frontend/pages/SoftwarePage/SoftwareVersionDetailsPage/SoftwareVersionDetailsPage.tsx b/frontend/pages/SoftwarePage/SoftwareVersionDetailsPage/SoftwareVersionDetailsPage.tsx index 328713f78c..8ee1118aef 100644 --- a/frontend/pages/SoftwarePage/SoftwareVersionDetailsPage/SoftwareVersionDetailsPage.tsx +++ b/frontend/pages/SoftwarePage/SoftwareVersionDetailsPage/SoftwareVersionDetailsPage.tsx @@ -31,6 +31,7 @@ import Card from "components/Card"; import SoftwareDetailsSummary from "../components/SoftwareDetailsSummary"; import SoftwareVulnerabilitiesTable from "../components/SoftwareVulnerabilitiesTable"; import DetailsNoHosts from "../components/DetailsNoHosts"; +import { VulnsNotSupported } from "../components/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable"; const baseClass = "software-version-details-page"; @@ -112,6 +113,23 @@ const SoftwareVersionDetailsPage = ({ [handleTeamChange] ); + const renderVulnTable = (swVersion: ISoftwareVersion) => { + if (["ios_apps", "ipados_apps"].includes(swVersion.source)) { + const supportInterestText = + swVersion.source === "ios_apps" ? "iOS" : "iPadOS"; + return ; + } + return ( + + ); + }; + const renderContent = () => { if (isSoftwareVersionLoading) { return ; @@ -157,13 +175,7 @@ const SoftwareVersionDetailsPage = ({ className={`${baseClass}__vulnerabilities-section`} >

Vulnerabilities

- + {renderVulnTable(softwareVersion)} )} diff --git a/frontend/pages/SoftwarePage/components/AppStoreVpp/AppStoreVpp.tsx b/frontend/pages/SoftwarePage/components/AppStoreVpp/AppStoreVpp.tsx index 1a1fc02ad0..65d19bcefd 100644 --- a/frontend/pages/SoftwarePage/components/AppStoreVpp/AppStoreVpp.tsx +++ b/frontend/pages/SoftwarePage/components/AppStoreVpp/AppStoreVpp.tsx @@ -10,6 +10,8 @@ import mdmAppleAPI, { } from "services/entities/mdm_apple"; import { DEFAULT_USE_QUERY_OPTIONS } from "utilities/constants"; +import { PLATFORM_DISPLAY_NAMES } from "interfaces/platform"; + import Card from "components/Card"; import CustomLink from "components/CustomLink"; import Spinner from "components/Spinner"; @@ -29,7 +31,7 @@ const EnableVppCard = () => {

- Volume Purchasing Program (VPP) isn’t enabled. + Volume Purchasing Program (VPP) isn't enabled

To add App Store apps, first enable VPP. @@ -66,6 +68,11 @@ const VppAppListItem = ({ app, selected, onSelect }: IVppAppListItemProps) => { name="vppApp" onChange={() => onSelect(app)} /> + {app.platform && ( +

+ {PLATFORM_DISPLAY_NAMES[app.platform]} +
+ )} ); }; @@ -76,40 +83,20 @@ interface IVppAppListProps { onSelect: (app: IVppApp) => void; } -const VppAppList = ({ apps, selectedApp, onSelect }: IVppAppListProps) => { - const renderContent = () => { - if (apps.length === 0) { - return ( -
-

- You don't have any App Store apps -

-

- You must purchase apps in ABM. App Store apps that are already added - to this team are not listed. -

-
- ); - } - - return ( -
    - {apps.map((app) => ( - - ))} -
- ); - }; - - return ( -
{renderContent()}
- ); -}; +const VppAppList = ({ apps, selectedApp, onSelect }: IVppAppListProps) => ( +
+
    + {apps.map((app) => ( + + ))} +
+
+); interface IAppStoreVppProps { teamId: number; @@ -193,20 +180,41 @@ const AppStoreVpp = ({ teamId, router, onExit }: IAppStoreVppProps) => { return ; } - return vppApps ? ( - - ) : null; + if (vppApps) { + if (vppApps.length === 0) { + return ( +
+

+ You don't have any App Store apps +

+

+ Add apps in{" "} + {" "} + Apps that are already added to this team are not listed. +

+
+ ); + } + return ( + <> + +
+ These apps were added in Apple Business Manager (ABM). To add more + apps, head to{" "} + +
+ + ); + } + return null; }; return (
-

- Apple App Store apps purchased via Apple Business Manager. -

{renderContent()}