mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 08:55:24 +00:00
Add policies to my device page (#5787)
This commit is contained in:
parent
894fa22c71
commit
c52604cfb7
BIN
assets/images/icon-action-fail-16x16@2x.png
Normal file
BIN
assets/images/icon-action-fail-16x16@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 680 B |
1
changes/issue-5787-device-page-policies
Normal file
1
changes/issue-5787-device-page-policies
Normal file
@ -0,0 +1 @@
|
|||||||
|
- Added policies to device user page for premium users
|
@ -38,7 +38,7 @@
|
|||||||
}
|
}
|
||||||
&--no:before {
|
&--no:before {
|
||||||
padding-right: 8px;
|
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);
|
transform: translate(-4px, -6px) scale(0.5);
|
||||||
}
|
}
|
||||||
&--indeterminate {
|
&--indeterminate {
|
||||||
|
@ -7,10 +7,12 @@ import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
|
|||||||
import classnames from "classnames";
|
import classnames from "classnames";
|
||||||
import { pick } from "lodash";
|
import { pick } from "lodash";
|
||||||
|
|
||||||
|
import { AppContext, IAppContext } from "context/app";
|
||||||
import { NotificationContext } from "context/notification";
|
import { NotificationContext } from "context/notification";
|
||||||
import deviceUserAPI from "services/entities/device_user";
|
import deviceUserAPI from "services/entities/device_user";
|
||||||
import { IHost, IDeviceMappingResponse } from "interfaces/host";
|
import { IHost, IDeviceMappingResponse } from "interfaces/host";
|
||||||
import { ISoftware } from "interfaces/software";
|
import { ISoftware } from "interfaces/software";
|
||||||
|
import { IHostPolicy } from "interfaces/policy";
|
||||||
import PageError from "components/DataError";
|
import PageError from "components/DataError";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import OrgLogoIcon from "components/icons/OrgLogoIcon";
|
import OrgLogoIcon from "components/icons/OrgLogoIcon";
|
||||||
@ -22,10 +24,12 @@ import { normalizeEmptyValues, wrapFleetHelper } from "utilities/helpers";
|
|||||||
import HostSummaryCard from "../cards/HostSummary";
|
import HostSummaryCard from "../cards/HostSummary";
|
||||||
import AboutCard from "../cards/About";
|
import AboutCard from "../cards/About";
|
||||||
import SoftwareCard from "../cards/Software";
|
import SoftwareCard from "../cards/Software";
|
||||||
|
import PoliciesCard from "../cards/Policies";
|
||||||
import InfoModal from "./InfoModal";
|
import InfoModal from "./InfoModal";
|
||||||
|
|
||||||
import InfoIcon from "../../../../../assets/images/icon-info-purple-14x14@2x.png";
|
import InfoIcon from "../../../../../assets/images/icon-info-purple-14x14@2x.png";
|
||||||
import FleetIcon from "../../../../../assets/images/fleet-avatar-24x24@2x.png";
|
import FleetIcon from "../../../../../assets/images/fleet-avatar-24x24@2x.png";
|
||||||
|
import PolicyDetailsModal from "../cards/Policies/HostPoliciesTable/PolicyDetailsModal";
|
||||||
|
|
||||||
const baseClass = "device-user";
|
const baseClass = "device-user";
|
||||||
|
|
||||||
@ -43,6 +47,7 @@ const DeviceUserPage = ({
|
|||||||
}: IDeviceUserPageProps): JSX.Element => {
|
}: IDeviceUserPageProps): JSX.Element => {
|
||||||
const deviceAuthToken = device_auth_token;
|
const deviceAuthToken = device_auth_token;
|
||||||
const { renderFlash } = useContext(NotificationContext);
|
const { renderFlash } = useContext(NotificationContext);
|
||||||
|
const { isPremiumTier } = useContext(AppContext);
|
||||||
const handlePageError = useErrorHandler();
|
const handlePageError = useErrorHandler();
|
||||||
|
|
||||||
const [showInfoModal, setShowInfoModal] = useState<boolean>(false);
|
const [showInfoModal, setShowInfoModal] = useState<boolean>(false);
|
||||||
@ -52,6 +57,12 @@ const DeviceUserPage = ({
|
|||||||
const [hostSoftware, setHostSoftware] = useState<ISoftware[]>([]);
|
const [hostSoftware, setHostSoftware] = useState<ISoftware[]>([]);
|
||||||
const [host, setHost] = useState<IHost | null>();
|
const [host, setHost] = useState<IHost | null>();
|
||||||
const [orgLogoURL, setOrgLogoURL] = useState<string>("");
|
const [orgLogoURL, setOrgLogoURL] = useState<string>("");
|
||||||
|
const [selectedPolicy, setSelectedPolicy] = useState<IHostPolicy | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
const [showPolicyDetailsModal, setShowPolicyDetailsModal] = useState<boolean>(
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
const { data: deviceMapping, refetch: refetchDeviceMapping } = useQuery(
|
const { data: deviceMapping, refetch: refetchDeviceMapping } = useQuery(
|
||||||
["deviceMapping", deviceAuthToken],
|
["deviceMapping", deviceAuthToken],
|
||||||
@ -88,15 +99,7 @@ const DeviceUserPage = ({
|
|||||||
onSuccess: (returnedHost) => {
|
onSuccess: (returnedHost) => {
|
||||||
setShowRefetchSpinner(returnedHost.host.refetch_requested);
|
setShowRefetchSpinner(returnedHost.host.refetch_requested);
|
||||||
if (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 (!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") {
|
if (returnedHost.host.status === "online") {
|
||||||
setRefetchStartTime(Date.now());
|
setRefetchStartTime(Date.now());
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -129,7 +132,7 @@ const DeviceUserPage = ({
|
|||||||
setShowRefetchSpinner(false);
|
setShowRefetchSpinner(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return; // exit early because refectch is pending so we can avoid unecessary steps below
|
return;
|
||||||
}
|
}
|
||||||
setHostSoftware(returnedHost.host.software);
|
setHostSoftware(returnedHost.host.software);
|
||||||
setHost(returnedHost.host);
|
setHost(returnedHost.host);
|
||||||
@ -170,11 +173,20 @@ const DeviceUserPage = ({
|
|||||||
setShowInfoModal(!showInfoModal);
|
setShowInfoModal(!showInfoModal);
|
||||||
}, [showInfoModal, setShowInfoModal]);
|
}, [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 () => {
|
const onRefetchHost = async () => {
|
||||||
if (host) {
|
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);
|
setShowRefetchSpinner(true);
|
||||||
try {
|
try {
|
||||||
await deviceUserAPI.refetch(deviceAuthToken).then(() => {
|
await deviceUserAPI.refetch(deviceAuthToken).then(() => {
|
||||||
@ -219,6 +231,7 @@ const DeviceUserPage = ({
|
|||||||
showRefetchSpinner={showRefetchSpinner}
|
showRefetchSpinner={showRefetchSpinner}
|
||||||
onRefetchHost={onRefetchHost}
|
onRefetchHost={onRefetchHost}
|
||||||
renderActionButtons={renderActionButtons}
|
renderActionButtons={renderActionButtons}
|
||||||
|
isPremiumTier
|
||||||
deviceUser
|
deviceUser
|
||||||
/>
|
/>
|
||||||
<TabsWrapper>
|
<TabsWrapper>
|
||||||
@ -226,6 +239,18 @@ const DeviceUserPage = ({
|
|||||||
<TabList>
|
<TabList>
|
||||||
<Tab>Details</Tab>
|
<Tab>Details</Tab>
|
||||||
<Tab>Software</Tab>
|
<Tab>Software</Tab>
|
||||||
|
{isPremiumTier && (
|
||||||
|
<Tab>
|
||||||
|
<div>
|
||||||
|
{titleData.issues.failing_policies_count > 0 && (
|
||||||
|
<span className="fail-count">
|
||||||
|
{titleData.issues.failing_policies_count}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
Policies
|
||||||
|
</div>
|
||||||
|
</Tab>
|
||||||
|
)}
|
||||||
</TabList>
|
</TabList>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<AboutCard
|
<AboutCard
|
||||||
@ -242,11 +267,27 @@ const DeviceUserPage = ({
|
|||||||
deviceUser
|
deviceUser
|
||||||
/>
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
{isPremiumTier && (
|
||||||
|
<TabPanel>
|
||||||
|
<PoliciesCard
|
||||||
|
policies={host?.policies || []}
|
||||||
|
isLoading={isLoadingHost}
|
||||||
|
deviceUser
|
||||||
|
togglePolicyDetailsModal={togglePolicyDetailsModal}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
)}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</TabsWrapper>
|
</TabsWrapper>
|
||||||
{showInfoModal && <InfoModal onCancel={toggleInfoModal} />}
|
{showInfoModal && <InfoModal onCancel={toggleInfoModal} />}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{!!host && showPolicyDetailsModal && (
|
||||||
|
<PolicyDetailsModal
|
||||||
|
onCancel={onCancelPolicyDetailsModal}
|
||||||
|
policy={selectedPolicy}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -275,6 +275,20 @@
|
|||||||
.section {
|
.section {
|
||||||
margin-top: $pad-medium;
|
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 {
|
.col-50 {
|
||||||
|
@ -158,9 +158,9 @@ const HostSummary = ({
|
|||||||
{titleData.status}
|
{titleData.status}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{!deviceUser &&
|
{titleData.issues?.total_issues_count > 0 && deviceUser
|
||||||
titleData.issues?.total_issues_count > 0 &&
|
? isPremiumTier && renderIssues()
|
||||||
renderIssues()}
|
: renderIssues()}
|
||||||
{!deviceUser && isPremiumTier && renderHostTeam()}
|
{!deviceUser && isPremiumTier && renderHostTeam()}
|
||||||
<div className="info-flex__item info-flex__item--title">
|
<div className="info-flex__item info-flex__item--title">
|
||||||
<span className="info-flex__header">Disk space</span>
|
<span className="info-flex__header">Disk space</span>
|
||||||
|
@ -7,7 +7,6 @@ import { IHostPolicy } from "interfaces/policy";
|
|||||||
import { PolicyResponse } from "utilities/constants";
|
import { PolicyResponse } from "utilities/constants";
|
||||||
|
|
||||||
import Chevron from "../../../../../../../assets/images/icon-chevron-right-9x6@2x.png";
|
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 = {
|
const TAGGED_TEMPLATES = {
|
||||||
hostsByPolicyRoute: (policyId: number, policyResponse: PolicyResponse) => {
|
hostsByPolicyRoute: (policyId: number, policyResponse: PolicyResponse) => {
|
||||||
|
@ -26,8 +26,7 @@ const PolicyDetailsModal = ({
|
|||||||
{policy?.resolution && (
|
{policy?.resolution && (
|
||||||
<div className={`${baseClass}__resolution`}>
|
<div className={`${baseClass}__resolution`}>
|
||||||
<span className={`${baseClass}__resolve-header`}> Resolve:</span>
|
<span className={`${baseClass}__resolve-header`}> Resolve:</span>
|
||||||
<br />
|
<p>{policy?.resolution}</p>
|
||||||
{policy?.resolution}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={`${baseClass}__btn-wrap`}>
|
<div className={`${baseClass}__btn-wrap`}>
|
||||||
|
@ -18,4 +18,8 @@
|
|||||||
&__btn {
|
&__btn {
|
||||||
margin-left: 12px;
|
margin-left: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.policy-details-modal__resolution {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,8 @@ const PolicyFailingCount = (policyProps: {
|
|||||||
{failCount === 1 ? " 1 policy" : ` ${failCount} policies`}
|
{failCount === 1 ? " 1 policy" : ` ${failCount} policies`}
|
||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
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" : ""}.
|
{failCount > 1 ? "s" : ""}.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,7 +25,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: $small;
|
|
||||||
font-weight: $bold;
|
font-weight: $bold;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,12 +13,14 @@ import { isValidPolicyResponse } from "../../../ManageHostsPage/helpers";
|
|||||||
interface IPoliciesProps {
|
interface IPoliciesProps {
|
||||||
policies: IHostPolicy[];
|
policies: IHostPolicy[];
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
deviceUser?: boolean;
|
||||||
togglePolicyDetailsModal: (policy: IHostPolicy) => void;
|
togglePolicyDetailsModal: (policy: IHostPolicy) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Policies = ({
|
const Policies = ({
|
||||||
policies,
|
policies,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
deviceUser,
|
||||||
togglePolicyDetailsModal,
|
togglePolicyDetailsModal,
|
||||||
}: IPoliciesProps): JSX.Element => {
|
}: IPoliciesProps): JSX.Element => {
|
||||||
if (policies.length === 0) {
|
if (policies.length === 0) {
|
||||||
@ -26,9 +28,13 @@ const Policies = ({
|
|||||||
<div className="section section--policies">
|
<div className="section section--policies">
|
||||||
<p className="section__header">Policies</p>
|
<p className="section__header">Policies</p>
|
||||||
<div className="results__data">
|
<div className="results__data">
|
||||||
<b>No policies are checked for this host.</b>
|
<b>
|
||||||
|
No policies are checked{" "}
|
||||||
|
{deviceUser ? `on your device` : `for this host`}.
|
||||||
|
</b>
|
||||||
<p>
|
<p>
|
||||||
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.
|
to report new vitals.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -36,6 +42,8 @@ const Policies = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("policies: ", policies);
|
||||||
|
|
||||||
const tableHeaders = generatePolicyTableHeaders(togglePolicyDetailsModal);
|
const tableHeaders = generatePolicyTableHeaders(togglePolicyDetailsModal);
|
||||||
const noResponses: IHostPolicy[] =
|
const noResponses: IHostPolicy[] =
|
||||||
policies.filter(
|
policies.filter(
|
||||||
|
@ -13,7 +13,7 @@ import PATHS from "router/paths";
|
|||||||
import sortUtils from "utilities/sort";
|
import sortUtils from "utilities/sort";
|
||||||
import { PolicyResponse } from "utilities/constants";
|
import { PolicyResponse } from "utilities/constants";
|
||||||
import PassIcon from "../../../../../../assets/images/icon-check-circle-green-16x16@2x.png";
|
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)}
|
// TODO functions for paths math e.g., path={PATHS.MANAGE_HOSTS + getParams(cellProps.row.original)}
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ import HeaderCell from "components/TableContainer/DataTable/HeaderCell/HeaderCel
|
|||||||
import { IHostPolicyQuery } from "interfaces/host";
|
import { IHostPolicyQuery } from "interfaces/host";
|
||||||
import sortUtils from "utilities/sort";
|
import sortUtils from "utilities/sort";
|
||||||
import PassIcon from "../../../../../../assets/images/icon-check-circle-green-16x16@2x.png";
|
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 {
|
interface IHeaderProps {
|
||||||
column: ColumnInstance & IDataColumn;
|
column: ColumnInstance & IDataColumn;
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
$col-sm: 62px; // column size will "hug" its contents
|
$col-sm: 102px; // column size will "hug" its contents
|
||||||
$col-md: 202px; // column size not including 24px padding on left and right (total width is 250px)
|
$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 250px)
|
$col-lg: 352px; // column size not including 24px padding on left and right (total width is 400px)
|
||||||
|
Loading…
Reference in New Issue
Block a user