Host Details Page: Render vulnerable software warnings (#1220)

* Conditionally renders vulnerability div and issue tooltip
* Refactors IconToolTip to include an Error icon
* Add vulnerabilities to type interface, small cleanup

Co-authored-by: Rachel Elysia Perkins <rachel@fleetdm.com>
This commit is contained in:
Zach Wasserman 2021-06-28 11:09:45 -07:00 committed by GitHub
parent f1e42ee775
commit d6d584094f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 214 additions and 30 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

View File

@ -4,14 +4,13 @@ import ReactTooltip from "react-tooltip";
interface IIconToolTipProps {
text: string;
isHtml?: boolean;
issue?: boolean;
}
// TODO: handle html text better. possibly use 'children' prop for html
const IconToolTip = (props: IIconToolTipProps): JSX.Element => {
const { text, isHtml } = props;
return (
<div className="icon-tooltip">
<span data-tip={text} data-html={isHtml}>
const { text, isHtml, issue } = props;
let svgIcon = (
<svg
width="16"
height="17"
@ -25,6 +24,28 @@ const IconToolTip = (props: IIconToolTipProps): JSX.Element => {
fill="white"
/>
</svg>
);
if (issue) {
svgIcon = (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
>
<path
d="M0 8C0 12.4183 3.5817 16 8 16C12.4183 16 16 12.4183 16 8C16 3.5817 12.4183 0 8 0C3.5817 0 0 3.5817 0 8ZM14 8C14 11.3137 11.3137 14 8 14C4.6863 14 2 11.3137 2 8C2 4.6863 4.6863 2 8 2C11.3137 2 14 4.6863 14 8ZM7 12V10H9V12H7ZM7 4V9H9V4H7Z"
fill="#8B8FA2"
/>
</svg>
);
}
return (
<div className="icon-tooltip">
<span data-tip={text} data-html={isHtml}>
{svgIcon}
</span>
{/* same colour as $core-fleet-blue */}
<ReactTooltip

View File

@ -5,4 +5,13 @@ export default PropTypes.shape({
name: PropTypes.string,
version: PropTypes.string,
id: PropTypes.number,
vulnerabilities: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number,
uid: PropTypes.number,
username: PropTypes.string,
type: PropTypes.string,
groupname: PropTypes.string,
})
),
});

View File

@ -12,6 +12,7 @@ import Button from "components/buttons/Button";
import Modal from "components/modals/Modal";
import SoftwareListRow from "pages/hosts/HostDetailsPage/SoftwareListRow";
import PackQueriesListRow from "pages/hosts/HostDetailsPage/PackQueriesListRow";
import SoftwareVulnerabilities from "pages/hosts/HostDetailsPage/SoftwareVulnerabilities";
import HostUsersListRow from "pages/hosts/HostDetailsPage/HostUsersListRow";
import permissionUtils from "utilities/permissions";
@ -377,6 +378,7 @@ export class HostDetailsPage extends Component {
return (
<div className="section section--software">
<p className="section__header">Software</p>
{host.software.length === 0 ? (
<div className="results">
<p className="results__header">
@ -388,18 +390,20 @@ export class HostDetailsPage extends Component {
</p>
</div>
) : (
<>
<SoftwareVulnerabilities softwareList={host.software} />
<div className={`${baseClass}__wrapper`}>
<table className={wrapperClassName}>
<thead>
<tr>
<th />
<th>Name</th>
<th>Type</th>
<th>Installed Version</th>
</tr>
</thead>
<tbody>
{!!host.software.length &&
host.software.map((software) => {
{host.software.map((software) => {
return (
<SoftwareListRow
key={`software-row-${software.id}`}
@ -410,6 +414,7 @@ export class HostDetailsPage extends Component {
</tbody>
</table>
</div>
</>
)}
</div>
);

View File

@ -1,4 +1,5 @@
import React, { Component } from "react";
import IconToolTip from "components/IconToolTip";
import softwareInterface from "interfaces/software";
@ -11,7 +12,7 @@ class SoftwareListRow extends Component {
render() {
const { software } = this.props;
const { name, source, version } = software;
const { name, source, version, vulnerabilities } = software;
const TYPE_CONVERSION = {
apt_sources: "Package (APT)",
@ -35,8 +36,26 @@ class SoftwareListRow extends Component {
const type = TYPE_CONVERSION[source] || "Unknown";
const vulnerabilitiesIcon = () => {
if (vulnerabilities.length === 0) {
return null;
}
const vulText =
vulnerabilities.length === 1 ? "vulnerability" : "vulnerabilities";
return (
<IconToolTip
text={`${vulnerabilities.length} ${vulText} detected`}
issue
isHtml
/>
);
};
return (
<tr>
<td className={`${baseClass}__name`}>{vulnerabilitiesIcon()}</td>
<td className={`${baseClass}__name`}>{name}</td>
<td className={`${baseClass}__type`}>{type}</td>
<td className={`${baseClass}__installed-version`}>{version}</td>

View File

@ -0,0 +1,86 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import FleetIcon from "components/icons/FleetIcon";
import softwareInterface from "interfaces/software";
const baseClass = "software-vulnerabilities";
class SoftwareVulnerabilities extends Component {
static propTypes = {
softwareList: PropTypes.arrayOf(softwareInterface),
};
render() {
const { softwareList } = this.props;
const vulsList = [];
const vulnerabilitiesListMaker = () => {
softwareList.forEach((software) => {
if (software.vulnerabilities) {
software.vulnerabilities.forEach((vulnerability) => {
vulsList.push({
name: software.name,
cve: vulnerability.cve,
details_link: vulnerability.details_link,
});
});
}
});
};
vulnerabilitiesListMaker();
const renderVulsCount = (list) => {
if (list.length === 1) {
return "1 vulnerability detected";
}
return `${list.length} vulnerabilities detected`;
};
const renderVul = (vul, index) => {
return (
<li key={index}>
Read more about{" "}
<a href={vul.details_link} target="_blank" rel="noopener noreferrer">
<em>{vul.name}</em> {vul.cve} vulnerability &nbsp;
<FleetIcon name="external-link" />
</a>
</li>
);
};
// No software vulnerabilities
if (vulsList.length === 0) {
return null;
}
// Software vulnerabilities
return (
<div className={`${baseClass}`}>
<div className={`${baseClass}__count`}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
>
<path
d="M0 8C0 12.4183 3.5817 16 8 16C12.4183 16 16 12.4183 16 8C16 3.5817 12.4183 0 8 0C3.5817 0 0 3.5817 0 8ZM14 8C14 11.3137 11.3137 14 8 14C4.6863 14 2 11.3137 2 8C2 4.6863 4.6863 2 8 2C11.3137 2 14 4.6863 14 8ZM7 12V10H9V12H7ZM7 4V9H9V4H7Z"
fill="#8B8FA2"
/>
</svg>
&nbsp;
{renderVulsCount(vulsList)}
</div>
<div className={`${baseClass}__list`}>
<ul>{vulsList.map((vul, index) => renderVul(vul, index))}</ul>
</div>
</div>
);
}
}
export default SoftwareVulnerabilities;

View File

@ -0,0 +1,26 @@
.software-vulnerabilities {
font-size: $x-small;
background-color: $ui-off-white;
border: solid 1px $ui-fleet-black-50;
box-sizing: border-box;
border-radius: 10px;
overflow: scroll;
margin-bottom: $pad-large;
padding: $pad-large;
padding-bottom: $pad-small;
a {
color: $core-vibrant-blue;
font-weight: $bold;
text-decoration: none;
}
&__count {
font-size: $small;
font-weight: $bold;
}
svg {
padding-right: $pad-xxsmall;
}
}

View File

@ -0,0 +1 @@
export { default } from "./SoftwareVulnerabilities";

View File

@ -307,6 +307,23 @@
}
}
.section--software {
th {
&:first-child {
border-right: none;
width: 16px;
padding-right: 0px;
}
}
tr {
td {
&:first-child {
padding-right: 0px;
}
}
}
}
tbody {
td {
padding: 12px 27px;