Frontend : Vulnerability changes to existing pages (#16601)
This commit is contained in:
committed by
mostlikelee
parent
4f15b24b07
commit
0ec010976a
+60
@@ -0,0 +1,60 @@
|
||||
import React from "react";
|
||||
import ReactTooltip from "react-tooltip";
|
||||
import { formatFloatAsPercentage } from "utilities/helpers";
|
||||
|
||||
import Icon from "components/Icon";
|
||||
import { COLORS } from "styles/var/colors";
|
||||
|
||||
import TextCell from "../TextCell";
|
||||
|
||||
interface IProbabilityOfExploitCellProps {
|
||||
probabilityOfExploit?: number | null;
|
||||
cisaKnownExploit?: boolean | null;
|
||||
rowId: string;
|
||||
}
|
||||
|
||||
const ProbabilityOfExploitCell = ({
|
||||
probabilityOfExploit,
|
||||
cisaKnownExploit,
|
||||
rowId,
|
||||
}: IProbabilityOfExploitCellProps): JSX.Element => {
|
||||
// Includes edge case of probability being 0
|
||||
if (typeof probabilityOfExploit === "number") {
|
||||
return (
|
||||
<>
|
||||
{formatFloatAsPercentage(probabilityOfExploit)}{" "}
|
||||
{cisaKnownExploit && (
|
||||
<>
|
||||
<span
|
||||
className={`probability-of-exploit tooltip tooltip__tooltip-icon`}
|
||||
data-tip
|
||||
data-for={`probability-of-exploit__${rowId.toString()}`}
|
||||
>
|
||||
<Icon name="error" size="small" color={"status-error"} />
|
||||
</span>
|
||||
|
||||
<ReactTooltip
|
||||
place="top"
|
||||
effect="solid"
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
id={`probability-of-exploit__${rowId.toString()}`}
|
||||
data-html
|
||||
>
|
||||
<span className={`tooltip__tooltip-text`}>
|
||||
<>
|
||||
The vulnerability has been actively exploited in the wild.
|
||||
This data is reported by the Cybersecurity and Infrastructure
|
||||
Security Agency (CISA).
|
||||
</>
|
||||
</span>
|
||||
</ReactTooltip>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
// Probability can return null or undefined
|
||||
return <TextCell greyed />;
|
||||
};
|
||||
|
||||
export default ProbabilityOfExploitCell;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./ProbabilityOfExploitCell";
|
||||
@@ -211,6 +211,10 @@ $shadow-transition-width: 10px;
|
||||
max-width: 500px;
|
||||
word-wrap: break-word;
|
||||
|
||||
&.linkToFilteredHosts__cell {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&.selection__cell {
|
||||
width: 0px;
|
||||
padding: 0 $pad-medium;
|
||||
|
||||
@@ -159,4 +159,21 @@
|
||||
vertical-align: sub;
|
||||
}
|
||||
}
|
||||
.linkToFilteredHosts__header {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
// This hides View all host link unless the row is hovered
|
||||
tr {
|
||||
.row-hover-link {
|
||||
opacity: 0;
|
||||
transition: 250ms;
|
||||
text-overflow: none;
|
||||
}
|
||||
&:hover {
|
||||
.row-hover-link {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ interface IHostLinkProps {
|
||||
platformLabelId?: number;
|
||||
/** Shows right chevron without text */
|
||||
condensed?: boolean;
|
||||
/** Table links shows on row hover only */
|
||||
rowHover?: boolean;
|
||||
}
|
||||
|
||||
const baseClass = "view-all-hosts-link";
|
||||
@@ -22,8 +24,11 @@ const ViewAllHostsLink = ({
|
||||
className,
|
||||
platformLabelId,
|
||||
condensed = false,
|
||||
rowHover = false,
|
||||
}: IHostLinkProps): JSX.Element => {
|
||||
const viewAllHostsLinkClass = classnames(baseClass, className);
|
||||
const viewAllHostsLinkClass = classnames(baseClass, className, {
|
||||
"row-hover-link": rowHover,
|
||||
});
|
||||
|
||||
const endpoint = platformLabelId
|
||||
? PATHS.MANAGE_HOSTS_LABEL(platformLabelId)
|
||||
|
||||
@@ -67,6 +67,7 @@ export interface ISoftwareVulnerability {
|
||||
cve_published?: string | null;
|
||||
cve_description?: string | null;
|
||||
resolved_in_version?: string | null;
|
||||
created_at?: string | null;
|
||||
}
|
||||
|
||||
export interface ISoftwareVersion {
|
||||
|
||||
+25
-14
@@ -108,21 +108,32 @@ const defaultTableHeaders: IDataColumn[] = [
|
||||
),
|
||||
disableSortBy: true,
|
||||
accessor: "windowsHosts",
|
||||
Cell: ({
|
||||
cell: { value: aggregateCount },
|
||||
row: { original },
|
||||
}: ICellProps) => {
|
||||
Cell: ({ cell: { value: aggregateCount } }: ICellProps) => {
|
||||
return (
|
||||
<div className="disk-encryption-table__aggregate-table-data">
|
||||
<TextCell value={aggregateCount} formatter={(val) => <>{val}</>} />
|
||||
<ViewAllHostsLink
|
||||
className="view-hosts-link"
|
||||
queryParams={{
|
||||
[HOSTS_QUERY_PARAMS.DISK_ENCRYPTION]: original.status.value,
|
||||
team_id: original.teamId,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<TextCell value={aggregateCount} formatter={(val) => <>{val}</>} />
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "",
|
||||
Header: "",
|
||||
accessor: "linkToFilteredHosts",
|
||||
disableSortBy: true,
|
||||
Cell: (cellProps: ICellProps) => {
|
||||
return (
|
||||
<>
|
||||
{cellProps.row.original && (
|
||||
<ViewAllHostsLink
|
||||
className="view-hosts-link"
|
||||
queryParams={{
|
||||
[HOSTS_QUERY_PARAMS.DISK_ENCRYPTION]:
|
||||
cellProps.row.original.status.value,
|
||||
team_id: cellProps.row.original.teamId,
|
||||
}}
|
||||
rowHover
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
-19
@@ -5,25 +5,6 @@
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
&__aggregate-table-data {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
tr {
|
||||
.view-hosts-link {
|
||||
opacity: 0;
|
||||
transition: 250ms;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.view-hosts-link {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $break-md) {
|
||||
.view-hosts-link {
|
||||
span {
|
||||
|
||||
+16
-9
@@ -61,23 +61,30 @@ export const generateTableHeaders = (teamId: number) => {
|
||||
),
|
||||
Cell: ({ row }: IHostCellProps): JSX.Element => {
|
||||
const { hosts_count, name_only, version } = row.original;
|
||||
return <TextCell value={hosts_count} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "",
|
||||
Header: "",
|
||||
accessor: "linkToFilteredHosts",
|
||||
disableSortBy: true,
|
||||
Cell: (cellProps: IOSTypeCellProps) => {
|
||||
return (
|
||||
<span className="hosts-cell__wrapper">
|
||||
<span className="hosts-cell__count">
|
||||
<TextCell value={hosts_count} />
|
||||
</span>
|
||||
<span className="hosts-cell__link">
|
||||
<>
|
||||
{cellProps.row.original && (
|
||||
<ViewAllHostsLink
|
||||
queryParams={{
|
||||
os_name: name_only,
|
||||
os_version: version,
|
||||
os_name: cellProps.row.original.name_only,
|
||||
os_version: cellProps.row.original.version,
|
||||
team_id: teamId,
|
||||
}}
|
||||
condensed
|
||||
className="os-hosts-link"
|
||||
rowHover
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
+24
-14
@@ -82,21 +82,31 @@ export const COLUMN_CONFIGS: IColumnConfig[] = [
|
||||
/>
|
||||
),
|
||||
accessor: "hosts",
|
||||
Cell: ({
|
||||
cell: { value: aggregateCount },
|
||||
row: { original },
|
||||
}: ICellProps) => {
|
||||
Cell: ({ cell: { value: aggregateCount } }: ICellProps) => {
|
||||
return (
|
||||
<div className="bootstrap-package-table__aggregate-table-data">
|
||||
<TextCell value={aggregateCount} formatter={(val) => <>{val}</>} />
|
||||
<ViewAllHostsLink
|
||||
className="view-hosts-link"
|
||||
queryParams={{
|
||||
bootstrap_package: original.status.value,
|
||||
team_id: original.teamId,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<TextCell value={aggregateCount} formatter={(val) => <>{val}</>} />
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "",
|
||||
Header: "",
|
||||
accessor: "linkToFilteredHosts",
|
||||
disableSortBy: true,
|
||||
Cell: (cellProps: ICellProps) => {
|
||||
return (
|
||||
<>
|
||||
{cellProps.row.original && (
|
||||
<ViewAllHostsLink
|
||||
className="view-hosts-link"
|
||||
queryParams={{
|
||||
bootstrap_package: cellProps.row.original.status.value,
|
||||
team_id: cellProps.row.original.teamId,
|
||||
}}
|
||||
rowHover
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
+2
-17
@@ -10,23 +10,8 @@
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
&__aggregate-table-data {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
tr {
|
||||
.view-hosts-link {
|
||||
opacity: 0;
|
||||
transition: 250ms;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.view-hosts-link {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
th:nth-last-child(2) {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
@media (max-width: $break-lg) {
|
||||
|
||||
+22
-13
@@ -88,21 +88,30 @@ const generateSoftwareTitleDetailsTableConfig = (router: InjectedRouter) => {
|
||||
disableSortBy: true,
|
||||
accessor: "hosts_count",
|
||||
Cell: (cellProps: INumberCellProps): JSX.Element => (
|
||||
<span className="hosts-cell__wrapper">
|
||||
<span className="hosts-cell__count">
|
||||
<TextCell value={cellProps.cell.value} />
|
||||
</span>
|
||||
<span className="hosts-cell__link">
|
||||
<ViewAllHostsLink
|
||||
queryParams={{
|
||||
software_version_id: cellProps.row.original.id,
|
||||
}}
|
||||
className="software-link"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
<TextCell value={cellProps.cell.value} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "",
|
||||
Header: "",
|
||||
accessor: "linkToFilteredHosts",
|
||||
disableSortBy: true,
|
||||
Cell: (cellProps: ICellProps) => {
|
||||
return (
|
||||
<>
|
||||
{cellProps.row.original && (
|
||||
<ViewAllHostsLink
|
||||
queryParams={{
|
||||
software_version_id: cellProps.row.original.id,
|
||||
}}
|
||||
className="software-link"
|
||||
rowHover
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return tableHeaders;
|
||||
|
||||
+3
-9
@@ -1,13 +1,7 @@
|
||||
.software-title-details-table {
|
||||
|
||||
.hosts-cell__wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.hosts-cell__link {
|
||||
display: flex;
|
||||
white-space: nowrap;
|
||||
.data-table {
|
||||
.hosts_count__header {
|
||||
border-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,18 +16,4 @@
|
||||
font-size: $medium;
|
||||
}
|
||||
}
|
||||
|
||||
// for showing and hiding software link on hover
|
||||
tr {
|
||||
.software-link {
|
||||
opacity: 0;
|
||||
transition: opacity 250ms;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.software-link {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+19
-14
@@ -161,22 +161,27 @@ const generateTableHeaders = (
|
||||
disableSortBy: false,
|
||||
accessor: "hosts_count",
|
||||
Cell: (cellProps: INumberCellProps): JSX.Element => (
|
||||
<span className="hosts-cell__wrapper">
|
||||
<span className="hosts-cell__count">
|
||||
<TextCell value={cellProps.cell.value} />
|
||||
</span>
|
||||
<span className="hosts-cell__link">
|
||||
<ViewAllHostsLink
|
||||
queryParams={{
|
||||
software_title_id: cellProps.row.original.id,
|
||||
team_id: teamId, // TODO: do we need team id here?
|
||||
}}
|
||||
className="software-link"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
<TextCell value={cellProps.cell.value} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "",
|
||||
Header: "",
|
||||
accessor: "linkToFilteredHosts",
|
||||
disableSortBy: true,
|
||||
Cell: (cellProps: ICellProps) => {
|
||||
return (
|
||||
<ViewAllHostsLink
|
||||
queryParams={{
|
||||
software_title_id: cellProps.row.original.id,
|
||||
team_id: teamId, // TODO: do we need team id here?
|
||||
}}
|
||||
className="software-link"
|
||||
rowHover
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return softwareTableHeaders;
|
||||
|
||||
+23
-14
@@ -134,22 +134,31 @@ const generateTableHeaders = (
|
||||
disableSortBy: false,
|
||||
accessor: "hosts_count",
|
||||
Cell: (cellProps: INumberCellProps): JSX.Element => (
|
||||
<span className="hosts-cell__wrapper">
|
||||
<span className="hosts-cell__count">
|
||||
<TextCell value={cellProps.cell.value} />
|
||||
</span>
|
||||
<span className="hosts-cell__link">
|
||||
<ViewAllHostsLink
|
||||
queryParams={{
|
||||
software_version_id: cellProps.row.original.id,
|
||||
team_id: teamId, // TODO: do we need team id here?
|
||||
}}
|
||||
className="software-link"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
<TextCell value={cellProps.cell.value} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "",
|
||||
Header: "",
|
||||
accessor: "linkToFilteredHosts",
|
||||
disableSortBy: true,
|
||||
Cell: (cellProps: ICellProps) => {
|
||||
return (
|
||||
<>
|
||||
{cellProps.row.original && (
|
||||
<ViewAllHostsLink
|
||||
queryParams={{
|
||||
software_version_id: cellProps.row.original.id,
|
||||
team_id: teamId, // TODO: do we need team id here?
|
||||
}}
|
||||
className="software-link"
|
||||
rowHover
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return softwareTableHeaders;
|
||||
|
||||
@@ -32,6 +32,9 @@
|
||||
|
||||
&__data-set {
|
||||
font-size: $x-small;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $pad-xsmall;
|
||||
|
||||
dt {
|
||||
font-weight: $bold;
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ const SoftwareVulnerabilitiesTable = ({
|
||||
<TableContainer
|
||||
columnConfigs={tableHeaders}
|
||||
data={data}
|
||||
defaultSortHeader={isPremiumTier ? "epss_probability" : "cve"}
|
||||
defaultSortHeader={isPremiumTier ? "updated_at" : "cve"} // TODO: Change premium to created_at when added to API
|
||||
defaultSortDirection={"desc"}
|
||||
emptyComponent={() => <NoVulnsDetected itemName={itemName} />}
|
||||
isAllPagesSelected={false}
|
||||
|
||||
+69
-52
@@ -1,15 +1,17 @@
|
||||
import React from "react";
|
||||
|
||||
import { formatFloatAsPercentage } from "utilities/helpers";
|
||||
import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants";
|
||||
import { ISoftwareVulnerability } from "interfaces/software";
|
||||
|
||||
import paths from "router/paths";
|
||||
import HeaderCell from "components/TableContainer/DataTable/HeaderCell/HeaderCell";
|
||||
import TextCell from "components/TableContainer/DataTable/TextCell";
|
||||
import TooltipWrapper from "components/TooltipWrapper";
|
||||
import CustomLink from "components/CustomLink";
|
||||
import { HumanTimeDiffWithDateTip } from "components/HumanTimeDiffWithDateTip";
|
||||
import PremiumFeatureIconWithTooltip from "components/PremiumFeatureIconWithTooltip";
|
||||
import ProbabilityOfExploitCell from "components/TableContainer/DataTable/ProbabilityOfExploitCell.tsx/ProbabilityOfExploitCell";
|
||||
import ViewAllHostsLink from "components/ViewAllHostsLink";
|
||||
import LinkCell from "components/TableContainer/DataTable/LinkCell";
|
||||
|
||||
interface IHeaderProps {
|
||||
column: {
|
||||
@@ -72,12 +74,12 @@ const generateTableConfig = (
|
||||
accessor: "cve",
|
||||
disableSortBy: true,
|
||||
Header: "Vulnerability",
|
||||
Cell: ({ cell: { value }, row }: ITextCellProps) => {
|
||||
Cell: ({ cell: { value } }: ITextCellProps) => {
|
||||
const cveName = value.toString();
|
||||
return (
|
||||
<CustomLink
|
||||
url={row.original.details_link}
|
||||
text={value.toString()}
|
||||
newTab
|
||||
<LinkCell
|
||||
value={cveName}
|
||||
path={paths.SOFTWARE_VULNERABILITY_DETAILS(cveName)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
@@ -85,39 +87,6 @@ const generateTableConfig = (
|
||||
];
|
||||
|
||||
const premiumHeaders: IDataColumn[] = [
|
||||
{
|
||||
title: "Probability of exploit",
|
||||
accessor: "epss_probability",
|
||||
disableSortBy: false,
|
||||
Header: (headerProps: IHeaderProps): JSX.Element => {
|
||||
const titleWithToolTip = (
|
||||
<TooltipWrapper
|
||||
tipContent={
|
||||
<>
|
||||
The probability that this vulnerability will be exploited in the
|
||||
next 30 days (EPSS probability).
|
||||
<br />
|
||||
This data is reported by FIRST.org.
|
||||
</>
|
||||
}
|
||||
>
|
||||
Probability of exploit
|
||||
</TooltipWrapper>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<HeaderCell
|
||||
value={titleWithToolTip}
|
||||
isSortedDesc={headerProps.column.isSortedDesc}
|
||||
/>
|
||||
{isSandboxMode && <PremiumFeatureIconWithTooltip />}
|
||||
</>
|
||||
);
|
||||
},
|
||||
Cell: ({ cell: { value } }: ITextCellProps): JSX.Element => (
|
||||
<TextCell formatter={formatFloatAsPercentage} value={value} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Severity",
|
||||
accessor: "cvss_score",
|
||||
@@ -129,9 +98,6 @@ const generateTableConfig = (
|
||||
<>
|
||||
The worst case impact across different environments (CVSS base
|
||||
score).
|
||||
<br />
|
||||
This data is reported by the National Vulnerability Database
|
||||
(NVD).
|
||||
</>
|
||||
}
|
||||
>
|
||||
@@ -153,22 +119,22 @@ const generateTableConfig = (
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Known exploit",
|
||||
accessor: "cisa_known_exploit",
|
||||
title: "Probability of exploit",
|
||||
accessor: "epss_probability",
|
||||
disableSortBy: false,
|
||||
sortType: "boolean",
|
||||
Header: (headerProps: IHeaderProps): JSX.Element => {
|
||||
const titleWithToolTip = (
|
||||
<TooltipWrapper
|
||||
className="epss_probability"
|
||||
tipContent={
|
||||
<>
|
||||
The vulnerability has been actively exploited in the wild. This
|
||||
data is reported by the Cybersecurity and Infrustructure
|
||||
Security Agency (CISA).
|
||||
The probability that this vulnerability will be exploited in the
|
||||
next 30 days (EPSS probability). <br />
|
||||
This data is reported by FIRST.org.
|
||||
</>
|
||||
}
|
||||
>
|
||||
Known exploit
|
||||
Probability of exploit
|
||||
</TooltipWrapper>
|
||||
);
|
||||
return (
|
||||
@@ -181,8 +147,12 @@ const generateTableConfig = (
|
||||
</>
|
||||
);
|
||||
},
|
||||
Cell: ({ cell: { value } }: ITextCellProps): JSX.Element => (
|
||||
<TextCell value={value ? "Yes" : "No"} />
|
||||
Cell: (cellProps: ICellProps): JSX.Element => (
|
||||
<ProbabilityOfExploitCell
|
||||
probabilityOfExploit={cellProps.row.original.epss_probability}
|
||||
cisaKnownExploit={cellProps.row.original.cisa_known_exploit}
|
||||
rowId={cellProps.row.original.cve}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -222,6 +192,53 @@ const generateTableConfig = (
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Detected",
|
||||
accessor: "created_at",
|
||||
disableSortBy: false,
|
||||
Header: (headerProps: IHeaderProps): JSX.Element => {
|
||||
return (
|
||||
<>
|
||||
<HeaderCell
|
||||
value="Detected"
|
||||
isSortedDesc={headerProps.column.isSortedDesc}
|
||||
/>
|
||||
{isSandboxMode && <PremiumFeatureIconWithTooltip />}
|
||||
</>
|
||||
);
|
||||
},
|
||||
Cell: (cellProps: ICellProps): JSX.Element => {
|
||||
const createdAt = cellProps.row.original.created_at || "";
|
||||
|
||||
return (
|
||||
<TextCell
|
||||
value={{ timeString: createdAt }}
|
||||
formatter={HumanTimeDiffWithDateTip}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "",
|
||||
Header: "",
|
||||
accessor: "linkToFilteredHosts",
|
||||
disableSortBy: true,
|
||||
Cell: (cellProps: ICellProps) => {
|
||||
return (
|
||||
<>
|
||||
{cellProps.row.original && (
|
||||
<ViewAllHostsLink
|
||||
queryParams={{
|
||||
vulnerability: cellProps.row.original.cve,
|
||||
}}
|
||||
className="vulnerabilities-link"
|
||||
rowHover
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return isPremiumTier ? tableHeaders.concat(premiumHeaders) : tableHeaders;
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
.software-vulnerabilities-table {
|
||||
|
||||
// keeps table data within the table container at smaller screen sizes
|
||||
.data-table {
|
||||
&__wrapper {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
th:nth-last-child(2) {
|
||||
border-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// used to position header text with premium icon correctly
|
||||
@@ -12,4 +15,21 @@
|
||||
display: flex;
|
||||
gap: $pad-small;
|
||||
}
|
||||
|
||||
@media (max-width: $break-md) {
|
||||
.linkToFilteredHosts__header {
|
||||
width: 40px;
|
||||
}
|
||||
.view-all-hosts-link {
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.epss_probability__cell,
|
||||
.epss_probability__header,
|
||||
.cve_published__cell,
|
||||
.cve_published__header {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,6 +241,7 @@ const ManageHostsPage = ({
|
||||
os_name: osName,
|
||||
os_version: osVersion,
|
||||
} = queryParams;
|
||||
const vulnerability = queryParams?.vulnerability;
|
||||
const munkiIssueId =
|
||||
queryParams?.munki_issue_id !== undefined
|
||||
? parseInt(queryParams.munki_issue_id, 10)
|
||||
@@ -383,6 +384,7 @@ const ManageHostsPage = ({
|
||||
osVersionId,
|
||||
osName,
|
||||
osVersion,
|
||||
vulnerability,
|
||||
page: tableQueryData ? tableQueryData.pageIndex : 0,
|
||||
perPage: tableQueryData ? tableQueryData.pageSize : 50,
|
||||
device_mapping: true,
|
||||
@@ -425,6 +427,7 @@ const ManageHostsPage = ({
|
||||
osVersionId,
|
||||
osName,
|
||||
osVersion,
|
||||
vulnerability,
|
||||
osSettings: osSettingsStatus,
|
||||
diskEncryptionStatus,
|
||||
bootstrapPackageStatus,
|
||||
@@ -824,6 +827,8 @@ const ManageHostsPage = ({
|
||||
newQueryParams.os_version_id = osVersionId;
|
||||
newQueryParams.os_name = osName;
|
||||
newQueryParams.os_version = osVersion;
|
||||
} else if (vulnerability) {
|
||||
newQueryParams.vulnerability = vulnerability;
|
||||
} else if (osSettingsStatus) {
|
||||
newQueryParams[PARAMS.OS_SETTINGS] = osSettingsStatus;
|
||||
} else if (diskEncryptionStatus && isPremiumTier) {
|
||||
@@ -1281,6 +1286,7 @@ const ManageHostsPage = ({
|
||||
os_version_id: osVersionId,
|
||||
os_name: osName,
|
||||
os_version: osVersion,
|
||||
vulnerability,
|
||||
visibleColumns,
|
||||
};
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ interface IHostsFilterBlockProps {
|
||||
osVersionId?: string;
|
||||
osName?: string;
|
||||
osVersion?: string;
|
||||
vulnerability?: string;
|
||||
munkiIssueId?: number;
|
||||
osVersions?: IOperatingSystemVersion[];
|
||||
softwareDetails: { name: string; version?: string } | null;
|
||||
@@ -104,6 +105,7 @@ const HostsFilterBlock = ({
|
||||
osVersionId,
|
||||
osName,
|
||||
osVersion,
|
||||
vulnerability,
|
||||
munkiIssueId,
|
||||
munkiIssueDetails,
|
||||
policyResponse,
|
||||
@@ -203,6 +205,24 @@ const HostsFilterBlock = ({
|
||||
);
|
||||
};
|
||||
|
||||
const renderVulnerabilityFilterBlock = () => {
|
||||
if (!vulnerability) return null;
|
||||
|
||||
// TODO: Move formatOperatingSystemDisplayName into utils file
|
||||
const label = formatOperatingSystemDisplayName(vulnerability);
|
||||
const TooltipDescription = (
|
||||
<span>Hosts affected by the specified CVE.</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<FilterPill
|
||||
label={label}
|
||||
tooltipDescription={TooltipDescription}
|
||||
onClear={() => handleClearFilter(["vulnerability"])}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// NOTE: good example of filter dropdown with pill
|
||||
const renderPoliciesFilterBlock = () => (
|
||||
<>
|
||||
@@ -498,6 +518,8 @@ const HostsFilterBlock = ({
|
||||
return renderMDMEnrollmentFilterBlock();
|
||||
case !!osVersionId || (!!osName && !!osVersion):
|
||||
return renderOSFilterBlock();
|
||||
case !!vulnerability:
|
||||
return renderVulnerabilityFilterBlock();
|
||||
case !!munkiIssueId:
|
||||
return renderMunkiIssueFilterBlock();
|
||||
case !!lowDiskSpaceHosts:
|
||||
|
||||
@@ -58,6 +58,10 @@ export default {
|
||||
SOFTWARE_OS_DETAILS: (id: number): string => {
|
||||
return `${URL_PREFIX}/software/os/${id}`;
|
||||
},
|
||||
SOFTWARE_VULNERABILITIES: `${URL_PREFIX}/software/vulnerabilities`,
|
||||
SOFTWARE_VULNERABILITY_DETAILS: (cve: string): string => {
|
||||
return `${URL_PREFIX}/software/vulnerabilities/${cve}`;
|
||||
},
|
||||
|
||||
EDIT_PACK: (packId: number): string => {
|
||||
return `${URL_PREFIX}/packs/${packId}/edit`;
|
||||
|
||||
@@ -50,6 +50,7 @@ export interface IHostCountLoadOptions {
|
||||
osName?: string;
|
||||
osVersion?: string;
|
||||
osSettings?: MdmProfileStatus;
|
||||
vulnerability?: string;
|
||||
diskEncryptionStatus?: DiskEncryptionStatus;
|
||||
bootstrapPackageStatus?: BootstrapPackageStatus;
|
||||
}
|
||||
@@ -77,6 +78,7 @@ export default {
|
||||
const osName = options?.osName;
|
||||
const osVersion = options?.osVersion;
|
||||
const osSettings = options?.osSettings;
|
||||
const vulnerability = options?.vulnerability;
|
||||
const diskEncryptionStatus = options?.diskEncryptionStatus;
|
||||
const bootstrapPackageStatus = options?.bootstrapPackageStatus;
|
||||
|
||||
@@ -102,6 +104,7 @@ export default {
|
||||
osVersionId,
|
||||
osVersion,
|
||||
osSettings,
|
||||
vulnerability,
|
||||
diskEncryptionStatus,
|
||||
bootstrapPackageStatus,
|
||||
}),
|
||||
|
||||
@@ -72,6 +72,7 @@ export interface ILoadHostsOptions {
|
||||
osVersionId?: number;
|
||||
osName?: string;
|
||||
osVersion?: string;
|
||||
vulnerability?: string;
|
||||
munkiIssueId?: number;
|
||||
device_mapping?: boolean;
|
||||
columns?: string;
|
||||
@@ -102,6 +103,7 @@ export interface IExportHostsOptions {
|
||||
osId?: number;
|
||||
osName?: string;
|
||||
osVersion?: string;
|
||||
vulnerability?: string;
|
||||
device_mapping?: boolean;
|
||||
columns?: string;
|
||||
visibleColumns?: string;
|
||||
@@ -261,6 +263,7 @@ export default {
|
||||
osVersionId,
|
||||
osName,
|
||||
osVersion,
|
||||
vulnerability,
|
||||
device_mapping,
|
||||
selectedLabels,
|
||||
sortBy,
|
||||
@@ -299,6 +302,7 @@ export default {
|
||||
osVersionId,
|
||||
osName,
|
||||
osVersion,
|
||||
vulnerability,
|
||||
diskEncryptionStatus,
|
||||
osSettings,
|
||||
bootstrapPackageStatus,
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
HOSTS_QUERY_PARAMS,
|
||||
MacSettingsStatusQueryParam,
|
||||
} from "services/entities/hosts";
|
||||
import vulnerability from "interfaces/vulnerability";
|
||||
|
||||
type QueryValues = string | number | boolean | undefined | null;
|
||||
export type QueryParams = Record<string, QueryValues>;
|
||||
@@ -36,6 +37,7 @@ interface IMutuallyExclusiveHostParams {
|
||||
osVersionId?: number;
|
||||
osName?: string;
|
||||
osVersion?: string;
|
||||
vulnerability?: string;
|
||||
osSettings?: MdmProfileStatus;
|
||||
diskEncryptionStatus?: DiskEncryptionStatus;
|
||||
bootstrapPackageStatus?: BootstrapPackageStatus;
|
||||
@@ -155,6 +157,8 @@ export const reconcileMutuallyExclusiveHostParams = ({
|
||||
return { os_version_id: osVersionId };
|
||||
case !!osName && !!osVersion:
|
||||
return { os_name: osName, os_version: osVersion };
|
||||
case !!vulnerability:
|
||||
return { vulnerability };
|
||||
case !!lowDiskSpaceHosts:
|
||||
return { low_disk_space: lowDiskSpaceHosts };
|
||||
case !!osSettings:
|
||||
|
||||
Reference in New Issue
Block a user