mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 08:55:24 +00:00
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:
parent
f1e42ee775
commit
d6d584094f
BIN
assets/images/icon-issue-fleet-black-50-16x16@2x.png
Normal file
BIN
assets/images/icon-issue-fleet-black-50-16x16@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 917 B |
@ -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
|
||||
|
@ -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,
|
||||
})
|
||||
),
|
||||
});
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
<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>
|
||||
|
||||
{renderVulsCount(vulsList)}
|
||||
</div>
|
||||
<div className={`${baseClass}__list`}>
|
||||
<ul>{vulsList.map((vul, index) => renderVul(vul, index))}</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SoftwareVulnerabilities;
|
@ -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;
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
export { default } from "./SoftwareVulnerabilities";
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user