mirror of
https://github.com/fleetdm/fleet
synced 2026-05-22 16:39:01 +00:00
Fleet UI: Host details page > policies improvements (#19483)
This commit is contained in:
parent
05eb338561
commit
99f431f8d7
7 changed files with 88 additions and 29 deletions
1
changes/conf-6385-host-policy-table-fixes
Normal file
1
changes/conf-6385-host-policy-table-fixes
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Host policy table can be sortable by response and View all host link preserves the team
|
||||
|
|
@ -471,6 +471,7 @@ const DeviceUserPage = ({
|
|||
deviceUser
|
||||
togglePolicyDetailsModal={togglePolicyDetailsModal}
|
||||
hostPlatform={host?.platform || ""}
|
||||
router={router}
|
||||
/>
|
||||
</TabPanel>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -903,6 +903,8 @@ const HostDetailsPage = ({
|
|||
isLoading={isLoadingHost}
|
||||
togglePolicyDetailsModal={togglePolicyDetailsModal}
|
||||
hostPlatform={host.platform}
|
||||
router={router}
|
||||
currentTeamId={currentTeam?.id}
|
||||
/>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
import React from "react";
|
||||
import React, { useCallback } from "react";
|
||||
import { InjectedRouter } from "react-router";
|
||||
import { Row } from "react-table";
|
||||
import { noop } from "lodash";
|
||||
|
||||
import { IHostPolicy } from "interfaces/policy";
|
||||
import { SUPPORT_LINK } from "utilities/constants";
|
||||
import { PolicyResponse, SUPPORT_LINK } from "utilities/constants";
|
||||
import { createHostsByPolicyPath } from "utilities/helpers";
|
||||
import TableContainer from "components/TableContainer";
|
||||
import EmptyTable from "components/EmptyTable";
|
||||
import Card from "components/Card";
|
||||
|
|
@ -21,6 +25,15 @@ interface IPoliciesProps {
|
|||
deviceUser?: boolean;
|
||||
togglePolicyDetailsModal: (policy: IHostPolicy) => void;
|
||||
hostPlatform: string;
|
||||
router: InjectedRouter;
|
||||
currentTeamId?: number;
|
||||
}
|
||||
|
||||
interface IHostPoliciesRowProps extends Row {
|
||||
original: {
|
||||
id: number;
|
||||
response: "pass" | "fail";
|
||||
};
|
||||
}
|
||||
|
||||
const Policies = ({
|
||||
|
|
@ -29,8 +42,13 @@ const Policies = ({
|
|||
deviceUser,
|
||||
togglePolicyDetailsModal,
|
||||
hostPlatform,
|
||||
router,
|
||||
currentTeamId,
|
||||
}: IPoliciesProps): JSX.Element => {
|
||||
const tableHeaders = generatePolicyTableHeaders(togglePolicyDetailsModal);
|
||||
const tableHeaders = generatePolicyTableHeaders(
|
||||
togglePolicyDetailsModal,
|
||||
currentTeamId
|
||||
);
|
||||
if (deviceUser) {
|
||||
// Remove view all hosts link
|
||||
tableHeaders.pop();
|
||||
|
|
@ -38,6 +56,23 @@ const Policies = ({
|
|||
const failingResponses: IHostPolicy[] =
|
||||
policies.filter((policy: IHostPolicy) => policy.response === "fail") || [];
|
||||
|
||||
const onClickRow = useCallback(
|
||||
(row: IHostPoliciesRowProps) => {
|
||||
const { id: policyId, response: policyResponse } = row.original;
|
||||
|
||||
const viewAllHostPath = createHostsByPolicyPath(
|
||||
policyId,
|
||||
policyResponse === "pass"
|
||||
? PolicyResponse.PASSING
|
||||
: PolicyResponse.FAILING,
|
||||
currentTeamId
|
||||
);
|
||||
|
||||
router.push(viewAllHostPath);
|
||||
},
|
||||
[router]
|
||||
);
|
||||
|
||||
const renderHostPolicies = () => {
|
||||
if (hostPlatform === "ios" || hostPlatform === "ipados") {
|
||||
return (
|
||||
|
|
@ -83,14 +118,16 @@ const Policies = ({
|
|||
columnConfigs={tableHeaders}
|
||||
data={generatePolicyDataSet(policies)}
|
||||
isLoading={isLoading}
|
||||
manualSortBy
|
||||
resultsTitle="policy items"
|
||||
defaultSortHeader="response"
|
||||
defaultSortDirection="asc"
|
||||
resultsTitle="policies"
|
||||
emptyComponent={() => <></>}
|
||||
showMarkAllPages={false}
|
||||
isAllPagesSelected={false}
|
||||
disablePagination
|
||||
disableCount
|
||||
disableMultiRowSelect
|
||||
disableMultiRowSelect={!deviceUser} // Removes hover/click state if deviceUser
|
||||
isClientSidePagination
|
||||
onClickRow={deviceUser ? noop : onClickRow}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
import React from "react";
|
||||
import StatusIndicatorWithIcon from "components/StatusIndicatorWithIcon";
|
||||
import Button from "components/buttons/Button";
|
||||
|
||||
import { IHostPolicy } from "interfaces/policy";
|
||||
import { PolicyResponse, DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants";
|
||||
|
||||
import StatusIndicatorWithIcon from "components/StatusIndicatorWithIcon";
|
||||
import Button from "components/buttons/Button";
|
||||
import HeaderCell from "components/TableContainer/DataTable/HeaderCell";
|
||||
import ViewAllHostsLink from "components/ViewAllHostsLink";
|
||||
import { IndicatorStatus } from "components/StatusIndicatorWithIcon/StatusIndicatorWithIcon";
|
||||
|
||||
|
|
@ -42,7 +45,8 @@ interface IDataColumn {
|
|||
// NOTE: cellProps come from react-table
|
||||
// more info here https://react-table.tanstack.com/docs/api/useTable#cell-properties
|
||||
const generatePolicyTableHeaders = (
|
||||
togglePolicyDetails: (policy: IHostPolicy, teamId?: number) => void
|
||||
togglePolicyDetails: (policy: IHostPolicy, teamId?: number) => void,
|
||||
currentTeamId?: number
|
||||
): IDataColumn[] => {
|
||||
const STATUS_CELL_VALUES: Record<PolicyStatus, IStatusCellValue> = {
|
||||
pass: {
|
||||
|
|
@ -65,12 +69,17 @@ const generatePolicyTableHeaders = (
|
|||
disableSortBy: true,
|
||||
Cell: (cellProps) => {
|
||||
const { name } = cellProps.row.original;
|
||||
|
||||
const onClickPolicyName = (e: React.MouseEvent) => {
|
||||
// Allows for button to be clickable in a clickable row
|
||||
e.stopPropagation();
|
||||
togglePolicyDetails(cellProps.row.original);
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
className="policy-info"
|
||||
onClick={() => {
|
||||
togglePolicyDetails(cellProps.row.original);
|
||||
}}
|
||||
onClick={onClickPolicyName}
|
||||
variant="text-icon"
|
||||
>
|
||||
<span className={`policy-info-text`}>{name}</span>
|
||||
|
|
@ -80,9 +89,15 @@ const generatePolicyTableHeaders = (
|
|||
},
|
||||
{
|
||||
title: "Status",
|
||||
Header: "Status",
|
||||
Header: (cellProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
disableSortBy: false,
|
||||
sortType: "caseInsensitive",
|
||||
accessor: "response",
|
||||
disableSortBy: true,
|
||||
Cell: (cellProps) => {
|
||||
if (cellProps.row.original.response === "") {
|
||||
return <>{DEFAULT_EMPTY_CELL_VALUE}</>;
|
||||
|
|
@ -114,6 +129,7 @@ const generatePolicyTableHeaders = (
|
|||
cellProps.row.original.response === "pass"
|
||||
? PolicyResponse.PASSING
|
||||
: PolicyResponse.FAILING,
|
||||
team_id: currentTeamId,
|
||||
}}
|
||||
className="policy-link"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import { IPolicyStats } from "interfaces/policy";
|
|||
import PATHS from "router/paths";
|
||||
import sortUtils from "utilities/sort";
|
||||
import { PolicyResponse } from "utilities/constants";
|
||||
import { buildQueryStringFromParams } from "utilities/url";
|
||||
import { createHostsByPolicyPath } from "utilities/helpers";
|
||||
import InheritedBadge from "components/InheritedBadge";
|
||||
import { getConditionalSelectHeaderCheckboxProps } from "components/TableContainer/utilities/config_utils";
|
||||
import PassingColumnHeader from "../PassingColumnHeader";
|
||||
|
|
@ -60,18 +60,6 @@ interface IDataColumn {
|
|||
sortType?: string;
|
||||
}
|
||||
|
||||
const createHostsByPolicyPath = (
|
||||
policyId: number,
|
||||
policyResponse: PolicyResponse,
|
||||
teamId?: number | null
|
||||
) => {
|
||||
return `${PATHS.MANAGE_HOSTS}?${buildQueryStringFromParams({
|
||||
policy_id: policyId,
|
||||
policy_response: policyResponse,
|
||||
team_id: teamId,
|
||||
})}`;
|
||||
};
|
||||
|
||||
const getPolicyRefreshTime = (ms: number): string => {
|
||||
const seconds = ms / 1000;
|
||||
if (seconds < 60) {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import {
|
|||
intlFormat,
|
||||
intervalToDuration,
|
||||
isAfter,
|
||||
isBefore,
|
||||
addDays,
|
||||
} from "date-fns";
|
||||
import yaml from "js-yaml";
|
||||
|
|
@ -41,6 +40,7 @@ import {
|
|||
import { ITeam } from "interfaces/team";
|
||||
import { UserRole } from "interfaces/user";
|
||||
|
||||
import PATHS from "router/paths";
|
||||
import stringUtils from "utilities/strings";
|
||||
import sortUtils from "utilities/sort";
|
||||
import { checkTable } from "utilities/sql_tools";
|
||||
|
|
@ -54,6 +54,7 @@ import {
|
|||
INITIAL_FLEET_DATE,
|
||||
PLATFORM_LABEL_DISPLAY_TYPES,
|
||||
isPlatformLabelNameFromAPI,
|
||||
PolicyResponse,
|
||||
} from "utilities/constants";
|
||||
import { ISchedulableQueryStats } from "interfaces/schedulable_query";
|
||||
import { IDropdownOption } from "interfaces/dropdownOption";
|
||||
|
|
@ -91,6 +92,18 @@ export const addGravatarUrlToResource = (resource: any): any => {
|
|||
};
|
||||
};
|
||||
|
||||
export const createHostsByPolicyPath = (
|
||||
policyId: number,
|
||||
policyResponse: PolicyResponse,
|
||||
teamId?: number | null
|
||||
) => {
|
||||
return `${PATHS.MANAGE_HOSTS}?${buildQueryStringFromParams({
|
||||
policy_id: policyId,
|
||||
policy_response: policyResponse,
|
||||
team_id: teamId,
|
||||
})}`;
|
||||
};
|
||||
|
||||
const labelSlug = (label: ILabel): string => {
|
||||
const { id, name } = label;
|
||||
|
||||
|
|
@ -965,6 +978,7 @@ export function getCustomDropdownOptions(
|
|||
|
||||
export default {
|
||||
addGravatarUrlToResource,
|
||||
createHostsByPolicyPath,
|
||||
formatConfigDataForServer,
|
||||
formatLabelResponse,
|
||||
formatFloatAsPercentage,
|
||||
|
|
|
|||
Loading…
Reference in a new issue