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 {
|
||||
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 {
|
||||
|
@ -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<boolean>(false);
|
||||
@ -52,6 +57,12 @@ const DeviceUserPage = ({
|
||||
const [hostSoftware, setHostSoftware] = useState<ISoftware[]>([]);
|
||||
const [host, setHost] = useState<IHost | null>();
|
||||
const [orgLogoURL, setOrgLogoURL] = useState<string>("");
|
||||
const [selectedPolicy, setSelectedPolicy] = useState<IHostPolicy | null>(
|
||||
null
|
||||
);
|
||||
const [showPolicyDetailsModal, setShowPolicyDetailsModal] = useState<boolean>(
|
||||
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
|
||||
/>
|
||||
<TabsWrapper>
|
||||
@ -226,6 +239,18 @@ const DeviceUserPage = ({
|
||||
<TabList>
|
||||
<Tab>Details</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>
|
||||
<TabPanel>
|
||||
<AboutCard
|
||||
@ -242,11 +267,27 @@ const DeviceUserPage = ({
|
||||
deviceUser
|
||||
/>
|
||||
</TabPanel>
|
||||
{isPremiumTier && (
|
||||
<TabPanel>
|
||||
<PoliciesCard
|
||||
policies={host?.policies || []}
|
||||
isLoading={isLoadingHost}
|
||||
deviceUser
|
||||
togglePolicyDetailsModal={togglePolicyDetailsModal}
|
||||
/>
|
||||
</TabPanel>
|
||||
)}
|
||||
</Tabs>
|
||||
</TabsWrapper>
|
||||
{showInfoModal && <InfoModal onCancel={toggleInfoModal} />}
|
||||
</div>
|
||||
)}
|
||||
{!!host && showPolicyDetailsModal && (
|
||||
<PolicyDetailsModal
|
||||
onCancel={onCancelPolicyDetailsModal}
|
||||
policy={selectedPolicy}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -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 {
|
||||
|
@ -158,9 +158,9 @@ const HostSummary = ({
|
||||
{titleData.status}
|
||||
</span>
|
||||
</div>
|
||||
{!deviceUser &&
|
||||
titleData.issues?.total_issues_count > 0 &&
|
||||
renderIssues()}
|
||||
{titleData.issues?.total_issues_count > 0 && deviceUser
|
||||
? isPremiumTier && renderIssues()
|
||||
: renderIssues()}
|
||||
{!deviceUser && isPremiumTier && renderHostTeam()}
|
||||
<div className="info-flex__item info-flex__item--title">
|
||||
<span className="info-flex__header">Disk space</span>
|
||||
|
@ -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) => {
|
||||
|
@ -26,8 +26,7 @@ const PolicyDetailsModal = ({
|
||||
{policy?.resolution && (
|
||||
<div className={`${baseClass}__resolution`}>
|
||||
<span className={`${baseClass}__resolve-header`}> Resolve:</span>
|
||||
<br />
|
||||
{policy?.resolution}
|
||||
<p>{policy?.resolution}</p>
|
||||
</div>
|
||||
)}
|
||||
<div className={`${baseClass}__btn-wrap`}>
|
||||
|
@ -18,4 +18,8 @@
|
||||
&__btn {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.policy-details-modal__resolution {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,8 @@ const PolicyFailingCount = (policyProps: {
|
||||
{failCount === 1 ? " 1 policy" : ` ${failCount} policies`}
|
||||
</div>
|
||||
<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" : ""}.
|
||||
</p>
|
||||
</div>
|
||||
|
@ -25,7 +25,6 @@
|
||||
display: flex;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
font-size: $small;
|
||||
font-weight: $bold;
|
||||
}
|
||||
}
|
||||
|
@ -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 = ({
|
||||
<div className="section section--policies">
|
||||
<p className="section__header">Policies</p>
|
||||
<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>
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
@ -36,6 +42,8 @@ const Policies = ({
|
||||
);
|
||||
}
|
||||
|
||||
console.log("policies: ", policies);
|
||||
|
||||
const tableHeaders = generatePolicyTableHeaders(togglePolicyDetailsModal);
|
||||
const noResponses: IHostPolicy[] =
|
||||
policies.filter(
|
||||
|
@ -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)}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user