mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 17:08:53 +00:00
UI – Follow-ups for iPadOS/iPadOS VPP (#20916)
## Follow ups to #20467, part 4 of #20917 - Use combination of apps' fields to uniquely identify them – [bug and fix demo/explanation](https://www.loom.com/share/2e5f088677604f04927bce8d9dacf8fe?sid=d946bea5-11a9-419a-b946-962829a53adc) - Add new field to vpp `POST` requests for correct add VPP software functionality  - Implement desired states for software action dropdown  - [x] Manual QA for all new/changed functionality --------- Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
This commit is contained in:
parent
08d08d5602
commit
2ccc0f79e7
7 changed files with 71 additions and 43 deletions
|
|
@ -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 (
|
||||
<li className={`${baseClass}__list-item`}>
|
||||
<Radio
|
||||
|
|
@ -77,9 +83,9 @@ const VppAppListItem = ({ app, selected, onSelect }: IVppAppListItemProps) => {
|
|||
<span>{app.name}</span>
|
||||
</div>
|
||||
}
|
||||
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) => (
|
||||
<div className={`${baseClass}__list-container`}>
|
||||
<ul className={`${baseClass}__list`}>
|
||||
{apps.map((app) => (
|
||||
<VppAppListItem
|
||||
key={app.app_store_id}
|
||||
app={app}
|
||||
selected={selectedApp?.app_store_id === app.app_store_id}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
const VppAppList = ({ apps, selectedApp, onSelect }: IVppAppListProps) => {
|
||||
const uniqueSelectedAppId = selectedApp ? getUniqueAppId(selectedApp) : null;
|
||||
return (
|
||||
<div className={`${baseClass}__list-container`}>
|
||||
<ul className={`${baseClass}__list`}>
|
||||
{apps.map((app) => {
|
||||
const uniqueAppId = getUniqueAppId(app);
|
||||
return (
|
||||
<VppAppListItem
|
||||
key={uniqueAppId}
|
||||
app={app}
|
||||
selected={uniqueSelectedAppId === uniqueAppId}
|
||||
uniqueAppId={uniqueAppId}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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",
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -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}`;
|
||||
|
|
|
|||
|
|
@ -415,7 +415,7 @@ const DeviceUserPage = ({
|
|||
<SoftwareCard
|
||||
id={deviceAuthToken}
|
||||
softwareUpdatedAt={host.software_updated_at}
|
||||
isFleetdHost={!!host.orbit_version}
|
||||
hostCanInstallSoftware={!!host.orbit_version}
|
||||
router={router}
|
||||
pathname={location.pathname}
|
||||
queryParams={parseHostSoftwareQueryParams(location.query)}
|
||||
|
|
|
|||
|
|
@ -922,7 +922,9 @@ const HostDetailsPage = ({
|
|||
<SoftwareCard
|
||||
id={host.id}
|
||||
softwareUpdatedAt={host.software_updated_at}
|
||||
isFleetdHost={!!host.orbit_version}
|
||||
hostCanInstallSoftware={
|
||||
!!host.orbit_version || isIosOrIpadosHost
|
||||
}
|
||||
isSoftwareEnabled={featuresConfig?.enable_software_inventory}
|
||||
router={router}
|
||||
queryParams={parseHostSoftwareQueryParams(location.query)}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ interface IHostSoftwareProps {
|
|||
/** This is the host id or the device token */
|
||||
id: number | string;
|
||||
softwareUpdatedAt?: string;
|
||||
isFleetdHost: boolean;
|
||||
hostCanInstallSoftware: boolean;
|
||||
router: InjectedRouter;
|
||||
queryParams: ReturnType<typeof parseHostSoftwareQueryParams>;
|
||||
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
|
||||
|
|
|
|||
|
|
@ -50,17 +50,17 @@ type IVulnerabilitiesCellProps = IInstalledVersionsCellProps;
|
|||
// type IActionsCellProps = CellProps<IHostSoftware, IHostSoftware["id"]>;
|
||||
|
||||
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 = ({
|
|||
<DropdownCell
|
||||
placeholder="Actions"
|
||||
options={generateActions({
|
||||
canInstall,
|
||||
isFleetdHost,
|
||||
userHasSWInstallPermission,
|
||||
hostCanInstallSoftware,
|
||||
installingSoftwareId,
|
||||
softwareId,
|
||||
status,
|
||||
|
|
|
|||
|
|
@ -69,11 +69,12 @@ export default {
|
|||
return sendRequest("GET", path);
|
||||
},
|
||||
|
||||
addVppApp: (teamId: number, appStoreId: string) => {
|
||||
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,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue