diff --git a/frontend/pages/SoftwarePage/components/AppStoreVpp/AppStoreVpp.tsx b/frontend/pages/SoftwarePage/components/AppStoreVpp/AppStoreVpp.tsx index b6191d228b..874d2d8849 100644 --- a/frontend/pages/SoftwarePage/components/AppStoreVpp/AppStoreVpp.tsx +++ b/frontend/pages/SoftwarePage/components/AppStoreVpp/AppStoreVpp.tsx @@ -22,7 +22,7 @@ import { NotificationContext } from "context/notification"; import { getErrorReason } from "interfaces/errors"; import { buildQueryStringFromParams } from "utilities/url"; import SoftwareIcon from "../icons/SoftwareIcon"; -import { getErrorMessage } from "./helpers"; +import { getErrorMessage, getUniqueAppId } from "./helpers"; const baseClass = "app-store-vpp"; @@ -64,10 +64,16 @@ const NoVppAppsCard = () => ( interface IVppAppListItemProps { app: IVppApp; selected: boolean; + uniqueAppId: string; onSelect: (software: IVppApp) => void; } -const VppAppListItem = ({ app, selected, onSelect }: IVppAppListItemProps) => { +const VppAppListItem = ({ + app, + selected, + uniqueAppId, + onSelect, +}: IVppAppListItemProps) => { return (
  • { {app.name} } - id={`vppApp-${app.app_store_id}`} + id={`vppApp-${uniqueAppId}`} checked={selected} - value={app.app_store_id.toString()} + value={uniqueAppId} name="vppApp" onChange={() => onSelect(app)} /> @@ -98,20 +104,27 @@ interface IVppAppListProps { onSelect: (app: IVppApp) => void; } -const VppAppList = ({ apps, selectedApp, onSelect }: IVppAppListProps) => ( -
    -
      - {apps.map((app) => ( - - ))} -
    -
    -); +const VppAppList = ({ apps, selectedApp, onSelect }: IVppAppListProps) => { + const uniqueSelectedAppId = selectedApp ? getUniqueAppId(selectedApp) : null; + return ( +
    +
      + {apps.map((app) => { + const uniqueAppId = getUniqueAppId(app); + return ( + + ); + })} +
    +
    + ); +}; interface IAppStoreVppProps { teamId: number; @@ -160,7 +173,11 @@ const AppStoreVpp = ({ teamId, router, onExit }: IAppStoreVppProps) => { } try { - await mdmAppleAPI.addVppApp(teamId, selectedApp.app_store_id); + await mdmAppleAPI.addVppApp( + teamId, + selectedApp.app_store_id, + selectedApp.platform + ); renderFlash( "success", <> diff --git a/frontend/pages/SoftwarePage/components/AppStoreVpp/helpers.tsx b/frontend/pages/SoftwarePage/components/AppStoreVpp/helpers.tsx index f359a6441d..a0130de802 100644 --- a/frontend/pages/SoftwarePage/components/AppStoreVpp/helpers.tsx +++ b/frontend/pages/SoftwarePage/components/AppStoreVpp/helpers.tsx @@ -1,5 +1,6 @@ import React from "react"; import { getErrorReason } from "interfaces/errors"; +import { IVppApp } from "services/entities/mdm_apple"; const ADD_SOFTWARE_ERROR_PREFIX = "Couldn’t add software."; const DEFAULT_ERROR_MESSAGE = `${ADD_SOFTWARE_ERROR_PREFIX} Please try again.`; @@ -40,3 +41,6 @@ export const getErrorMessage = (e: unknown) => { } return DEFAULT_ERROR_MESSAGE; }; + +export const getUniqueAppId = (app: IVppApp) => + `${app.app_store_id}_${app.platform}`; diff --git a/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx b/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx index 189c8ae488..ba78e6ab25 100644 --- a/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx +++ b/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx @@ -415,7 +415,7 @@ const DeviceUserPage = ({ ; pathname: string; @@ -83,7 +83,7 @@ export const parseHostSoftwareQueryParams = (queryParams: { const HostSoftware = ({ id, softwareUpdatedAt, - isFleetdHost, + hostCanInstallSoftware, router, queryParams, pathname, @@ -169,7 +169,7 @@ const HostSoftware = ({ [isMyDevicePage, refetchDeviceSoftware, refetchHostSoftware] ); - const canInstallSoftware = Boolean( + const userHasSWInstallPermission = Boolean( isGlobalAdmin || isGlobalMaintainer || isTeamAdmin || isTeamMaintainer ); @@ -213,19 +213,19 @@ const HostSoftware = ({ : generateHostSoftwareTableConfig({ router, installingSoftwareId, - canInstall: canInstallSoftware, + userHasSWInstallPermission, onSelectAction, teamId: hostTeamId, - isFleetdHost, + hostCanInstallSoftware, }); }, [ isMyDevicePage, router, installingSoftwareId, - canInstallSoftware, + userHasSWInstallPermission, onSelectAction, hostTeamId, - isFleetdHost, + hostCanInstallSoftware, ]); const isLoading = isMyDevicePage diff --git a/frontend/pages/hosts/details/cards/Software/HostSoftwareTableConfig.tsx b/frontend/pages/hosts/details/cards/Software/HostSoftwareTableConfig.tsx index b01806032f..66777b1ed3 100644 --- a/frontend/pages/hosts/details/cards/Software/HostSoftwareTableConfig.tsx +++ b/frontend/pages/hosts/details/cards/Software/HostSoftwareTableConfig.tsx @@ -50,17 +50,17 @@ type IVulnerabilitiesCellProps = IInstalledVersionsCellProps; // type IActionsCellProps = CellProps; const generateActions = ({ - canInstall, + userHasSWInstallPermission, + hostCanInstallSoftware, installingSoftwareId, - isFleetdHost, softwareId, status, software_package, app_store_app, }: { - canInstall: boolean; + userHasSWInstallPermission: boolean; + hostCanInstallSoftware: boolean; installingSoftwareId: number | null; - isFleetdHost: boolean; softwareId: number; status: SoftwareInstallStatus | null; software_package: IHostSoftwarePackage | null; @@ -78,14 +78,18 @@ const generateActions = ({ } const hasSoftwareToInstall = !!software_package || !!app_store_app; - // remove install if there is no package to install - if (!hasSoftwareToInstall || !canInstall) { + // remove install if there is no package to install or if the software is already installed + if ( + !hasSoftwareToInstall || + !userHasSWInstallPermission || + status === "installed" + ) { actions.splice(indexInstallAction, 1); return actions; } - // disable install option if not a fleetd host - if (!isFleetdHost) { + // disable install option if not a fleetd, iPad, or iOS host + if (!hostCanInstallSoftware) { actions[indexInstallAction].disabled = true; actions[indexInstallAction].tooltipContent = "To install software on this host, deploy the fleetd agent with --enable-scripts and refetch host vitals."; @@ -102,9 +106,9 @@ const generateActions = ({ }; interface ISoftwareTableHeadersProps { - canInstall: boolean; + userHasSWInstallPermission: boolean; + hostCanInstallSoftware: boolean; installingSoftwareId: number | null; - isFleetdHost: boolean; router: InjectedRouter; teamId: number; onSelectAction: (software: IHostSoftware, action: string) => void; @@ -113,9 +117,9 @@ interface ISoftwareTableHeadersProps { // NOTE: cellProps come from react-table // more info here https://react-table.tanstack.com/docs/api/useTable#cell-properties export const generateSoftwareTableHeaders = ({ - canInstall, + userHasSWInstallPermission, + hostCanInstallSoftware, installingSoftwareId, - isFleetdHost, router, teamId, onSelectAction, @@ -202,8 +206,8 @@ export const generateSoftwareTableHeaders = ({ { + addVppApp: (teamId: number, appStoreId: string, platform: ApplePlatform) => { const { MDM_APPLE_VPP_APPS } = endpoints; return sendRequest("POST", MDM_APPLE_VPP_APPS, { app_store_id: appStoreId, team_id: teamId, + platform, }); }, };