diff --git a/frontend/pages/ManageControlsPage/OSSettings/ProfileStatusAggregate/ProfileStatusAggregate.tsx b/frontend/pages/ManageControlsPage/OSSettings/ProfileStatusAggregate/ProfileStatusAggregate.tsx index 92381e87a1..b4b489e36d 100644 --- a/frontend/pages/ManageControlsPage/OSSettings/ProfileStatusAggregate/ProfileStatusAggregate.tsx +++ b/frontend/pages/ManageControlsPage/OSSettings/ProfileStatusAggregate/ProfileStatusAggregate.tsx @@ -3,6 +3,7 @@ import React from "react"; import paths from "router/paths"; import { buildQueryStringFromParams } from "utilities/url"; import { MdmProfileStatus } from "interfaces/mdm"; +import { HOSTS_QUERY_PARAMS } from "services/entities/hosts"; import { ProfileStatusSummaryResponse } from "services/entities/mdm"; import Spinner from "components/Spinner"; @@ -31,12 +32,12 @@ const ProfileStatusCount = ({ hostCount, tooltipText, }: IProfileStatusCountProps) => { - const generateFilterHostsByStatusLink = () => { - return `${paths.MANAGE_HOSTS}?${buildQueryStringFromParams({ + const linkHostsByStatus = `${paths.MANAGE_HOSTS}?${buildQueryStringFromParams( + { team_id: teamId, - macos_settings: statusValue, - })}`; - }; + [HOSTS_QUERY_PARAMS.OS_SETTINGS]: statusValue, + } + )}`; return (
@@ -47,7 +48,7 @@ const ProfileStatusCount = ({ layout="vertical" valueClassName={`${baseClass}__status-indicator-value`} /> - {hostCount} hosts + {hostCount} hosts
); }; diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/DiskEncryption/components/DiskEncryptionTable/DiskEncryptionTableConfig.tsx b/frontend/pages/ManageControlsPage/OSSettings/cards/DiskEncryption/components/DiskEncryptionTable/DiskEncryptionTableConfig.tsx index 9a5b7baf5d..aef0bbc22c 100644 --- a/frontend/pages/ManageControlsPage/OSSettings/cards/DiskEncryption/components/DiskEncryptionTable/DiskEncryptionTableConfig.tsx +++ b/frontend/pages/ManageControlsPage/OSSettings/cards/DiskEncryption/components/DiskEncryptionTable/DiskEncryptionTableConfig.tsx @@ -5,7 +5,7 @@ import { IDiskEncryptionStatusAggregate, IDiskEncryptionSummaryResponse, } from "services/entities/mdm"; -import { DISK_ENCRYPTION_QUERY_PARAM_NAME } from "services/entities/hosts"; +import { HOSTS_QUERY_PARAMS } from "services/entities/hosts"; import TextCell from "components/TableContainer/DataTable/TextCell"; import HeaderCell from "components/TableContainer/DataTable/HeaderCell"; @@ -101,7 +101,7 @@ const defaultTableHeaders: IDataColumn[] = [ @@ -134,7 +134,7 @@ const windowsTableHeader: IDataColumn[] = [ diff --git a/frontend/pages/hosts/ManageHostsPage/HostsPageConfig.tsx b/frontend/pages/hosts/ManageHostsPage/HostsPageConfig.tsx index b4102d321e..be51c446ce 100644 --- a/frontend/pages/hosts/ManageHostsPage/HostsPageConfig.tsx +++ b/frontend/pages/hosts/ManageHostsPage/HostsPageConfig.tsx @@ -1,7 +1,7 @@ import React from "react"; import Icon from "components/Icon"; -import { DISK_ENCRYPTION_QUERY_PARAM_NAME } from "services/entities/hosts"; +import { HOSTS_QUERY_PARAMS } from "services/entities/hosts"; export const MANAGE_HOSTS_PAGE_FILTER_KEYS = [ "query", @@ -18,7 +18,8 @@ export const MANAGE_HOSTS_PAGE_FILTER_KEYS = [ "os_version", "munki_issue_id", "low_disk_space", - DISK_ENCRYPTION_QUERY_PARAM_NAME, + HOSTS_QUERY_PARAMS.OS_SETTINGS, + HOSTS_QUERY_PARAMS.DISK_ENCRYPTION, "bootstrap_package", ] as const; @@ -81,7 +82,7 @@ export const getHostSelectStatuses = (isSandboxMode = false) => { ]; }; -export const MAC_SETTINGS_FILTER_OPTIONS = [ +export const OS_SETTINGS_FILTER_OPTIONS = [ { disabled: false, label: "Verified", diff --git a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx index 6eedf30627..070ab3c533 100644 --- a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx +++ b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx @@ -18,7 +18,7 @@ import labelsAPI, { ILabelsResponse } from "services/entities/labels"; import teamsAPI, { ILoadTeamsResponse } from "services/entities/teams"; import globalPoliciesAPI from "services/entities/global_policies"; import hostsAPI, { - DISK_ENCRYPTION_QUERY_PARAM_NAME, + HOSTS_QUERY_PARAMS as PARAMS, ILoadHostsQueryKey, ILoadHostsResponse, ISortOption, @@ -50,7 +50,11 @@ import { IOperatingSystemVersion } from "interfaces/operating_system"; import { IPolicy, IStoredPolicyResponse } from "interfaces/policy"; import { ITeam } from "interfaces/team"; import { IEmptyTableProps } from "interfaces/empty_table"; -import { DiskEncryptionStatus, BootstrapPackageStatus } from "interfaces/mdm"; +import { + DiskEncryptionStatus, + BootstrapPackageStatus, + MdmProfileStatus, +} from "interfaces/mdm"; import sortUtils from "utilities/sort"; import { @@ -233,8 +237,9 @@ const ManageHostsPage = ({ ? parseInt(queryParams.low_disk_space, 10) : undefined; const missingHosts = queryParams?.status === "missing"; + const osSettingsStatus = queryParams?.[PARAMS.OS_SETTINGS]; const diskEncryptionStatus: DiskEncryptionStatus | undefined = - queryParams?.[DISK_ENCRYPTION_QUERY_PARAM_NAME]; + queryParams?.[PARAMS.DISK_ENCRYPTION]; const bootstrapPackageStatus: BootstrapPackageStatus | undefined = queryParams?.bootstrap_package; @@ -366,6 +371,7 @@ const ManageHostsPage = ({ page: tableQueryData ? tableQueryData.pageIndex : 0, perPage: tableQueryData ? tableQueryData.pageSize : 50, device_mapping: true, + osSettings: osSettingsStatus, diskEncryptionStatus, bootstrapPackageStatus, macSettingsStatus, @@ -402,6 +408,7 @@ const ManageHostsPage = ({ osId, osName, osVersion, + osSettings: osSettingsStatus, diskEncryptionStatus, bootstrapPackageStatus, macSettingsStatus, @@ -570,7 +577,24 @@ const ManageHostsPage = ({ routeParams, queryParams: { ...queryParams, - [DISK_ENCRYPTION_QUERY_PARAM_NAME]: newStatus, + [PARAMS.DISK_ENCRYPTION]: newStatus, + page: 0, // resets page index + }, + }) + ); + }; + + const handleChangeOsSettingsFilter = (newStatus: MdmProfileStatus) => { + handleResetPageIndex(); + + router.replace( + getNextLocationPath({ + pathPrefix: PATHS.MANAGE_HOSTS, + routeTemplate, + routeParams, + queryParams: { + ...queryParams, + [PARAMS.OS_SETTINGS]: newStatus, page: 0, // resets page index }, }) @@ -767,9 +791,11 @@ const ManageHostsPage = ({ newQueryParams.os_id = osId; newQueryParams.os_name = osName; newQueryParams.os_version = osVersion; + } else if (osSettingsStatus) { + newQueryParams[PARAMS.OS_SETTINGS] = osSettingsStatus; } else if (diskEncryptionStatus && isPremiumTier) { // Premium feature only - newQueryParams[DISK_ENCRYPTION_QUERY_PARAM_NAME] = diskEncryptionStatus; + newQueryParams[PARAMS.DISK_ENCRYPTION] = diskEncryptionStatus; } else if (bootstrapPackageStatus && isPremiumTier) { newQueryParams.bootstrap_package = bootstrapPackageStatus; } @@ -807,6 +833,7 @@ const ManageHostsPage = ({ router, routeTemplate, routeParams, + osSettingsStatus, diskEncryptionStatus, bootstrapPackageStatus, ] @@ -1539,6 +1566,7 @@ const ManageHostsPage = ({ softwareDetails: hostsData?.software || null, mdmSolutionDetails: hostsData?.mobile_device_management_solution || null, + osSettingsStatus, diskEncryptionStatus, bootstrapPackageStatus, }} @@ -1547,6 +1575,7 @@ const ManageHostsPage = ({ handleClearRouteParam={handleClearRouteParam} handleClearFilter={handleClearFilter} onChangePoliciesFilter={handleChangePoliciesFilter} + onChangeOsSettingsFilter={handleChangeOsSettingsFilter} onChangeDiskEncryptionStatusFilter={ handleChangeDiskEncryptionStatusFilter } diff --git a/frontend/pages/hosts/ManageHostsPage/components/HostsFilterBlock/HostsFilterBlock.tsx b/frontend/pages/hosts/ManageHostsPage/components/HostsFilterBlock/HostsFilterBlock.tsx index 1ff7287cb3..1c45076031 100644 --- a/frontend/pages/hosts/ManageHostsPage/components/HostsFilterBlock/HostsFilterBlock.tsx +++ b/frontend/pages/hosts/ManageHostsPage/components/HostsFilterBlock/HostsFilterBlock.tsx @@ -11,12 +11,13 @@ import { BootstrapPackageStatus, IMdmSolution, MDM_ENROLLMENT_STATUS, + MdmProfileStatus, } from "interfaces/mdm"; import { IMunkiIssuesAggregate } from "interfaces/macadmins"; import { ISoftware } from "interfaces/software"; import { IPolicy } from "interfaces/policy"; import { - DISK_ENCRYPTION_QUERY_PARAM_NAME, + HOSTS_QUERY_PARAMS, MacSettingsStatusQueryParam, } from "services/entities/hosts"; @@ -32,7 +33,7 @@ import Icon from "components/Icon/Icon"; import FilterPill from "../FilterPill"; import PoliciesFilter from "../PoliciesFilter"; -import { MAC_SETTINGS_FILTER_OPTIONS } from "../../HostsPageConfig"; +import { OS_SETTINGS_FILTER_OPTIONS } from "../../HostsPageConfig"; import DiskEncryptionStatusFilter from "../DiskEncryptionStatusFilter"; import BootstrapPackageStatusFilter from "../BootstrapPackageStatusFilter/BootstrapPackageStatusFilter"; @@ -63,6 +64,7 @@ interface IHostsFilterBlockProps { osVersions?: IOperatingSystemVersion[]; softwareDetails: ISoftware | null; mdmSolutionDetails: IMdmSolution | null; + osSettingsStatus?: MdmProfileStatus; diskEncryptionStatus?: DiskEncryptionStatus; bootstrapPackageStatus?: BootstrapPackageStatus; }; @@ -71,6 +73,7 @@ interface IHostsFilterBlockProps { handleClearRouteParam: () => void; handleClearFilter: (omitParams: string[]) => void; onChangePoliciesFilter: (response: PolicyResponse) => void; + onChangeOsSettingsFilter: (newStatus: MdmProfileStatus) => void; onChangeDiskEncryptionStatusFilter: (response: DiskEncryptionStatus) => void; onChangeBootstrapPackageStatusFilter: ( response: BootstrapPackageStatus @@ -105,6 +108,7 @@ const HostsFilterBlock = ({ softwareDetails, policy, mdmSolutionDetails, + osSettingsStatus, diskEncryptionStatus, bootstrapPackageStatus, }, @@ -113,6 +117,7 @@ const HostsFilterBlock = ({ handleClearRouteParam, handleClearFilter, onChangePoliciesFilter, + onChangeOsSettingsFilter, onChangeDiskEncryptionStatusFilter, onChangeBootstrapPackageStatusFilter, onChangeMacSettingsFilter, @@ -215,7 +220,7 @@ const HostsFilterBlock = ({ { + const label = "OS settings"; + return ( + <> + + handleClearFilter([HOSTS_QUERY_PARAMS.OS_SETTINGS])} + /> + + ); + }; + const renderDiskEncryptionStatusBlock = () => { if (!diskEncryptionStatus) return null; @@ -378,7 +401,9 @@ const HostsFilterBlock = ({ /> handleClearFilter([DISK_ENCRYPTION_QUERY_PARAM_NAME])} + onClear={() => + handleClearFilter([HOSTS_QUERY_PARAMS.DISK_ENCRYPTION]) + } /> ); @@ -414,6 +439,7 @@ const HostsFilterBlock = ({ osId || (osName && osVersion) || munkiIssueId || + osSettingsStatus || diskEncryptionStatus || bootstrapPackageStatus ) { @@ -458,6 +484,9 @@ const HostsFilterBlock = ({ return renderMunkiIssueFilterBlock(); case !!lowDiskSpaceHosts: return renderLowDiskSpaceFilterBlock(); + case !!osSettingsStatus: + console.log("renderOsSettingsBlock"); + return renderOsSettingsBlock(); case !!diskEncryptionStatus: return renderDiskEncryptionStatusBlock(); case !!bootstrapPackageStatus: diff --git a/frontend/pages/hosts/ManageHostsPage/components/HostsFilterBlock/_styles.scss b/frontend/pages/hosts/ManageHostsPage/components/HostsFilterBlock/_styles.scss index 8a306f4a72..a1072ad6cc 100644 --- a/frontend/pages/hosts/ManageHostsPage/components/HostsFilterBlock/_styles.scss +++ b/frontend/pages/hosts/ManageHostsPage/components/HostsFilterBlock/_styles.scss @@ -13,6 +13,7 @@ } // NOTE: Look more into this styling + &__os_settings-dropdown, &__macsettings-dropdown { width: 137px; diff --git a/frontend/services/entities/host_count.ts b/frontend/services/entities/host_count.ts index 5511dcfc10..7c30d17b35 100644 --- a/frontend/services/entities/host_count.ts +++ b/frontend/services/entities/host_count.ts @@ -1,7 +1,11 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import sendRequest from "services"; import endpoints from "utilities/endpoints"; -import { DiskEncryptionStatus, BootstrapPackageStatus } from "interfaces/mdm"; +import { + DiskEncryptionStatus, + BootstrapPackageStatus, + MdmProfileStatus, +} from "interfaces/mdm"; import { HostStatus } from "interfaces/host"; import { buildQueryStringFromParams, @@ -43,6 +47,7 @@ export interface IHostCountLoadOptions { osId?: number; osName?: string; osVersion?: string; + osSettings?: MdmProfileStatus; diskEncryptionStatus?: DiskEncryptionStatus; bootstrapPackageStatus?: BootstrapPackageStatus; } @@ -67,12 +72,17 @@ export default { const osId = options?.osId; const osName = options?.osName; const osVersion = options?.osVersion; + const osSettings = options?.osSettings; const diskEncryptionStatus = options?.diskEncryptionStatus; const bootstrapPackageStatus = options?.bootstrapPackageStatus; const queryParams = { query: globalFilter, - ...reconcileMutuallyInclusiveHostParams({ teamId, macSettingsStatus }), + ...reconcileMutuallyInclusiveHostParams({ + teamId, + macSettingsStatus, + osSettings, + }), ...reconcileMutuallyExclusiveHostParams({ label, policyId, @@ -85,6 +95,7 @@ export default { osName, osId, osVersion, + osSettings, diskEncryptionStatus, bootstrapPackageStatus, }), diff --git a/frontend/services/entities/hosts.ts b/frontend/services/entities/hosts.ts index e4d95353d5..65f8b1b241 100644 --- a/frontend/services/entities/hosts.ts +++ b/frontend/services/entities/hosts.ts @@ -14,6 +14,7 @@ import { DiskEncryptionStatus, BootstrapPackageStatus, IMdmSolution, + MdmProfileStatus, } from "interfaces/mdm"; import { IMunkiIssuesAggregate } from "interfaces/macadmins"; @@ -32,7 +33,10 @@ export interface ILoadHostsResponse { // the source of truth for the filter option names. // there are used on many other pages but we define them here. // TODO: add other filter options here. -export const DISK_ENCRYPTION_QUERY_PARAM_NAME = "os_settings_disk_encryption"; +export const HOSTS_QUERY_PARAMS = { + OS_SETTINGS: "os_settings", + DISK_ENCRYPTION: "os_settings_disk_encryption", +} as const; export interface ILoadHostsQueryKey extends ILoadHostsOptions { scope: "hosts"; @@ -62,6 +66,7 @@ export interface ILoadHostsOptions { device_mapping?: boolean; columns?: string; visibleColumns?: string; + osSettings?: MdmProfileStatus; diskEncryptionStatus?: DiskEncryptionStatus; bootstrapPackageStatus?: BootstrapPackageStatus; } @@ -88,6 +93,7 @@ export interface IExportHostsOptions { device_mapping?: boolean; columns?: string; visibleColumns?: string; + osSettings?: MdmProfileStatus; diskEncryptionStatus?: DiskEncryptionStatus; } @@ -179,6 +185,7 @@ export default { const visibleColumns = options?.visibleColumns; const label = getLabelParam(selectedLabels); const munkiIssueId = options?.munkiIssueId; + const osSettings = options?.osSettings; const diskEncryptionStatus = options?.diskEncryptionStatus; if (!sortBy.length) { @@ -189,7 +196,11 @@ export default { order_key: sortBy[0].key, order_direction: sortBy[0].direction, query: globalFilter, - ...reconcileMutuallyInclusiveHostParams({ teamId, macSettingsStatus }), + ...reconcileMutuallyInclusiveHostParams({ + teamId, + macSettingsStatus, + osSettings, + }), ...reconcileMutuallyExclusiveHostParams({ label, policyId, @@ -199,6 +210,7 @@ export default { munkiIssueId, softwareId, lowDiskSpaceHosts, + osSettings, diskEncryptionStatus, }), status, @@ -233,6 +245,7 @@ export default { device_mapping, selectedLabels, sortBy, + osSettings, diskEncryptionStatus, bootstrapPackageStatus, }: ILoadHostsOptions): Promise => { @@ -250,6 +263,7 @@ export default { ...reconcileMutuallyInclusiveHostParams({ teamId, macSettingsStatus, + osSettings, }), ...reconcileMutuallyExclusiveHostParams({ label, @@ -264,6 +278,7 @@ export default { osName, osVersion, diskEncryptionStatus, + osSettings, bootstrapPackageStatus, }), }; diff --git a/frontend/utilities/url/index.ts b/frontend/utilities/url/index.ts index d6eb7b6c89..5fcf792bbc 100644 --- a/frontend/utilities/url/index.ts +++ b/frontend/utilities/url/index.ts @@ -1,8 +1,12 @@ import { isEmpty, reduce, omitBy, Dictionary } from "lodash"; -import { DiskEncryptionStatus, BootstrapPackageStatus } from "interfaces/mdm"; import { - DISK_ENCRYPTION_QUERY_PARAM_NAME, + DiskEncryptionStatus, + BootstrapPackageStatus, + MdmProfileStatus, +} from "interfaces/mdm"; +import { + HOSTS_QUERY_PARAMS, MacSettingsStatusQueryParam, } from "services/entities/hosts"; @@ -14,6 +18,7 @@ type FilteredQueryParams = Record; interface IMutuallyInclusiveHostParams { teamId?: number; macSettingsStatus?: MacSettingsStatusQueryParam; + osSettings?: MdmProfileStatus; } interface IMutuallyExclusiveHostParams { @@ -28,6 +33,7 @@ interface IMutuallyExclusiveHostParams { osId?: number; osName?: string; osVersion?: string; + osSettings?: MdmProfileStatus; diskEncryptionStatus?: DiskEncryptionStatus; bootstrapPackageStatus?: BootstrapPackageStatus; } @@ -70,11 +76,17 @@ export const buildQueryStringFromParams = (queryParams: QueryParams) => { export const reconcileMutuallyInclusiveHostParams = ({ teamId, macSettingsStatus, -}: IMutuallyInclusiveHostParams): Record => { + osSettings, +}: IMutuallyInclusiveHostParams) => { // ensure macos_settings filter is always applied in // conjuction with a team_id, 0 (no teams) by default - const reconciled = { macos_settings: macSettingsStatus, team_id: teamId }; + const reconciled: Record = { team_id: teamId }; if (macSettingsStatus) { + reconciled.macos_settings = macSettingsStatus; + reconciled.team_id = teamId ?? 0; + } + if (osSettings) { + reconciled[HOSTS_QUERY_PARAMS.OS_SETTINGS] = osSettings; reconciled.team_id = teamId ?? 0; } return reconciled; @@ -91,6 +103,7 @@ export const reconcileMutuallyExclusiveHostParams = ({ osId, osName, osVersion, + osSettings, diskEncryptionStatus, bootstrapPackageStatus, }: IMutuallyExclusiveHostParams): Record => { @@ -126,8 +139,11 @@ export const reconcileMutuallyExclusiveHostParams = ({ return { os_name: osName, os_version: osVersion }; case !!lowDiskSpaceHosts: return { low_disk_space: lowDiskSpaceHosts }; + case !!osSettings: + return { [HOSTS_QUERY_PARAMS.OS_SETTINGS]: osSettings }; + case !!diskEncryptionStatus: - return { [DISK_ENCRYPTION_QUERY_PARAM_NAME]: diskEncryptionStatus }; + return { [HOSTS_QUERY_PARAMS.DISK_ENCRYPTION]: diskEncryptionStatus }; case !!bootstrapPackageStatus: return { bootstrap_package: bootstrapPackageStatus }; default: