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,
});
},
};