From ccbdf46119c57fd6abd7d0c518fe270937f88941 Mon Sep 17 00:00:00 2001 From: Gabriel Hernandez Date: Thu, 31 Oct 2024 15:10:49 +0000 Subject: [PATCH] add UI to add the include any option for custom profile custom label target (#23390) relates to #22575 Add the UI for allowing custom profiles to be uploaded to hosts that have any of specified labels. I also spent some time cleaning up the custom settings card and its components. I was able to remove a lot of unused styles. ![image](https://github.com/user-attachments/assets/167a575c-4520-496b-8f73-ed390e079e44) - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. - [ ] Added/updated tests - [x] Manual QA for all new/changed functionality --- changes/22575-ui-for-include-any-labels | 2 + frontend/interfaces/mdm.ts | 1 + .../cards/CustomSettings/CustomSettings.tsx | 23 +-- .../cards/CustomSettings/_styles.scss | 149 ------------------ .../ProfileLabelsModal/ProfileLabelsModal.tsx | 93 +++++------ .../ProfileLabelsModal/_styles.scss | 32 ++++ .../ProfileListItem/ProfileListItem.tsx | 3 +- .../{ => AddProfileCard}/AddProfileCard.tsx | 16 +- .../components/AddProfileCard/_styles.scss | 21 +++ .../components/AddProfileCard/index.ts | 1 + .../AddProfileModal/AddProfileModal.tsx | 17 +- .../components/AddProfileModal/helpers.tsx | 31 +++- frontend/services/entities/mdm.ts | 19 ++- 13 files changed, 158 insertions(+), 250 deletions(-) create mode 100644 changes/22575-ui-for-include-any-labels create mode 100644 frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileLabelsModal/_styles.scss rename frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/{ => AddProfileCard}/AddProfileCard.tsx (69%) create mode 100644 frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileCard/_styles.scss create mode 100644 frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileCard/index.ts diff --git a/changes/22575-ui-for-include-any-labels b/changes/22575-ui-for-include-any-labels new file mode 100644 index 0000000000..5f66f8396b --- /dev/null +++ b/changes/22575-ui-for-include-any-labels @@ -0,0 +1,2 @@ +- add UI for allowing users to install custom profiles on hosts that include any of the defined +labels diff --git a/frontend/interfaces/mdm.ts b/frontend/interfaces/mdm.ts index 2f822eb968..07fe2cffb9 100644 --- a/frontend/interfaces/mdm.ts +++ b/frontend/interfaces/mdm.ts @@ -111,6 +111,7 @@ export interface IMdmProfile { updated_at: string; checksum: string | null; // null for windows profiles labels_include_all?: IProfileLabel[]; + labels_include_any?: IProfileLabel[]; labels_exclude_any?: IProfileLabel[]; } diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/CustomSettings.tsx b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/CustomSettings.tsx index 89887e9b83..00cc7e199b 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/AddProfileModal"; +import AddProfileModal from "./components/ProfileUploader/components/AddProfileModal"; import DeleteProfileModal from "./components/DeleteProfileModal/DeleteProfileModal"; import ProfileLabelsModal from "./components/ProfileLabelsModal/ProfileLabelsModal"; import ProfileListItem from "./components/ProfileListItem"; @@ -136,7 +136,7 @@ const CustomSettings = ({ } if (!profiles?.length) { - return null; + return ; } return ( @@ -144,11 +144,11 @@ const CustomSettings = ({ - ProfileListHeading({ - onClickAddProfile: () => setShowAddProfileModal(true), - }) - } + HeadingComponent={() => ( + setShowAddProfileModal(true)} + /> + )} ListItemComponent={({ listItem }) => (

- {renderProfileList()} - {!isLoadingProfiles && !isErrorProfiles && !profiles?.length && ( - - )} + <>{renderProfileList()} {showAddProfileModal && ( diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/_styles.scss b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/_styles.scss index 30a35b38fb..b758f3c777 100644 --- a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/_styles.scss +++ b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/_styles.scss @@ -1,69 +1,15 @@ .custom-settings { - .section-header { - margin: 0; - padding: 0 0 12px 0; - - h2 { - padding-bottom: 0; - border-bottom: none; - margin: 0; - } - } - &__description { font-size: $x-small; margin: $pad-large 0; } - &__profiles-header { - padding: $pad-medium $pad-large; - display: flex; - justify-content: space-between; - font-size: $x-small; - font-weight: $bold; - border-bottom: 1px solid $ui-fleet-black-10; - } - - &__profile-list { - list-style: none; - padding: 0; - margin: 0; - } - &__pagination-controls { display: flex; justify-content: flex-end; margin: $pad-large 0; } - &__file-uploader { - margin-top: $pad-xxlarge; - } - - &__labels-list { - border-radius: 6px; - border: 1px solid $ui-fleet-black-10; - - &--label { - display: flex; - height: 41px; - padding: 0 $pad-large; - align-items: center; - justify-content: space-between; - border-bottom: 1px solid $ui-fleet-black-10; - - .warning { - display: flex; - padding: 0; - gap: $pad-small; - } - - &:last-of-type { - border-bottom: none; - } - } - } - .upload-list { &__list { .list-item__label-count { @@ -84,99 +30,4 @@ } } } - - .add-profile { - &__card--content-wrap { - display: flex; - flex-direction: column; - align-items: center; - gap: $pad-medium; - padding: 28.5px 0; - } - - &__profile-graphic { - display: flex; - flex-direction: column; - align-items: center; - gap: $pad-small; - - &--message { - text-align: center; - line-height: 20px; - } - } - - &__button-wrap { - display: flex; - justify-content: flex-end; - padding-top: $pad-medium; - } - - &__target { - margin: $pad-large 0 $pad-small 0; - } - - &__description { - margin: $pad-medium 0; - } - - &__no-labels { - display: flex; - height: 187px; - flex-direction: column; - align-items: center; - gap: $pad-small; - justify-content: center; - - span { - color: $ui-fleet-black-75; - } - } - - &__checkboxes { - display: flex; - max-height: 187px; - flex-direction: column; - border-radius: $border-radius; - border: 1px solid $ui-fleet-black-10; - overflow-y: auto; - - .loading-spinner { - margin: 69.5px auto; - } - } - - &__label { - width: 100%; - padding: $pad-small $pad-medium; - box-sizing: border-box; - display: flex; - align-items: center; - - &:not(:last-child) { - border-bottom: 1px solid $ui-fleet-black-10; - } - - .form-field--checkbox { - width: auto; - } - } - - &__label-name { - padding-left: $pad-large; - } - - .fleet-checkbox { - height: 20px; - display: flex; - align-items: center; - - &__label { - width: 490px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - } - } } diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileLabelsModal/ProfileLabelsModal.tsx b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileLabelsModal/ProfileLabelsModal.tsx index 4128a5e826..f5590c83af 100644 --- a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileLabelsModal/ProfileLabelsModal.tsx +++ b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileLabelsModal/ProfileLabelsModal.tsx @@ -6,35 +6,7 @@ import InfoBanner from "components/InfoBanner"; import TooltipWrapper from "components/TooltipWrapper"; import Icon from "components/Icon"; -interface IModalDescriptionProps { - baseClass: string; - profileName: string; - targetType: "includeAll" | "excludeAny"; -} - -const ModalDescription = ({ - baseClass, - profileName, - 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 baseClass = "profile-labels-modal"; const BrokenLabelWarning = () => ( @@ -51,16 +23,10 @@ const BrokenLabelWarning = () => ( ); -const LabelsList = ({ - baseClass, - labels, -}: { - baseClass: string; - labels: IProfileLabel[]; -}) => ( -
+const LabelsList = ({ labels }: { labels: IProfileLabel[] }) => ( +
    {labels.map((label) => ( -
    +
  • {label.name} {label.broken && ( @@ -68,19 +34,17 @@ const LabelsList = ({ Label deleted )} -
  • + ))} -
+ ); interface IProfileLabelsModalProps { - baseClass: string; profile: IMdmProfile | null; setModalData: React.Dispatch>; } const ProfileLabelsModal = ({ - baseClass, profile, setModalData, }: IProfileLabelsModalProps) => { @@ -88,30 +52,53 @@ const ProfileLabelsModal = ({ return null; } - const { name, labels_include_all, labels_exclude_any } = profile; - const labels = labels_include_all || labels_exclude_any; + const { + name, + labels_include_all, + labels_include_any, + labels_exclude_any, + } = profile; + const labels = labels_include_all || labels_include_any || labels_exclude_any; if (!labels?.length) { // caller ensures this never happens return null; } + const renderlabelDescription = () => { + let targetTypeText = <>; + if (labels_include_all) { + targetTypeText = have all; + } else if (labels_include_any) { + targetTypeText = have all; + } else { + targetTypeText = don't have any; + } + + return ( +

+ {name} profile only applies to hosts that {targetTypeText} of + these labels: +

+ ); + }; + return ( - setModalData(null)}> -
+ setModalData(null)} + > + <> {labels.some((label) => label.broken) && } - - + <>{renderlabelDescription()} +
-
+
); }; diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileLabelsModal/_styles.scss b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileLabelsModal/_styles.scss new file mode 100644 index 0000000000..1bac831a22 --- /dev/null +++ b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileLabelsModal/_styles.scss @@ -0,0 +1,32 @@ +.profile-labels-modal { + &__description { + font-size: $x-small; + margin: $pad-large 0 $pad-medium; + } + + &__labels-list { + border-radius: 6px; + border: 1px solid $ui-fleet-black-10; + padding: 0; + margin: 0; + + &--label { + display: flex; + height: 41px; + padding: 0 $pad-large; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid $ui-fleet-black-10; + + .warning { + display: flex; + padding: 0; + gap: $pad-small; + } + + &:last-of-type { + border-bottom: none; + } + } + } +} diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileListItem/ProfileListItem.tsx b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileListItem/ProfileListItem.tsx index 9fb0039d28..b62437d8d8 100644 --- a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileListItem/ProfileListItem.tsx +++ b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileListItem/ProfileListItem.tsx @@ -87,6 +87,7 @@ const ProfileListItem = ({ const { created_at, labels_include_all, + labels_include_any, labels_exclude_any, name, platform, @@ -102,7 +103,7 @@ const ProfileListItem = ({ FileSaver.saveAs(file); }; - const labels = labels_include_all || labels_exclude_any; + const labels = labels_include_all || labels_include_any || labels_exclude_any; const renderLabelInfo = () => { if (!isPremium || labels === undefined || labels.length === 0) { diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileCard.tsx b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileCard/AddProfileCard.tsx similarity index 69% rename from frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileCard.tsx rename to frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileCard/AddProfileCard.tsx index c3ab102ce7..d9d09f081f 100644 --- a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileCard.tsx +++ b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileCard/AddProfileCard.tsx @@ -2,16 +2,16 @@ import React from "react"; import Card from "components/Card"; import Button from "components/buttons/Button"; -import ProfileGraphic from "./AddProfileGraphic"; +import ProfileGraphic from "../AddProfileGraphic"; -const AddProfileCard = ({ - baseClass, - setShowModal, -}: { - baseClass: string; +const baseClass = "add-profile-card"; + +interface IAddProfileCardProps { setShowModal: React.Dispatch>; -}) => ( - +} + +const AddProfileCard = ({ setShowModal }: IAddProfileCardProps) => ( +
); diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileModal/helpers.tsx b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileModal/helpers.tsx index d6d61c5628..a63c121023 100644 --- a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileModal/helpers.tsx +++ b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileModal/helpers.tsx @@ -5,11 +5,22 @@ import { IDropdownOption } from "interfaces/dropdownOption"; export const CUSTOM_TARGET_OPTIONS: IDropdownOption[] = [ { value: "labelsIncludeAll", - label: "Include all ", + label: "Include all", helpText: ( <> - Profile will only be applied to hosts that have all of these - labels{" "} + Profile will only be applied to hosts that have all of these + labels. + + ), + disabled: false, + }, + { + value: "labelsIncludeAny", + label: "Include any", + helpText: ( + <> + Profile will only be applied to hosts that have any of these + labels. ), disabled: false, @@ -19,8 +30,8 @@ export const CUSTOM_TARGET_OPTIONS: IDropdownOption[] = [ label: "Exclude any", helpText: ( <> - Profile will be applied to hosts that don't have any of - these labels{" "} + Profile will only be applied to hosts that don't have any of + these labels. ), disabled: false, @@ -36,7 +47,10 @@ export const listNamesFromSelectedLabels = (dict: Record) => { }, [] as string[]); }; -export type CustomTargetOption = "labelsIncludeAll" | "labelsExcludeAny"; +export type CustomTargetOption = + | "labelsIncludeAll" + | "labelsIncldeAny" + | "labelsExcludeAny"; export const generateLabelKey = ( target: string, @@ -51,3 +65,8 @@ export const generateLabelKey = ( [customTargetOption]: listNamesFromSelectedLabels(selectedLabels), }; }; + +export const getDescriptionText = (value: string) => { + return CUSTOM_TARGET_OPTIONS.find((option) => option.value === value) + ?.helpText; +}; diff --git a/frontend/services/entities/mdm.ts b/frontend/services/entities/mdm.ts index 285973d7ff..50ed3bd428 100644 --- a/frontend/services/entities/mdm.ts +++ b/frontend/services/entities/mdm.ts @@ -49,6 +49,7 @@ export interface IUploadProfileApiParams { file: File; teamId?: number; labelsIncludeAll?: string[]; + labelsIncludeAny?: string[]; labelsExcludeAny?: string[]; } @@ -132,6 +133,7 @@ const mdmService = { file, teamId, labelsIncludeAll, + labelsIncludeAny, labelsExcludeAny, }: IUploadProfileApiParams) => { const { MDM_PROFILES } = endpoints; @@ -143,11 +145,18 @@ const mdmService = { formData.append("team_id", teamId.toString()); } - if (labelsIncludeAll || labelsExcludeAny) { - const labels = labelsIncludeAll || labelsExcludeAny; - const labelKey = labelsIncludeAll - ? "labels_include_all" - : "labels_exclude_any"; + if (labelsIncludeAll || labelsIncludeAny || labelsExcludeAny) { + const labels = labelsIncludeAll || labelsIncludeAny || labelsExcludeAny; + + let labelKey = ""; + if (labelsIncludeAll) { + labelKey = "labels_include_all"; + } else if (labelsIncludeAny) { + labelKey = "labels_include_any"; + } else { + labelKey = "labels_exclude_any"; + } + labels?.forEach((label) => { formData.append(labelKey, label); });