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,27 +4,48 @@ 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;
const { text, isHtml, issue } = props;
let svgIcon = (
<svg
width="16"
height="17"
viewBox="0 0 16 17"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="8" cy="8.59961" r="8" fill="#6A67FE" />
<path
d="M7.49605 10.1893V9.70927C7.49605 9.33327 7.56405 8.98527 7.70005 8.66527C7.84405 8.34527 8.08405 7.99727 8.42005 7.62127C8.67605 7.34127 8.85205 7.10127 8.94805 6.90127C9.05205 6.70127 9.10405 6.48927 9.10405 6.26527C9.10405 6.00127 9.00805 5.79327 8.81605 5.64127C8.62405 5.48927 8.35205 5.41326 8.00005 5.41326C7.21605 5.41326 6.49205 5.70127 5.82805 6.27727L5.32405 5.12527C5.66005 4.82127 6.07605 4.57727 6.57205 4.39327C7.07605 4.20927 7.58405 4.11727 8.09605 4.11727C8.60005 4.11727 9.04005 4.20127 9.41605 4.36927C9.80005 4.53727 10.096 4.76927 10.304 5.06527C10.52 5.36127 10.628 5.70927 10.628 6.10927C10.628 6.47727 10.544 6.82127 10.376 7.14127C10.216 7.46127 9.92805 7.80927 9.51205 8.18527C9.13605 8.52927 8.87605 8.82927 8.73205 9.08527C8.58805 9.34127 8.49605 9.59727 8.45605 9.85327L8.40805 10.1893H7.49605ZM7.11205 12.6973V11.0293H8.79205V12.6973H7.11205Z"
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}>
<svg
width="16"
height="17"
viewBox="0 0 16 17"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="8" cy="8.59961" r="8" fill="#6A67FE" />
<path
d="M7.49605 10.1893V9.70927C7.49605 9.33327 7.56405 8.98527 7.70005 8.66527C7.84405 8.34527 8.08405 7.99727 8.42005 7.62127C8.67605 7.34127 8.85205 7.10127 8.94805 6.90127C9.05205 6.70127 9.10405 6.48927 9.10405 6.26527C9.10405 6.00127 9.00805 5.79327 8.81605 5.64127C8.62405 5.48927 8.35205 5.41326 8.00005 5.41326C7.21605 5.41326 6.49205 5.70127 5.82805 6.27727L5.32405 5.12527C5.66005 4.82127 6.07605 4.57727 6.57205 4.39327C7.07605 4.20927 7.58405 4.11727 8.09605 4.11727C8.60005 4.11727 9.04005 4.20127 9.41605 4.36927C9.80005 4.53727 10.096 4.76927 10.304 5.06527C10.52 5.36127 10.628 5.70927 10.628 6.10927C10.628 6.47727 10.544 6.82127 10.376 7.14127C10.216 7.46127 9.92805 7.80927 9.51205 8.18527C9.13605 8.52927 8.87605 8.82927 8.73205 9.08527C8.58805 9.34127 8.49605 9.59727 8.45605 9.85327L8.40805 10.1893H7.49605ZM7.11205 12.6973V11.0293H8.79205V12.6973H7.11205Z"
fill="white"
/>
</svg>
{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>
) : (
<div className={`${baseClass}__wrapper`}>
<table className={wrapperClassName}>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Installed Version</th>
</tr>
</thead>
<tbody>
{!!host.software.length &&
host.software.map((software) => {
<>
<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.map((software) => {
return (
<SoftwareListRow
key={`software-row-${software.id}`}
@ -407,9 +411,10 @@ export class HostDetailsPage extends Component {
/>
);
})}
</tbody>
</table>
</div>
</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;