Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI impact widget new #214

Merged
merged 7 commits into from
Mar 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions ui/src/components/SeverityDisplay/severity-display.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,15 @@
.severity-title {
margin-left: 5px;
}
}

.severity-with-cvss-display {
display: flex;
align-items: center;

.cvss-score-display {
margin-left: 3px;
font-size: 10px;
line-height: 18px;
}
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
import React from 'react';
import SeverityDisplay, { SEVERITY_ITEMS } from 'components/SeverityDisplay';
import { TooltipWrapper } from 'components/Tooltip';
import { toCapitalized } from 'utils/utils';

import './severity-with-cvss-display.scss';

const SEVERITY_NOT_ALIGNED_MESSAGE = (
<div style={{width: "330px"}}>
While the scores may differ, the VMClarity (linux distribution severity) is more significant in your context than the CVSS base impact score.
</div>
);

const SeverityWithCvssDisplay = ({severity, cvssScore, cvssSeverity, compareTooltipId}) => {
if (!severity) {
return null;
}

const showSeverityTooltip = !!cvssSeverity && cvssSeverity !== severity &&
!(cvssSeverity === SEVERITY_ITEMS.NONE.value && severity === SEVERITY_ITEMS.NEGLIGIBLE.value);

const severityNotAlignedMessage = (
<div style={{width: "330px"}}>
{`Although the CVSS base impact score is ${cvssScore} (${toCapitalized(cvssSeverity || "")}), the linux distribution severity reflects the risk more accurately.`}
</div>
);

return (
<div className="severity-with-cvss-display">
<SeverityDisplay severity={severity} />
{!!cvssScore && <div className="cvss-score-display">{`(cvss ${cvssScore})`}</div>}
{!!showSeverityTooltip && <TooltipWrapper tooltipId={compareTooltipId} tooltipText={SEVERITY_NOT_ALIGNED_MESSAGE}>*</TooltipWrapper>}
{!!showSeverityTooltip && <TooltipWrapper tooltipId={compareTooltipId} tooltipText={severityNotAlignedMessage}>*</TooltipWrapper>}
</div>
);
}
Expand Down
20 changes: 10 additions & 10 deletions ui/src/components/Table/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,15 @@ const Table = props => {

return (
<React.Fragment key={row.id}>
<div className={classnames("table-tr", {clickable: !!onLineClick}, {"with-row-actions": withRowActions})} {...row.getRowProps()}>
<div
className={classnames("table-tr", {clickable: !!onLineClick}, {"with-row-actions": withRowActions})}
{...row.getRowProps()}
onClick={() => {
if (!!onLineClick) {
onLineClick(row.original);
}
}}
>
{
row.cells.map(cell => {
const {className, alignToTop} = cell.column;
Expand All @@ -267,15 +275,7 @@ const Table = props => {
const isTextValue = !!cell.column.accessor;

return (
<div
className={cellClassName}
{...cell.getCellProps()}
onClick={() => {
if (!!onLineClick) {
onLineClick(row.original);
}
}}
>{isTextValue ? cell.value : cell.render('Cell')}</div>
<div className={cellClassName} {...cell.getCellProps()}>{isTextValue ? cell.value : cell.render('Cell')}</div>
)
})
}
Expand Down
20 changes: 19 additions & 1 deletion ui/src/layout/AssetScans/AssetScansTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ import { FILTER_TYPES } from 'context/FiltersProvider';

const TABLE_TITLE = "asset scans";

const STATUS_MAPPING = {
NOT_SCANNED: "Not Scanned",
INIT: "Initialized",
ATTACHED: "Volume Snapshot Attached",
IN_PROGRESS: "In Progress",
DONE: "Done"
}

const AssetScansTable = () => {
const columns = useMemo(() => [
{
Expand Down Expand Up @@ -36,6 +44,16 @@ const AssetScansTable = () => {
},
disableSort: true
},
{
Header: "Scan status",
id: "status",
accessor: original => {
const {state} = original?.status?.general || {};

return STATUS_MAPPING[state];
},
disableSort: true
},
getVulnerabilitiesColumnConfigItem(TABLE_TITLE),
...getFindingsColumnsConfigList(TABLE_TITLE)
], []);
Expand All @@ -45,7 +63,7 @@ const AssetScansTable = () => {
columns={columns}
url={APIS.ASSET_SCANS}
expand="scan,target"
select="id,target,summary,scan"
select="id,target,summary,scan,status"
tableTitle={TABLE_TITLE}
filterType={FILTER_TYPES.ASSET_SCANS}
withMargin
Expand Down
90 changes: 90 additions & 0 deletions ui/src/layout/Dashboard/FindingsImpactWidget/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import React from 'react';
import SeverityWithCvssDisplay from 'components/SeverityWithCvssDisplay';
import { getHigestVersionCvssData } from 'utils/utils';
import { FINDINGS_MAPPING, VULNERABIITY_FINDINGS_ITEM, APIS } from 'utils/systemConsts';
import FindingsTabsWidget from '../FindingsTabsWidget';

const FINDINGS_ITEMS = [VULNERABIITY_FINDINGS_ITEM, ...Object.values(FINDINGS_MAPPING)];

const TABS_COLUMNS_MAPPING = {
[VULNERABIITY_FINDINGS_ITEM.dataKey]: {
headerItems: ["Name", "Severity"],
bodyItems: [
{dataKey: "vulnerability.vulnerabilityName"},
{customDisplay: ({vulnerability}) => {
const {severity, cvss, vulnerabilityName} = vulnerability || {};
const {score, severity: cvssSeverity} = getHigestVersionCvssData(cvss);

return (
<SeverityWithCvssDisplay
severity={severity}
cvssScore={score}
cvssSeverity={cvssSeverity?.toUpperCase()}
compareTooltipId={`severity-compare-tooltip-${vulnerabilityName}`}
/>
)
}}
]
},
[FINDINGS_MAPPING.EXPLOITS.dataKey]: {
headerItems: ["Vulnerability name"],
bodyItems: [
{dataKey: "exploit.cveID"}
]
},
[FINDINGS_MAPPING.MISCONFIGURATIONS.dataKey]: {
headerItems: ["Test description"],
bodyItems: [
{dataKey: "misconfiguration.testDescription"}
]
},
[FINDINGS_MAPPING.SECRETS.dataKey]: {
headerItems: ["Fingerprint"],
bodyItems: [
{dataKey: "secret.fingerprint"}
]
},
[FINDINGS_MAPPING.MALWARE.dataKey]: {
headerItems: ["Malware name"],
bodyItems: [
{dataKey: "malware.malwareName"}
]
},
[FINDINGS_MAPPING.ROOTKITS.dataKey]: {
headerItems: ["Rootkit name"],
bodyItems: [
{dataKey: "rootkit.rootkitName"}
]
},
[FINDINGS_MAPPING.PACKAGES.dataKey]: {
headerItems: ["Package name", "Version"],
bodyItems: [
{dataKey: "package.name"},
{dataKey: "package.version"}
]
}
}

const FindingsImpactWidget = ({className}) => (
<FindingsTabsWidget
className={className}
findingsItems={FINDINGS_ITEMS}
title="Findings impact"
widgetName="findings-impact"
url={APIS.DASHBOARD_FINDINGS_IMPACT}
getHeaderItems={(selectedId) => {
const {headerItems=[]} = TABS_COLUMNS_MAPPING[selectedId] || {};

return (
([...headerItems, "Affected assets"])
)
}}
getBodyItems={(selectedId) => {
const {bodyItems=[]} = TABS_COLUMNS_MAPPING[selectedId] || {};

return ([...bodyItems, {dataKey: "affectedAssetsCount"}])
}}
/>
)

export default FindingsImpactWidget;
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
@import 'utils/scss_variables.module.scss';

.findings-tabs-widget {
.tabbed-widget-table-wrapper {
height: calc(100% - 90px);
overflow-y: auto;

.tabbed-widget-table {
font-size: 14px;
border-collapse: collapse;
width: 100%;

thead {
font-size: 12px;
text-align: left;
text-transform: uppercase;

}
tbody tr {
border-bottom: 1px solid $color-grey-light;
}
th {
padding-top: 10px;
white-space: nowrap;
}
td {
padding: 12px 0 5px 0;
overflow-wrap: anywhere;
}
}
}
}
56 changes: 51 additions & 5 deletions ui/src/layout/Dashboard/FindingsTabsWidget/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,46 @@
import React, { useState } from 'react';
import classnames from 'classnames';
import { get } from 'lodash';
import { useFetch } from 'hooks';
import Loader from 'components/Loader';
import Tabs from 'components/Tabs';
import IconWithTooltip from 'components/IconWithTooltip';
import { FINDINGS_MAPPING, VULNERABIITY_FINDINGS_ITEM } from 'utils/systemConsts';
import WidgetWrapper from '../WidgetWrapper';

import COLORS from 'utils/scss_variables.module.scss';

const FINDINGS_ITEMS = [VULNERABIITY_FINDINGS_ITEM, ...Object.values(FINDINGS_MAPPING).filter(({value}) => value !== FINDINGS_MAPPING.PACKAGES.value)];
import './findings-tabs-widget.scss';

const WidgetContent = ({data=[], getHeaderItems, getBodyItems, selectedId}) => {
const displayData = (data || []).slice(0, 5);

return (
<table className="tabbed-widget-table">
<thead>
<tr>
{getHeaderItems(selectedId).map((item, index, items) => (
<th key={index} style={items.length - 1 === index ? {textAlign: "right"} : {}}>{item}</th>
))}
</tr>
</thead>
<tbody>
{
displayData.map((item, index) => {
return (
<tr key={index}>
{getBodyItems(selectedId).map(({dataKey, customDisplay: CustomDisplay}, index, items) => (
<td key={index} style={items.length - 1 === index ? {textAlign: "right"} : {}}>
{!!CustomDisplay ? <CustomDisplay {...item} /> : get(item, dataKey)}
</td>
))}
</tr>
)
})
}
</tbody>
</table>
)
}

const Tab = ({widgetName, title, icon, isActive}) => (
<IconWithTooltip
Expand All @@ -19,8 +52,10 @@ const Tab = ({widgetName, title, icon, isActive}) => (
/>
)

const FindingsTabsWidget = ({widgetName, className, title, tabContent: TabContent}) => {
const WIDGET_TAB_ITEMS = FINDINGS_ITEMS.map(({dataKey, icon, title}) => (
const FindingsTabsWidget = ({widgetName, findingsItems, className, title, url, getHeaderItems, getBodyItems}) => {
const [{data, error, loading}] = useFetch(url, {urlPrefix: "ui"});

const WIDGET_TAB_ITEMS = findingsItems.map(({dataKey, icon, title}) => (
{id: dataKey, customTitle: ({isActive}) => <Tab widgetName={widgetName} title={title} icon={icon} isActive={isActive} />}
))

Expand All @@ -34,7 +69,18 @@ const FindingsTabsWidget = ({widgetName, className, title, tabContent: TabConten
onClick={({id}) => setSelectedTabId(id)}
tabItemPadding={15}
/>
<div>{!!TabContent ? <TabContent selectedTabId={selectedTabId} /> : <div style={{margin: "10px 0"}}>TBD</div>}</div>
<div className="tabbed-widget-table-wrapper">
{
loading ? <Loader absolute={false} /> : (error ? null :
<WidgetContent
data={!!data ? data[selectedTabId] : []}
getHeaderItems={getHeaderItems}
getBodyItems={getBodyItems}
selectedId={selectedTabId}
/>
)
}
</div>
</WidgetWrapper>
)
}
Expand Down
4 changes: 2 additions & 2 deletions ui/src/layout/Dashboard/FindingsTrendsWidget/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ const WidgetChart = ({data, selectedFilters}) => {
}

const FindingsTrendsWidget = ({className}) => {
const {value, label} = TIME_RANGES.HOUR;
const {value, label} = TIME_RANGES.WEEK;
const [selectedRange, setSelectedRange] = useState({value, label});

const [{data, error, loading}, fetchData] = useFetch(APIS.DASHBOARD_FINDINGS_TRENDS, {loadOnMount: false});
Expand All @@ -121,7 +121,7 @@ const FindingsTrendsWidget = ({className}) => {
]);

return (
<WidgetWrapper className={classnames("findings-trends-widget", className)} title="Findings trend">
<WidgetWrapper className={classnames("findings-trends-widget", className)} title="Findings trends">
<div className="findings-trends-widget-header">
<div className="findings-trends-widget-filters">
{
Expand Down
Loading