fleet/frontend/pages/ManageControlsPage/SetupExperience/cards/BootstrapPackage/BootstrapPackage.tsx
Gabriel Hernandez e16c3c7272
change TurnOnMDMMessage component to generic TurnOnMessage and use in end user auth page (#36477)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #35296

This makes the TurnOnMDMMessage component more generic and display a
configurage "Turn on" message. We then are able to use this in the End
user auth page on the controls page.

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.
- [x] QA'd all new/changed functionality manually
2025-12-02 12:11:10 +00:00

254 lines
7.7 KiB
TypeScript

import React, { useContext, useState } from "react";
import { useQuery } from "react-query";
import { AxiosError, AxiosResponse } from "axios";
import PATHS from "router/paths";
import { IApiError } from "interfaces/errors";
import { IConfig } from "interfaces/config";
import { API_NO_TEAM_ID, ITeamConfig } from "interfaces/team";
import { ISoftwareTitle } from "interfaces/software";
import mdmAPI, {
IGetBootstrapPackageMetadataResponse,
IGetSetupExperienceScriptResponse,
IGetSetupExperienceSoftwareResponse,
} from "services/entities/mdm";
import configAPI from "services/entities/config";
import teamsAPI, { ILoadTeamResponse } from "services/entities/teams";
import { NotificationContext } from "context/notification";
import {
DEFAULT_USE_QUERY_OPTIONS,
LEARN_MORE_ABOUT_BASE_LINK,
} from "utilities/constants";
import Spinner from "components/Spinner";
import GenericMsgWithNavButton from "components/GenericMsgWithNavButton";
import SectionHeader from "components/SectionHeader";
import CustomLink from "components/CustomLink";
import PackageUploader from "./components/BootstrapPackageUploader";
import UploadedPackageView from "./components/UploadedPackageView";
import DeleteBootstrapPackageModal from "./components/DeleteBootstrapPackageModal";
import BootstrapAdvancedOptions from "./components/BootstrapAdvancedOptions";
import SetupExperienceContentContainer from "../../components/SetupExperienceContentContainer";
import { getInstallSoftwareDuringSetupCount } from "../InstallSoftware/components/AddInstallSoftware/helpers";
import { ISetupExperienceCardProps } from "../../SetupExperienceNavItems";
import getManualAgentInstallSetting from "../../helpers";
const baseClass = "bootstrap-package";
// This is so large because we want to get all the software titles that are
// available for install so we can correctly display the selected count.
const PER_PAGE_SIZE = 3000;
const BootstrapPackage = ({
currentTeamId,
router,
}: ISetupExperienceCardProps) => {
const { renderFlash } = useContext(NotificationContext);
const [
selectedManualAgentInstall,
setSelectedManualAgentInstall,
] = useState<boolean>(false);
const [
showDeleteBootstrapPackageModal,
setShowDeleteBootstrapPackageModal,
] = useState(false);
const { data: macSoftwareTitles, isLoading: isLoadingSoftware } = useQuery<
IGetSetupExperienceSoftwareResponse,
AxiosError,
ISoftwareTitle[] | null
>(
["install-software", currentTeamId],
() =>
mdmAPI.getSetupExperienceSoftware({
platform: "macos",
team_id: currentTeamId,
per_page: PER_PAGE_SIZE,
}),
{
...DEFAULT_USE_QUERY_OPTIONS,
select: (res) => res.software_titles,
}
);
const { data: script, isLoading: isLoadingScript } = useQuery<
IGetSetupExperienceScriptResponse,
AxiosError
>(
["setup-experience-script", currentTeamId],
() => mdmAPI.getSetupExperienceScript(currentTeamId),
{ ...DEFAULT_USE_QUERY_OPTIONS }
);
const {
data: globalConfig,
isLoading: isLoadingGlobalConfig,
refetch: refetchGlobalConfig,
} = useQuery<IConfig, Error>(
["config", currentTeamId],
() => configAPI.loadAll(),
{
...DEFAULT_USE_QUERY_OPTIONS,
onSuccess: (data) => {
if (currentTeamId === API_NO_TEAM_ID) {
setSelectedManualAgentInstall(
getManualAgentInstallSetting(currentTeamId, data)
);
}
},
}
);
const {
isLoading: isLoadingTeamConfig,
refetch: refetchTeamConfig,
} = useQuery<ILoadTeamResponse, Error, ITeamConfig>(
["team", currentTeamId],
() => teamsAPI.load(currentTeamId),
{
...DEFAULT_USE_QUERY_OPTIONS,
enabled: currentTeamId !== API_NO_TEAM_ID,
select: (res) => res.team,
onSuccess: (data) => {
setSelectedManualAgentInstall(
getManualAgentInstallSetting(currentTeamId, undefined, data)
);
},
}
);
const {
data: bootstrapMetadata,
isLoading: isloadingBootstrapMetadata,
error: errorBootstrapMetadata,
refetch: refretchBootstrapMetadata,
} = useQuery<IGetBootstrapPackageMetadataResponse, AxiosResponse<IApiError>>(
["bootstrap-metadata", currentTeamId],
() => mdmAPI.getBootstrapPackageMetadata(currentTeamId),
{
...DEFAULT_USE_QUERY_OPTIONS,
cacheTime: 0,
}
);
const onUpload = () => {
refretchBootstrapMetadata();
};
const onDelete = async () => {
try {
await mdmAPI.deleteBootstrapPackage(currentTeamId);
await mdmAPI.updateSetupExperienceSettings({
team_id: currentTeamId,
manual_agent_install: false,
});
renderFlash("success", "Successfully deleted!");
} catch {
renderFlash("error", "Couldn't delete. Please try again.");
} finally {
setShowDeleteBootstrapPackageModal(false);
refretchBootstrapMetadata();
if (currentTeamId !== API_NO_TEAM_ID) {
refetchTeamConfig();
} else {
refetchGlobalConfig();
}
}
};
// we are relying on the API to tell us this resource does not exist to
// determine if the user has uploaded a bootstrap package.
const noPackageUploaded =
(errorBootstrapMetadata && errorBootstrapMetadata.status === 404) ||
!bootstrapMetadata;
const hasSetupExperienceInstallSoftware =
getInstallSoftwareDuringSetupCount(macSoftwareTitles) !== 0;
const hasSetupExperienceScript = !!script;
const renderBootstrapView = () => {
const bootstrapPackageView = noPackageUploaded ? (
<PackageUploader currentTeamId={currentTeamId} onUpload={onUpload} />
) : (
<UploadedPackageView
bootstrapPackage={bootstrapMetadata}
currentTeamId={currentTeamId}
onDelete={() => setShowDeleteBootstrapPackageModal(true)}
/>
);
return (
<SetupExperienceContentContainer className={`${baseClass}__content`}>
<div className={`${baseClass}__uploader-container`}>
{bootstrapPackageView}
<BootstrapAdvancedOptions
currentTeamId={currentTeamId}
disableInstallManually={
noPackageUploaded ||
hasSetupExperienceInstallSoftware ||
hasSetupExperienceScript
}
selectManualAgentInstall={selectedManualAgentInstall}
onChange={(manualAgentInstall) => {
setSelectedManualAgentInstall(manualAgentInstall);
}}
/>
</div>
</SetupExperienceContentContainer>
);
};
const isLoading =
isloadingBootstrapMetadata ||
isLoadingGlobalConfig ||
isLoadingTeamConfig ||
isLoadingScript ||
isLoadingSoftware;
const renderContent = () => {
if (isLoading) {
return <Spinner />;
}
if (
!(
globalConfig?.mdm.enabled_and_configured &&
globalConfig?.mdm.apple_bm_enabled_and_configured
)
) {
return (
<GenericMsgWithNavButton
header="Additional configuration required"
info="Supported on macOS. To customize, first turn on automatic enrollment."
buttonText="Turn on"
path={PATHS.ADMIN_INTEGRATIONS_MDM}
router={router}
/>
);
}
return renderBootstrapView();
};
return (
<section className={baseClass}>
<SectionHeader
title="Bootstrap package"
details={
<CustomLink
newTab
url={`${LEARN_MORE_ABOUT_BASE_LINK}/setup-experience/bootstrap-package`}
text="Preview end user experience"
/>
}
/>
{renderContent()}
{showDeleteBootstrapPackageModal && (
<DeleteBootstrapPackageModal
onDelete={onDelete}
onCancel={() => setShowDeleteBootstrapPackageModal(false)}
/>
)}
</section>
);
};
export default BootstrapPackage;