diff --git a/assets/images/icon-action-fail-16x16@2x.png b/assets/images/icon-action-fail-16x16@2x.png new file mode 100644 index 000000000..ce903197b Binary files /dev/null and b/assets/images/icon-action-fail-16x16@2x.png differ diff --git a/changes/issue-5787-device-page-policies b/changes/issue-5787-device-page-policies new file mode 100644 index 000000000..94b2968ee --- /dev/null +++ b/changes/issue-5787-device-page-policies @@ -0,0 +1 @@ +- Added policies to device user page for premium users diff --git a/frontend/components/TableContainer/DataTable/StatusCell/_styles.scss b/frontend/components/TableContainer/DataTable/StatusCell/_styles.scss index e04a81c61..b136339bf 100644 --- a/frontend/components/TableContainer/DataTable/StatusCell/_styles.scss +++ b/frontend/components/TableContainer/DataTable/StatusCell/_styles.scss @@ -38,7 +38,7 @@ } &--no:before { padding-right: 8px; - content: url(../assets/images/icon-exclamation-circle-red-16x16@2x.png); + content: url(../assets/images/icon-action-fail-16x16@2x.png); transform: translate(-4px, -6px) scale(0.5); } &--indeterminate { diff --git a/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx b/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx index 379664b3e..0aeb49dc7 100644 --- a/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx +++ b/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx @@ -7,10 +7,12 @@ import { Tab, Tabs, TabList, TabPanel } from "react-tabs"; import classnames from "classnames"; import { pick } from "lodash"; +import { AppContext, IAppContext } from "context/app"; import { NotificationContext } from "context/notification"; import deviceUserAPI from "services/entities/device_user"; import { IHost, IDeviceMappingResponse } from "interfaces/host"; import { ISoftware } from "interfaces/software"; +import { IHostPolicy } from "interfaces/policy"; import PageError from "components/DataError"; // @ts-ignore import OrgLogoIcon from "components/icons/OrgLogoIcon"; @@ -22,10 +24,12 @@ import { normalizeEmptyValues, wrapFleetHelper } from "utilities/helpers"; import HostSummaryCard from "../cards/HostSummary"; import AboutCard from "../cards/About"; import SoftwareCard from "../cards/Software"; +import PoliciesCard from "../cards/Policies"; import InfoModal from "./InfoModal"; import InfoIcon from "../../../../../assets/images/icon-info-purple-14x14@2x.png"; import FleetIcon from "../../../../../assets/images/fleet-avatar-24x24@2x.png"; +import PolicyDetailsModal from "../cards/Policies/HostPoliciesTable/PolicyDetailsModal"; const baseClass = "device-user"; @@ -43,6 +47,7 @@ const DeviceUserPage = ({ }: IDeviceUserPageProps): JSX.Element => { const deviceAuthToken = device_auth_token; const { renderFlash } = useContext(NotificationContext); + const { isPremiumTier } = useContext(AppContext); const handlePageError = useErrorHandler(); const [showInfoModal, setShowInfoModal] = useState(false); @@ -52,6 +57,12 @@ const DeviceUserPage = ({ const [hostSoftware, setHostSoftware] = useState([]); const [host, setHost] = useState(); const [orgLogoURL, setOrgLogoURL] = useState(""); + const [selectedPolicy, setSelectedPolicy] = useState( + null + ); + const [showPolicyDetailsModal, setShowPolicyDetailsModal] = useState( + false + ); const { data: deviceMapping, refetch: refetchDeviceMapping } = useQuery( ["deviceMapping", deviceAuthToken], @@ -88,15 +99,7 @@ const DeviceUserPage = ({ onSuccess: (returnedHost) => { setShowRefetchSpinner(returnedHost.host.refetch_requested); if (returnedHost.host.refetch_requested) { - // If the API reports that a Fleet refetch request is pending, we want to check back for fresh - // host details. Here we set a one second timeout and poll the API again using - // fullyReloadHost. We will repeat this process with each onSuccess cycle for a total of - // 60 seconds or until the API reports that the Fleet refetch request has been resolved - // or that the host has gone offline. if (!refetchStartTime) { - // If our 60 second timer wasn't already started (e.g., if a refetch was pending when - // the first page loads), we start it now if the host is online. If the host is offline, - // we skip the refetch on page load. if (returnedHost.host.status === "online") { setRefetchStartTime(Date.now()); setTimeout(() => { @@ -129,7 +132,7 @@ const DeviceUserPage = ({ setShowRefetchSpinner(false); } } - return; // exit early because refectch is pending so we can avoid unecessary steps below + return; } setHostSoftware(returnedHost.host.software); setHost(returnedHost.host); @@ -170,11 +173,20 @@ const DeviceUserPage = ({ setShowInfoModal(!showInfoModal); }, [showInfoModal, setShowInfoModal]); + const togglePolicyDetailsModal = useCallback( + (policy: IHostPolicy) => { + setShowPolicyDetailsModal(!showPolicyDetailsModal); + setSelectedPolicy(policy); + }, + [showPolicyDetailsModal, setShowPolicyDetailsModal, setSelectedPolicy] + ); + const onCancelPolicyDetailsModal = useCallback(() => { + setShowPolicyDetailsModal(!showPolicyDetailsModal); + setSelectedPolicy(null); + }, [showPolicyDetailsModal, setShowPolicyDetailsModal, setSelectedPolicy]); + const onRefetchHost = async () => { if (host) { - // Once the user clicks to refetch, the refetch loading spinner should continue spinning - // unless there is an error. The spinner state is also controlled in the fullyReloadHost - // method. setShowRefetchSpinner(true); try { await deviceUserAPI.refetch(deviceAuthToken).then(() => { @@ -219,6 +231,7 @@ const DeviceUserPage = ({ showRefetchSpinner={showRefetchSpinner} onRefetchHost={onRefetchHost} renderActionButtons={renderActionButtons} + isPremiumTier deviceUser /> @@ -226,6 +239,18 @@ const DeviceUserPage = ({ Details Software + {isPremiumTier && ( + +
+ {titleData.issues.failing_policies_count > 0 && ( + + {titleData.issues.failing_policies_count} + + )} + Policies +
+
+ )}
+ {isPremiumTier && ( + + + + )}
{showInfoModal && } )} + {!!host && showPolicyDetailsModal && ( + + )} ); }; diff --git a/frontend/pages/hosts/details/DeviceUserPage/_styles.scss b/frontend/pages/hosts/details/DeviceUserPage/_styles.scss index b4f04fa90..5971488d8 100644 --- a/frontend/pages/hosts/details/DeviceUserPage/_styles.scss +++ b/frontend/pages/hosts/details/DeviceUserPage/_styles.scss @@ -275,6 +275,20 @@ .section { margin-top: $pad-medium; } + + .fail-count { + background-color: $ui-error; + border-radius: 45%; + color: $core-white; + display: inline-block; + padding: 0 10px; + font-size: 0.75rem; + font-weight: bold; + line-height: 1.75; + margin: 0 8px -8px 0; + position: relative; + top: -1px; + } } .col-50 { diff --git a/frontend/pages/hosts/details/cards/HostSummary/HostSummary.tsx b/frontend/pages/hosts/details/cards/HostSummary/HostSummary.tsx index b0c176ea1..488bb4dcf 100644 --- a/frontend/pages/hosts/details/cards/HostSummary/HostSummary.tsx +++ b/frontend/pages/hosts/details/cards/HostSummary/HostSummary.tsx @@ -158,9 +158,9 @@ const HostSummary = ({ {titleData.status} - {!deviceUser && - titleData.issues?.total_issues_count > 0 && - renderIssues()} + {titleData.issues?.total_issues_count > 0 && deviceUser + ? isPremiumTier && renderIssues() + : renderIssues()} {!deviceUser && isPremiumTier && renderHostTeam()}
Disk space diff --git a/frontend/pages/hosts/details/cards/Policies/HostPoliciesTable/HostPoliciesTableConfig.tsx b/frontend/pages/hosts/details/cards/Policies/HostPoliciesTable/HostPoliciesTableConfig.tsx index afe30ec2a..f14bb2d25 100644 --- a/frontend/pages/hosts/details/cards/Policies/HostPoliciesTable/HostPoliciesTableConfig.tsx +++ b/frontend/pages/hosts/details/cards/Policies/HostPoliciesTable/HostPoliciesTableConfig.tsx @@ -7,7 +7,6 @@ import { IHostPolicy } from "interfaces/policy"; import { PolicyResponse } from "utilities/constants"; import Chevron from "../../../../../../../assets/images/icon-chevron-right-9x6@2x.png"; -import ArrowIcon from "../../../../../../../assets/images/icon-arrow-right-vibrant-blue-10x18@2x.png"; const TAGGED_TEMPLATES = { hostsByPolicyRoute: (policyId: number, policyResponse: PolicyResponse) => { diff --git a/frontend/pages/hosts/details/cards/Policies/HostPoliciesTable/PolicyDetailsModal/PolicyDetailsModal.tsx b/frontend/pages/hosts/details/cards/Policies/HostPoliciesTable/PolicyDetailsModal/PolicyDetailsModal.tsx index 4fbbc405d..a50b19332 100644 --- a/frontend/pages/hosts/details/cards/Policies/HostPoliciesTable/PolicyDetailsModal/PolicyDetailsModal.tsx +++ b/frontend/pages/hosts/details/cards/Policies/HostPoliciesTable/PolicyDetailsModal/PolicyDetailsModal.tsx @@ -26,8 +26,7 @@ const PolicyDetailsModal = ({ {policy?.resolution && (
Resolve: -
- {policy?.resolution} +

{policy?.resolution}

)}
diff --git a/frontend/pages/hosts/details/cards/Policies/HostPoliciesTable/PolicyDetailsModal/_styles.scss b/frontend/pages/hosts/details/cards/Policies/HostPoliciesTable/PolicyDetailsModal/_styles.scss index 8b028d5f8..bec932df7 100644 --- a/frontend/pages/hosts/details/cards/Policies/HostPoliciesTable/PolicyDetailsModal/_styles.scss +++ b/frontend/pages/hosts/details/cards/Policies/HostPoliciesTable/PolicyDetailsModal/_styles.scss @@ -18,4 +18,8 @@ &__btn { margin-left: 12px; } + + .policy-details-modal__resolution { + overflow: hidden; + } } diff --git a/frontend/pages/hosts/details/cards/Policies/HostPoliciesTable/PolicyFailingCount/PolicyFailingCount.tsx b/frontend/pages/hosts/details/cards/Policies/HostPoliciesTable/PolicyFailingCount/PolicyFailingCount.tsx index 126e8108a..543a93a6a 100644 --- a/frontend/pages/hosts/details/cards/Policies/HostPoliciesTable/PolicyFailingCount/PolicyFailingCount.tsx +++ b/frontend/pages/hosts/details/cards/Policies/HostPoliciesTable/PolicyFailingCount/PolicyFailingCount.tsx @@ -22,7 +22,8 @@ const PolicyFailingCount = (policyProps: { {failCount === 1 ? " 1 policy" : ` ${failCount} policies`}

- Click a policy below to see steps for resolving the failure + Click a policy below to see if there are steps you can take to resolve + the issue {failCount > 1 ? "s" : ""}.

diff --git a/frontend/pages/hosts/details/cards/Policies/HostPoliciesTable/PolicyFailingCount/_styles.scss b/frontend/pages/hosts/details/cards/Policies/HostPoliciesTable/PolicyFailingCount/_styles.scss index 6777bd900..edeb27c7a 100644 --- a/frontend/pages/hosts/details/cards/Policies/HostPoliciesTable/PolicyFailingCount/_styles.scss +++ b/frontend/pages/hosts/details/cards/Policies/HostPoliciesTable/PolicyFailingCount/_styles.scss @@ -25,7 +25,6 @@ display: flex; align-content: center; align-items: center; - font-size: $small; font-weight: $bold; } } diff --git a/frontend/pages/hosts/details/cards/Policies/Policies.tsx b/frontend/pages/hosts/details/cards/Policies/Policies.tsx index 6e6b8304a..d2bfa4cf3 100644 --- a/frontend/pages/hosts/details/cards/Policies/Policies.tsx +++ b/frontend/pages/hosts/details/cards/Policies/Policies.tsx @@ -13,12 +13,14 @@ import { isValidPolicyResponse } from "../../../ManageHostsPage/helpers"; interface IPoliciesProps { policies: IHostPolicy[]; isLoading: boolean; + deviceUser?: boolean; togglePolicyDetailsModal: (policy: IHostPolicy) => void; } const Policies = ({ policies, isLoading, + deviceUser, togglePolicyDetailsModal, }: IPoliciesProps): JSX.Element => { if (policies.length === 0) { @@ -26,9 +28,13 @@ const Policies = ({

Policies

- No policies are checked for this host. + + No policies are checked{" "} + {deviceUser ? `on your device` : `for this host`}. +

- Expecting to see policies? Try selecting “Refetch” to ask this host + Expecting to see policies? Try selecting “Refetch” to ask{" "} + {deviceUser ? `your device ` : `this host `} to report new vitals.

@@ -36,6 +42,8 @@ const Policies = ({ ); } + console.log("policies: ", policies); + const tableHeaders = generatePolicyTableHeaders(togglePolicyDetailsModal); const noResponses: IHostPolicy[] = policies.filter( diff --git a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesListWrapper/PoliciesTableConfig.tsx b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesListWrapper/PoliciesTableConfig.tsx index 38f77a4e9..20bec0f1f 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesListWrapper/PoliciesTableConfig.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesListWrapper/PoliciesTableConfig.tsx @@ -13,7 +13,7 @@ import PATHS from "router/paths"; import sortUtils from "utilities/sort"; import { PolicyResponse } from "utilities/constants"; import PassIcon from "../../../../../../assets/images/icon-check-circle-green-16x16@2x.png"; -import FailIcon from "../../../../../../assets/images/icon-exclamation-circle-red-16x16@2x.png"; +import FailIcon from "../../../../../../assets/images/icon-action-fail-16x16@2x.png"; // TODO functions for paths math e.g., path={PATHS.MANAGE_HOSTS + getParams(cellProps.row.original)} diff --git a/frontend/pages/policies/PolicyPage/components/PolicyQueriesListWrapper/PolicyQueriesTableConfig.tsx b/frontend/pages/policies/PolicyPage/components/PolicyQueriesListWrapper/PolicyQueriesTableConfig.tsx index 676435e8a..f72b1ff6c 100644 --- a/frontend/pages/policies/PolicyPage/components/PolicyQueriesListWrapper/PolicyQueriesTableConfig.tsx +++ b/frontend/pages/policies/PolicyPage/components/PolicyQueriesListWrapper/PolicyQueriesTableConfig.tsx @@ -12,7 +12,7 @@ import HeaderCell from "components/TableContainer/DataTable/HeaderCell/HeaderCel import { IHostPolicyQuery } from "interfaces/host"; import sortUtils from "utilities/sort"; import PassIcon from "../../../../../../assets/images/icon-check-circle-green-16x16@2x.png"; -import FailIcon from "../../../../../../assets/images/icon-exclamation-circle-red-16x16@2x.png"; +import FailIcon from "../../../../../../assets/images/icon-action-fail-16x16@2x.png"; interface IHeaderProps { column: ColumnInstance & IDataColumn; diff --git a/frontend/styles/var/tables.scss b/frontend/styles/var/tables.scss index c03d8de21..1cd58ab8c 100644 --- a/frontend/styles/var/tables.scss +++ b/frontend/styles/var/tables.scss @@ -1,3 +1,3 @@ -$col-sm: 62px; // column size will "hug" its contents -$col-md: 202px; // column size not including 24px padding on left and right (total width is 250px) -$col-lg: 352px; // column size not including 24px padding on left and right (total width is 250px) +$col-sm: 102px; // column size will "hug" its contents +$col-md: 252px; // column size not including 24px padding on left and right (total width is 300px) +$col-lg: 352px; // column size not including 24px padding on left and right (total width is 400px)