diff --git a/changes/issue-18848-include-all-exclude-any-custom-profile b/changes/issue-18848-include-all-exclude-any-custom-profile
new file mode 100644
index 0000000000..ef065303f3
--- /dev/null
+++ b/changes/issue-18848-include-all-exclude-any-custom-profile
@@ -0,0 +1,2 @@
+- add UI for uploading custom profiles with a target of hosts that include all/exclude
+ any selected labels
diff --git a/frontend/interfaces/config.ts b/frontend/interfaces/config.ts
index ebb286630c..21ac95dbcd 100644
--- a/frontend/interfaces/config.ts
+++ b/frontend/interfaces/config.ts
@@ -29,6 +29,12 @@ export interface IMacOsMigrationSettings {
webhook_url: string;
}
+interface ICustomSetting {
+ path: string;
+ labels_include_all?: string[];
+ labels_exclude_any?: string[];
+}
+
export interface IMdmConfig {
enable_disk_encryption: boolean;
enabled_and_configured: boolean;
@@ -42,7 +48,7 @@ export interface IMdmConfig {
deadline: string | null;
};
macos_settings: {
- custom_settings: null;
+ custom_settings: null | ICustomSetting[];
enable_disk_encryption: boolean;
};
macos_setup: {
diff --git a/frontend/interfaces/dropdownOption.ts b/frontend/interfaces/dropdownOption.ts
index a8895d48b9..67b1bacd4d 100644
--- a/frontend/interfaces/dropdownOption.ts
+++ b/frontend/interfaces/dropdownOption.ts
@@ -1,3 +1,4 @@
+import { ReactNode } from "react";
import PropTypes from "prop-types";
export default PropTypes.shape({
@@ -10,6 +11,7 @@ export interface IDropdownOption {
disabled?: boolean;
label: string | JSX.Element;
value: string | number;
+ helpText?: ReactNode;
premiumOnly?: boolean;
tooltipContent?: string | JSX.Element;
}
diff --git a/frontend/interfaces/mdm.ts b/frontend/interfaces/mdm.ts
index 4b3a8791e6..a911f0c9e7 100644
--- a/frontend/interfaces/mdm.ts
+++ b/frontend/interfaces/mdm.ts
@@ -71,7 +71,8 @@ export type ProfilePlatform = "darwin" | "windows";
export interface IProfileLabel {
name: string;
- broken: boolean;
+ id?: number; // id is only present when the label is not broken
+ broken?: boolean;
}
export interface IMdmProfile {
@@ -83,7 +84,8 @@ export interface IMdmProfile {
created_at: string;
updated_at: string;
checksum: string | null; // null for windows profiles
- labels?: IProfileLabel[];
+ labels_include_all?: IProfileLabel[];
+ labels_exclude_any?: IProfileLabel[];
}
export type MdmProfileStatus = "verified" | "verifying" | "pending" | "failed";
diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/CustomSettings.tsx b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/CustomSettings.tsx
index 8298a10647..89887e9b83 100644
--- a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/CustomSettings.tsx
+++ b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/CustomSettings.tsx
@@ -20,7 +20,7 @@ import Pagination from "pages/ManageControlsPage/components/Pagination";
import UploadList from "../../../components/UploadList";
import AddProfileCard from "./components/ProfileUploader/components/AddProfileCard";
-import AddProfileModal from "./components/ProfileUploader/components/AddProfileModal";
+import AddProfileModal from "./components/ProfileUploader/components/AddProfileModal/AddProfileModal";
import DeleteProfileModal from "./components/DeleteProfileModal/DeleteProfileModal";
import ProfileLabelsModal from "./components/ProfileLabelsModal/ProfileLabelsModal";
import ProfileListItem from "./components/ProfileListItem";
@@ -101,7 +101,7 @@ const CustomSettings = ({
onMutation();
renderFlash("success", "Successfully deleted!");
} catch (e) {
- renderFlash("error", "Couldn’t delete. Please try again.");
+ renderFlash("error", "Couldn't delete. Please try again.");
} finally {
selectedProfile.current = null;
setShowDeleteProfileModal(false);
@@ -169,6 +169,10 @@ const CustomSettings = ({
);
};
+ const hasLabels =
+ !!profileLabelsModalData?.labels_include_all?.length ||
+ !!profileLabelsModalData?.labels_exclude_any?.length;
+
return (
@@ -189,7 +193,6 @@ const CustomSettings = ({
)}
{showAddProfileModal && (
)}
- {!!isPremiumTier && !!profileLabelsModalData?.labels?.length && (
+ {isPremiumTier && hasLabels && (
(
-
- {profileName} will only be applied to hosts that have all these
- labels:
-
-);
+ targetType,
+}: IModalDescriptionProps) => {
+ const targetTypeText =
+ targetType === "includeAll" ? (
+ <>
+ have all
+ >
+ ) : (
+ <>
+ don't have any
+ >
+ );
+
+ return (
+
+ {profileName} profile only applies to hosts that {targetTypeText}{" "}
+ of these labels:
+
+ );
+};
const BrokenLabelWarning = () => (
The configuration profile is{" "}
broken
@@ -67,7 +84,14 @@ const ProfileLabelsModal = ({
profile,
setModalData,
}: IProfileLabelsModalProps) => {
- if (!profile?.labels?.length) {
+ if (!profile) {
+ return null;
+ }
+
+ const { name, labels_include_all, labels_exclude_any } = profile;
+ const labels = labels_include_all || labels_exclude_any;
+
+ if (!labels?.length) {
// caller ensures this never happens
return null;
}
@@ -75,9 +99,13 @@ const ProfileLabelsModal = ({
return (
setModalData(null)}>
- {profile.labels.some((label) => label.broken) &&
}
-
-
+ {labels.some((label) => label.broken) &&
}
+
+