mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 09:28:54 +00:00
228 lines
6 KiB
TypeScript
228 lines
6 KiB
TypeScript
import React, { useContext, useState } from "react";
|
||
import { useQuery } from "react-query";
|
||
import { InjectedRouter } from "react-router";
|
||
import { AxiosError } from "axios";
|
||
|
||
import PATHS from "router/paths";
|
||
import mdmAppleAPI, {
|
||
IGetVppInfoResponse,
|
||
IVppApp,
|
||
} from "services/entities/mdm_apple";
|
||
import { DEFAULT_USE_QUERY_OPTIONS } from "utilities/constants";
|
||
|
||
import Card from "components/Card";
|
||
import CustomLink from "components/CustomLink";
|
||
import Spinner from "components/Spinner";
|
||
import Button from "components/buttons/Button";
|
||
import DataError from "components/DataError";
|
||
import Radio from "components/forms/fields/Radio";
|
||
import { NotificationContext } from "context/notification";
|
||
import { getErrorReason } from "interfaces/errors";
|
||
import { buildQueryStringFromParams } from "utilities/url";
|
||
import SoftwareIcon from "../icons/SoftwareIcon";
|
||
import { getErrorMessage } from "./helpers";
|
||
|
||
const baseClass = "app-store-vpp";
|
||
|
||
const EnableVppCard = () => {
|
||
return (
|
||
<Card borderRadiusSize="medium">
|
||
<div className={`${baseClass}__enable-vpp`}>
|
||
<p className={`${baseClass}__enable-vpp-title`}>
|
||
<b>Volume Purchasing Program (VPP) isn’t enabled.</b>
|
||
</p>
|
||
<p className={`${baseClass}__enable-vpp-description`}>
|
||
To add App Store apps, first enable VPP.
|
||
</p>
|
||
<CustomLink
|
||
url={PATHS.ADMIN_INTEGRATIONS_VPP}
|
||
text="Enable VPP"
|
||
className={`${baseClass}__enable-vpp-link`}
|
||
/>
|
||
</div>
|
||
</Card>
|
||
);
|
||
};
|
||
|
||
interface IVppAppListItemProps {
|
||
app: IVppApp;
|
||
selected: boolean;
|
||
onSelect: (software: IVppApp) => void;
|
||
}
|
||
|
||
const VppAppListItem = ({ app, selected, onSelect }: IVppAppListItemProps) => {
|
||
return (
|
||
<li className={`${baseClass}__list-item`}>
|
||
<Radio
|
||
label={
|
||
<div className={`${baseClass}__app-info`}>
|
||
<SoftwareIcon url={app.icon_url} />
|
||
<span>{app.name}</span>
|
||
</div>
|
||
}
|
||
id={`vppApp-${app.app_store_id}`}
|
||
checked={selected}
|
||
value={app.app_store_id.toString()}
|
||
name="vppApp"
|
||
onChange={() => onSelect(app)}
|
||
/>
|
||
</li>
|
||
);
|
||
};
|
||
|
||
interface IVppAppListProps {
|
||
apps: IVppApp[];
|
||
selectedApp: IVppApp | null;
|
||
onSelect: (app: IVppApp) => void;
|
||
}
|
||
|
||
const VppAppList = ({ apps, selectedApp, onSelect }: IVppAppListProps) => {
|
||
const renderContent = () => {
|
||
if (apps.length === 0) {
|
||
return (
|
||
<div className={`${baseClass}__no-software`}>
|
||
<p className={`${baseClass}__no-software-title`}>
|
||
You don't have any App Store apps
|
||
</p>
|
||
<p className={`${baseClass}__no-software-description`}>
|
||
You must purchase apps in ABM. App Store apps that are already added
|
||
to this team are not listed.
|
||
</p>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<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>
|
||
);
|
||
};
|
||
|
||
return (
|
||
<div className={`${baseClass}__list-container`}>{renderContent()}</div>
|
||
);
|
||
};
|
||
|
||
interface IAppStoreVppProps {
|
||
teamId: number;
|
||
router: InjectedRouter;
|
||
onExit: () => void;
|
||
}
|
||
|
||
const AppStoreVpp = ({ teamId, router, onExit }: IAppStoreVppProps) => {
|
||
const { renderFlash } = useContext(NotificationContext);
|
||
const [isSubmitDisabled, setIsSubmitDisabled] = useState(true);
|
||
const [selectedApp, setSelectedApp] = useState<IVppApp | null>(null);
|
||
|
||
const {
|
||
data: vppInfo,
|
||
isLoading: isLoadingVppInfo,
|
||
error: errorVppInfo,
|
||
} = useQuery<IGetVppInfoResponse, AxiosError>(
|
||
["vppInfo"],
|
||
() => mdmAppleAPI.getVppInfo(),
|
||
{
|
||
...DEFAULT_USE_QUERY_OPTIONS,
|
||
staleTime: 30000,
|
||
retry: (tries, error) => error.status !== 404 && tries <= 3,
|
||
}
|
||
);
|
||
|
||
const {
|
||
data: vppApps,
|
||
isLoading: isLoadingVppApps,
|
||
error: errorVppApps,
|
||
} = useQuery(["vppSoftware", teamId], () => mdmAppleAPI.getVppApps(teamId), {
|
||
...DEFAULT_USE_QUERY_OPTIONS,
|
||
enabled: !!vppInfo,
|
||
staleTime: 30000,
|
||
select: (res) => res.app_store_apps,
|
||
});
|
||
|
||
const onSelectApp = (app: IVppApp) => {
|
||
setIsSubmitDisabled(false);
|
||
setSelectedApp(app);
|
||
};
|
||
|
||
const onAddSoftware = async () => {
|
||
if (!selectedApp) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
await mdmAppleAPI.addVppApp(teamId, selectedApp.app_store_id);
|
||
renderFlash(
|
||
"success",
|
||
<>
|
||
<b>{selectedApp.name}</b> successfully added. Go to Host details page
|
||
to install software.
|
||
</>
|
||
);
|
||
const queryParams = buildQueryStringFromParams({
|
||
team_id: teamId,
|
||
available_for_install: true,
|
||
});
|
||
router.push(`${PATHS.SOFTWARE}?${queryParams}`);
|
||
} catch (e) {
|
||
renderFlash("error", getErrorMessage(e));
|
||
}
|
||
onExit();
|
||
};
|
||
|
||
const renderContent = () => {
|
||
if (isLoadingVppInfo || isLoadingVppApps) {
|
||
return <Spinner />;
|
||
}
|
||
|
||
if (
|
||
errorVppInfo &&
|
||
getErrorReason(errorVppInfo).includes("MDMConfigAsset was not found")
|
||
) {
|
||
return <EnableVppCard />;
|
||
}
|
||
|
||
if (errorVppInfo || errorVppApps) {
|
||
return <DataError className={`${baseClass}__error`} />;
|
||
}
|
||
|
||
return vppApps ? (
|
||
<VppAppList
|
||
apps={vppApps}
|
||
selectedApp={selectedApp}
|
||
onSelect={onSelectApp}
|
||
/>
|
||
) : null;
|
||
};
|
||
|
||
return (
|
||
<div className={baseClass}>
|
||
<p className={`${baseClass}__description`}>
|
||
Apple App Store apps purchased via Apple Business Manager.
|
||
</p>
|
||
{renderContent()}
|
||
<div className="modal-cta-wrap">
|
||
<Button
|
||
type="submit"
|
||
variant="brand"
|
||
disabled={isSubmitDisabled}
|
||
onClick={onAddSoftware}
|
||
>
|
||
Add software
|
||
</Button>
|
||
<Button onClick={onExit} variant="inverse">
|
||
Cancel
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default AppStoreVpp;
|