fleet/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareCustomPackage/SoftwareCustomPackage.tsx

192 lines
5.6 KiB
TypeScript
Raw Normal View History

import React, { useContext, useEffect } from "react";
import { InjectedRouter } from "react-router";
import { useQuery } from "react-query";
import PATHS from "router/paths";
import { DEFAULT_USE_QUERY_OPTIONS } from "utilities/constants";
import { getFileDetails, IFileDetails } from "utilities/file/fileUtils";
import { getPathWithQueryParams, QueryParams } from "utilities/url";
import softwareAPI, {
MAX_FILE_SIZE_BYTES,
MAX_FILE_SIZE_MB,
} from "services/entities/software";
import labelsAPI, { getCustomLabels } from "services/entities/labels";
import { NotificationContext } from "context/notification";
import { AppContext } from "context/app";
import { ILabelSummary } from "interfaces/label";
import FileProgressModal from "components/FileProgressModal";
import PremiumFeatureMessage from "components/PremiumFeatureMessage";
import Spinner from "components/Spinner";
import DataError from "components/DataError";
import PackageForm from "pages/SoftwarePage/components/PackageForm";
import { IPackageFormData } from "pages/SoftwarePage/components/PackageForm/PackageForm";
import { getErrorMessage } from "./helpers";
const baseClass = "software-custom-package";
interface ISoftwarePackageProps {
currentTeamId: number;
router: InjectedRouter;
isSidePanelOpen: boolean;
setSidePanelOpen: (isOpen: boolean) => void;
}
const SoftwareCustomPackage = ({
currentTeamId,
router,
isSidePanelOpen,
setSidePanelOpen,
}: ISoftwarePackageProps) => {
const { renderFlash } = useContext(NotificationContext);
const { isPremiumTier } = useContext(AppContext);
const [uploadProgress, setUploadProgress] = React.useState(0);
const [uploadDetails, setUploadDetails] = React.useState<IFileDetails | null>(
null
);
const {
data: labels,
isLoading: isLoadingLabels,
isError: isErrorLabels,
} = useQuery<ILabelSummary[], Error>(
["custom_labels"],
() => labelsAPI.summary().then((res) => getCustomLabels(res.labels)),
{
...DEFAULT_USE_QUERY_OPTIONS,
enabled: isPremiumTier,
}
);
useEffect(() => {
const beforeUnloadHandler = (e: BeforeUnloadEvent) => {
e.preventDefault();
// Next line with e.returnValue is included for legacy support
// e.g.Chrome / Edge < 119
e.returnValue = true;
};
// set up event listener to prevent user from leaving page while uploading
if (uploadDetails) {
addEventListener("beforeunload", beforeUnloadHandler);
} else {
removeEventListener("beforeunload", beforeUnloadHandler);
}
// clean up event listener and timeout on component unmount
return () => {
removeEventListener("beforeunload", beforeUnloadHandler);
};
}, [uploadDetails]);
const onCancel = () => {
router.push(
getPathWithQueryParams(PATHS.SOFTWARE_TITLES, {
team_id: currentTeamId,
})
);
};
const onSubmit = async (formData: IPackageFormData) => {
if (!formData.software) {
renderFlash(
"error",
`Couldn't add. Please refresh the page and try again.`
);
return;
}
if (formData.software && formData.software.size > MAX_FILE_SIZE_BYTES) {
renderFlash(
"error",
`Couldn't add. The maximum file size is ${MAX_FILE_SIZE_MB} MB.`
);
return;
}
setUploadDetails(getFileDetails(formData.software));
// Note: This TODO is copied to onSaveSoftwareChanges in EditSoftwareModal
// TODO: confirm we are deleting the second sentence (not modifying it) for non-self-service installers
try {
await softwareAPI.addSoftwarePackage({
data: formData,
teamId: currentTeamId,
onUploadProgress: (progressEvent) => {
const progress = progressEvent.progress || 0;
// for large uploads it seems to take a bit for the server to finalize its response so we'll keep the
// progress bar at 97% until the server response is received
setUploadProgress(Math.max(progress - 0.03, 0.01));
},
});
const newQueryParams: QueryParams = { team_id: currentTeamId };
if (formData.selfService) {
newQueryParams.self_service = true;
} else {
newQueryParams.available_for_install = true;
}
router.push(
getPathWithQueryParams(PATHS.SOFTWARE_TITLES, newQueryParams)
);
renderFlash(
"success",
<>
<b>{formData.software?.name}</b> successfully added.
{formData.selfService
? " The end user can install from Fleet Desktop."
: ""}
</>
);
} catch (e) {
renderFlash("error", getErrorMessage(e));
}
setUploadDetails(null);
};
const renderContent = () => {
if (isLoadingLabels) {
return <Spinner />;
}
if (isErrorLabels) {
return <DataError className={`${baseClass}__data-error`} />;
}
return (
<>
<PackageForm
labels={labels || []}
showSchemaButton={!isSidePanelOpen}
onClickShowSchema={() => setSidePanelOpen(true)}
className={`${baseClass}__package-form`}
onCancel={onCancel}
onSubmit={onSubmit}
UI - GitOps Mode: Core abstractions, first batch of applications (#26401) ## For #26229 – Part 1 ![ezgif-6bbe6d60c12ed4](https://github.com/user-attachments/assets/37a04b64-abd7-4605-b4ac-9542836ff562) - This PR contains the core abstractions, routes, API updates, and types for GitOps mode in the UI. Since this work will touch essentially every part of the Fleet UI, it is ripe for merge conflicts. To mitigate such conflicts, I'll be merging this work in a number of iterative PRs. ~To effectively gate any of this work from showing until it is all merged to `main`, [this commit](feedbb2d4c25ec2e304e1f18d409cee62f6752ed) hides the settings section that allows enabling/disabling this setting, effectively feature flagging the entire thing. In the last of these iterative PRs, that commit will be reverted to engage the entire feature. For testing purposes, reviewers can `git revert feedbb2d4c25ec2e304e1f18d409cee62f6752ed` locally~ The new settings section for this feature is feature flagged until all PRs are merged - to show the setting section while testing, run `ALLOW_GITOPS_MODE=true NODE_ENV=development yarn run webpack --progress --watch` in place of `make generate-dev` - Changes file will be added and feature flag removed in the last PR - [x] Settings page with routing, form, API integration (hidden until last PR) - [x] Activities - [x] Navbar indicator - Apply GOM conditional UI to: - [x] Manage enroll secret modal: .5 - Controls > - [x] Scripts: - Setup experience > - [x] Install software > Select software modal - [x] OS Settings > - [x] Custom settings - [x] Disk encryption - [x] OS Updates 2/18/25, added to this PR: - [x] Controls > Setup experience > Run script - [x] Software > - [x] Manage automations modal - [x] Add software > - [x] App Store (VPP) - [x] Custom package - [x] Queries - [x] Manage - [x] Automations modal - [x] New - [x] Edit - [x] Policies - [x] Manage - [x] New - [x] Edit - Manage automations - [x] Calendar events - [x] Manual QA for all new/changed functionality --------- Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
2025-02-20 16:41:07 +00:00
// TODO - unnecessary if all uses of `PackageForm` are gitops compatible - TBD by product
gitopsCompatible
/>
{uploadDetails && (
<FileProgressModal
fileDetails={uploadDetails}
fileProgress={uploadProgress}
/>
)}
</>
);
};
if (!isPremiumTier) {
return (
<PremiumFeatureMessage className={`${baseClass}__premium-message`} />
);
}
return <div className={baseClass}>{renderContent()}</div>;
};
export default SoftwareCustomPackage;