2023-02-21 15:31:19 +00:00
|
|
|
|
import React, { useContext, useRef, useState } from "react";
|
2023-05-05 13:44:05 +00:00
|
|
|
|
import { useQuery } from "react-query";
|
2023-02-21 15:31:19 +00:00
|
|
|
|
import { AxiosResponse } from "axios";
|
|
|
|
|
|
|
|
|
|
|
|
import { IApiError } from "interfaces/errors";
|
2023-05-05 13:44:05 +00:00
|
|
|
|
import { IMdmProfile, IMdmProfilesResponse } from "interfaces/mdm";
|
2023-02-21 15:31:19 +00:00
|
|
|
|
import mdmAPI from "services/entities/mdm";
|
|
|
|
|
|
import { AppContext } from "context/app";
|
|
|
|
|
|
import { NotificationContext } from "context/notification";
|
|
|
|
|
|
|
|
|
|
|
|
import CustomLink from "components/CustomLink";
|
2023-03-06 15:03:48 +00:00
|
|
|
|
|
|
|
|
|
|
import FileUploader from "../../../components/FileUploader";
|
|
|
|
|
|
import UploadList from "../../../components/UploadList";
|
2023-02-21 15:31:19 +00:00
|
|
|
|
|
|
|
|
|
|
import { UPLOAD_ERROR_MESSAGES, getErrorMessage } from "./helpers";
|
|
|
|
|
|
import DeleteProfileModal from "./components/DeleteProfileModal/DeleteProfileModal";
|
2023-03-06 15:03:48 +00:00
|
|
|
|
import ProfileListItem from "./components/ProfileListItem";
|
|
|
|
|
|
import ProfileListHeading from "./components/ProfileListHeading";
|
2023-02-21 15:31:19 +00:00
|
|
|
|
|
|
|
|
|
|
const baseClass = "custom-settings";
|
|
|
|
|
|
|
2023-03-08 19:43:00 +00:00
|
|
|
|
interface ICustomSettingsProps {
|
2023-05-05 13:44:05 +00:00
|
|
|
|
currentTeamId: number;
|
|
|
|
|
|
onMutation: () => void;
|
2023-03-08 19:43:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const CustomSettings = ({
|
2023-05-05 13:44:05 +00:00
|
|
|
|
currentTeamId,
|
|
|
|
|
|
onMutation,
|
2023-03-08 19:43:00 +00:00
|
|
|
|
}: ICustomSettingsProps) => {
|
2023-02-21 15:31:19 +00:00
|
|
|
|
const { renderFlash } = useContext(NotificationContext);
|
|
|
|
|
|
|
|
|
|
|
|
const [showDeleteProfileModal, setShowDeleteProfileModal] = useState(false);
|
|
|
|
|
|
const [showLoading, setShowLoading] = useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
const selectedProfile = useRef<IMdmProfile | null>(null);
|
|
|
|
|
|
|
|
|
|
|
|
const onClickDelete = (profile: IMdmProfile) => {
|
|
|
|
|
|
selectedProfile.current = profile;
|
|
|
|
|
|
setShowDeleteProfileModal(true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2023-05-05 13:44:05 +00:00
|
|
|
|
const { data: profiles, refetch: refetchProfiles } = useQuery<
|
|
|
|
|
|
IMdmProfilesResponse,
|
|
|
|
|
|
unknown,
|
|
|
|
|
|
IMdmProfile[] | null
|
|
|
|
|
|
>(["profiles", currentTeamId], () => mdmAPI.getProfiles(currentTeamId), {
|
|
|
|
|
|
select: (data) => data.profiles,
|
|
|
|
|
|
refetchOnWindowFocus: false,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2023-02-21 15:31:19 +00:00
|
|
|
|
const onFileUpload = async (files: FileList | null) => {
|
|
|
|
|
|
setShowLoading(true);
|
|
|
|
|
|
|
2023-03-20 15:36:54 +00:00
|
|
|
|
if (!files || files.length === 0) {
|
|
|
|
|
|
setShowLoading(false);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2023-02-21 15:31:19 +00:00
|
|
|
|
|
|
|
|
|
|
const file = files[0];
|
|
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
|
file.type !== "application/x-apple-aspen-config" ||
|
|
|
|
|
|
!file.name.includes(".mobileconfig")
|
|
|
|
|
|
) {
|
|
|
|
|
|
renderFlash("error", UPLOAD_ERROR_MESSAGES.wrongType.message);
|
2023-03-15 20:42:01 +00:00
|
|
|
|
setShowLoading(false);
|
2023-02-21 15:31:19 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2023-05-05 13:44:05 +00:00
|
|
|
|
await mdmAPI.uploadProfile(file, currentTeamId);
|
|
|
|
|
|
refetchProfiles();
|
|
|
|
|
|
onMutation();
|
2023-02-21 15:31:19 +00:00
|
|
|
|
renderFlash("success", "Successfully uploaded!");
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
const error = e as AxiosResponse<IApiError>;
|
|
|
|
|
|
const errMessage = getErrorMessage(error);
|
|
|
|
|
|
renderFlash("error", errMessage);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setShowLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const onCancelDelete = () => {
|
|
|
|
|
|
selectedProfile.current = null;
|
|
|
|
|
|
setShowDeleteProfileModal(false);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const onDeleteProfile = async (profileId: number) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await mdmAPI.deleteProfile(profileId);
|
2023-05-05 13:44:05 +00:00
|
|
|
|
refetchProfiles();
|
|
|
|
|
|
onMutation();
|
2023-02-21 15:31:19 +00:00
|
|
|
|
renderFlash("success", "Successfully deleted!");
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
renderFlash("error", "Couldn’t delete. Please try again.");
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
selectedProfile.current = null;
|
|
|
|
|
|
setShowDeleteProfileModal(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className={baseClass}>
|
2023-03-08 02:35:11 +00:00
|
|
|
|
<h2>Custom settings</h2>
|
2023-02-21 15:31:19 +00:00
|
|
|
|
<p className={`${baseClass}__description`}>
|
|
|
|
|
|
Create and upload configuration profiles to apply custom settings.{" "}
|
|
|
|
|
|
<CustomLink
|
|
|
|
|
|
newTab
|
|
|
|
|
|
text="Learn how"
|
2023-05-01 15:52:57 +00:00
|
|
|
|
url="https://fleetdm.com/docs/using-fleet/mdm-custom-macos-settings"
|
2023-02-21 15:31:19 +00:00
|
|
|
|
/>
|
|
|
|
|
|
</p>
|
|
|
|
|
|
|
2023-03-06 15:03:48 +00:00
|
|
|
|
{profiles && (
|
|
|
|
|
|
<UploadList
|
|
|
|
|
|
listItems={profiles}
|
|
|
|
|
|
HeadingComponent={ProfileListHeading}
|
|
|
|
|
|
ListItemComponent={({ listItem }) => (
|
|
|
|
|
|
<ProfileListItem profile={listItem} onDelete={onClickDelete} />
|
|
|
|
|
|
)}
|
2023-02-21 15:31:19 +00:00
|
|
|
|
/>
|
2023-03-06 15:03:48 +00:00
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
<FileUploader
|
|
|
|
|
|
icon="profile"
|
|
|
|
|
|
message="Configuration profile (.mobileconfig)"
|
2023-04-27 15:10:41 +00:00
|
|
|
|
accept=".mobileconfig,application/x-apple-aspen-config"
|
2023-03-06 15:03:48 +00:00
|
|
|
|
isLoading={showLoading}
|
|
|
|
|
|
onFileUpload={onFileUpload}
|
2023-04-27 15:10:41 +00:00
|
|
|
|
className={`${baseClass}__file-uploader`}
|
2023-03-06 15:03:48 +00:00
|
|
|
|
/>
|
2023-02-21 15:31:19 +00:00
|
|
|
|
{showDeleteProfileModal && selectedProfile.current && (
|
|
|
|
|
|
<DeleteProfileModal
|
|
|
|
|
|
profileName={selectedProfile.current?.name}
|
|
|
|
|
|
profileId={selectedProfile.current?.profile_id}
|
|
|
|
|
|
onCancel={onCancelDelete}
|
|
|
|
|
|
onDelete={onDeleteProfile}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export default CustomSettings;
|