diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index e80af2366..2fe43c42e 100755 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -1,64 +1,17 @@ -import React from 'react'; -import {Button, Col} from 'react-bootstrap'; +import React, {Fragment} from 'react'; +import {Col, Grid, Row} from 'react-bootstrap'; import AuthComponent from '../AuthComponent'; import ReportHeader, {ReportTypes} from "../report-components/common/ReportHeader"; -import PillarGrades from "../report-components/zerotrust/PillarGrades"; -import PillarLabel from "../report-components/zerotrust/PillarLabel"; -import ResponsiveVennDiagram from "../report-components/zerotrust/venn-components/ResponsiveVennDiagram"; +import PillarsOverview from "../report-components/zerotrust/PillarOverview"; import FindingsTable from "../report-components/zerotrust/FindingsTable"; -import {SinglePillarRecommendationsStatus} from "../report-components/zerotrust/SinglePillarRecommendationsStatus"; - -let mockup = [ - { - "Conclusive": 4, - "Inconclusive": 0, - "Positive": 1, - "Unexecuted": 2, - "pillar": "Data" - }, - { - "Conclusive": 0, - "Inconclusive": 5, - "Positive": 0, - "Unexecuted": 2, - "pillar": "People" - }, - { - "Conclusive": 0, - "Inconclusive": 0, - "Positive": 6, - "Unexecuted": 3, - "pillar": "Networks" - }, - { - "Conclusive": 2, - "Inconclusive": 0, - "Positive": 1, - "Unexecuted": 1, - "pillar": "Devices" - }, - { - "Conclusive": 0, - "Inconclusive": 0, - "Positive": 0, - "Unexecuted": 0, - "pillar": "Workloads" - }, - { - "Conclusive": 0, - "Inconclusive": 2, - "Positive": 0, - "Unexecuted": 0, - "pillar": "Visibility & Analytics" - }, - { - "Conclusive": 0, - "Inconclusive": 0, - "Positive": 0, - "Unexecuted": 0, - "pillar": "Automation & Orchestration" - } -]; +import SinglePillarDirectivesStatus from "../report-components/zerotrust/SinglePillarDirectivesStatus"; +import MonkeysStillAliveWarning from "../report-components/common/MonkeysStillAliveWarning"; +import ReportLoader from "../report-components/common/ReportLoader"; +import MustRunMonkeyWarning from "../report-components/common/MustRunMonkeyWarning"; +import SecurityIssuesGlance from "../report-components/common/SecurityIssuesGlance"; +import StatusesToPillarsSummary from "../report-components/zerotrust/StatusesToPillarsSummary"; +import PrintReportButton from "../report-components/common/PrintReportButton"; +import {extractExecutionStatusFromServerResponse} from "../report-components/common/ExecutionStatus"; class ZeroTrustReportPageComponent extends AuthComponent { @@ -67,16 +20,30 @@ class ZeroTrustReportPageComponent extends AuthComponent { this.state = { allMonkeysAreDead: false, - runStarted: false + runStarted: true }; } - render() { - let res; - // Todo move to componentDidMount - this.getZeroTrustReportFromServer(res); + componentDidMount() { + this.updateMonkeysRunning().then(res => this.getZeroTrustReportFromServer(res)); + } - const content = this.generateReportContent(); + updateMonkeysRunning = () => { + return this.authFetch('/api') + .then(res => res.json()) + .then(res => { + this.setState(extractExecutionStatusFromServerResponse(res)); + return res; + }); + }; + + render() { + let content; + if (this.state.runStarted) { + content = this.generateReportContent(); + } else { + content = ; + } return ( @@ -92,63 +59,75 @@ class ZeroTrustReportPageComponent extends AuthComponent { let content; if (this.stillLoadingDataFromServer()) { - content = "Still empty"; + content = ; } else { - const pillarsSection =
-

Pillars Overview

- -
; - - const recommendationsSection =

Recommendations Status

- { - this.state.recommendations.map((recommendation) => - - ) - } -
; - - const findingSection =

Findings

-
; - - content =
- {pillarsSection} - {recommendationsSection} - {findingSection} + content =
+ {this.generateOverviewSection()} + {this.generateDirectivesSection()} + {this.generateFindingsSection()}
; } return ( -
-
- + +
+ {print();}} />

{content} -
-
{JSON.stringify(this.state.pillars, undefined, 2)}
-
- -
{JSON.stringify(this.state.recommendations, undefined, 2)}
-
-
{JSON.stringify(this.state.findings, undefined, 2)}
-
+
+ {print();}} /> +
+ ) } - stillLoadingDataFromServer() { - return typeof this.state.findings === "undefined" || typeof this.state.pillars === "undefined" || typeof this.state.recommendations === "undefined"; + generateFindingsSection() { + return (
+

Findings

+ +
); } - print() { - alert("unimplemented"); + generateDirectivesSection() { + return (
+

Directives

+ { + Object.keys(this.state.directives).map((pillar) => + + ) + } +
); + } + + generateOverviewSection() { + return (
+

Overview

+ + + + + + + + + + + + +
); + } + + stillLoadingDataFromServer() { + return typeof this.state.findings === "undefined" || typeof this.state.pillars === "undefined" || typeof this.state.directives === "undefined"; } getZeroTrustReportFromServer() { @@ -160,11 +139,11 @@ class ZeroTrustReportPageComponent extends AuthComponent { findings: res }); }); - this.authFetch('/api/report/zero_trust/recommendations') + this.authFetch('/api/report/zero_trust/directives') .then(res => res.json()) .then(res => { this.setState({ - recommendations: res + directives: res }); }); this.authFetch('/api/report/zero_trust/pillars') @@ -175,6 +154,14 @@ class ZeroTrustReportPageComponent extends AuthComponent { }); }); } + + anyIssuesFound() { + const severe = function(finding) { + return (finding.status === "Conclusive" || finding.status === "Inconclusive"); + }; + + return this.state.findings.some(severe); + } } export default ZeroTrustReportPageComponent; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js index f772e0652..824885cad 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js @@ -1,7 +1,7 @@ import React, {Component} from "react"; import PillarLabel from "./PillarLabel"; import * as PropTypes from "prop-types"; -import ResponsiveVennDiagram from "./VennDiagram"; +import ResponsiveVennDiagram from "./venn-components/ResponsiveVennDiagram"; const columns = [ { diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js index 0f154cdfe..1e0be3b16 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js @@ -7,31 +7,29 @@ import { TypographicUtilities } from './Utility.js' import './VennDiagram.css' class VennDiagram extends React.Component{ - - constructor(props_){ - - super(props_); - - this.state = { tooltip: { top: 0, left: 0, display: 'none', html: '' } } - - this.width = this.height = 512, this.zOrder = []; - + constructor(props_){ + super(props_); + + this.state = { tooltip: { top: 0, left: 0, display: 'none', html: '' } }; + + this.width = this.height = 512; + this.zOrder = []; + this.colors = ['#777777', '#D9534F', '#F0AD4E', '#5CB85C']; this.prefix = 'vennDiagram'; this.suffices = ['', '|tests are|conclusive', '|tests were|inconclusive', '|tests|performed']; this.fontStyles = [{size: Math.max(9, this.width / 32), color: 'white'}, {size: Math.max(6, this.width / 52), color: 'black'}]; this.offset = this.width / 16; - - this.thirdWidth = this.width / 3; + + this.thirdWidth = this.width / 3; this.sixthWidth = this.width / 6; - this.width2By7 = 2 * this.width / 7 + this.width2By7 = 2 * this.width / 7; this.width1By11 = this.width / 11; this.width1By28 = this.width / 28; - + this.toggle = false; - + this.layout = { - Data: { cx: 0, cy: 0, r: this.thirdWidth - this.offset * 2, offset: {x: 0, y: 0} }, People: { cx: -this.width2By7, cy: 0, r: this.sixthWidth, offset: {x: this.width1By11, y: 0} }, Networks: { cx: this.width2By7, cy: 0, r: this.sixthWidth, offset: {x: -this.width1By11, y: 0} }, @@ -39,15 +37,14 @@ class VennDiagram extends React.Component{ Workloads : { cx: 0, cy: -this.width2By7, r: this.sixthWidth, offset: {x: 0, y: this.width1By11} }, VisibilityAndAnalytics : { inner: this.thirdWidth - this.width1By28, outer: this.thirdWidth }, AutomationAndOrchestration: { inner: this.thirdWidth - this.width1By28 * 2, outer: this.thirdWidth - this.width1By28 } - }; - + /* RULE #1: All scores have to be equal 0, except Unexecuted [U] which could be also a negative integer - sum(C, I, P, U) has to be <=0 + sum(C, I, P) has to be <=0 - RULE #2: Conclusive [C] has to be > 0, + RULE #2: Conclusive [C] has to be > 0, sum(C) > 0 RULE #3: Inconclusive [I] has to be > 0 while Conclusive has to be 0, @@ -62,185 +59,150 @@ class VennDiagram extends React.Component{ this.rules = [ - { id: 'Rule #1', f: function(d_){ return d_['Conclusive'] + d_['Inconclusive'] + d_['Positive'] + d_['Unexecuted'] <= 0; } }, + { id: 'Rule #1', f: function(d_){ return d_['Conclusive'] + d_['Inconclusive'] + d_['Positive'] === 0; } }, { id: 'Rule #2', f: function(d_){ return d_['Conclusive'] > 0; } }, { id: 'Rule #3', f: function(d_){ return d_['Conclusive'] === 0 && d_['Inconclusive'] > 0; } }, { id: 'Rule #4', f: function(d_){ return d_['Positive'] + d_['Unexecuted'] >= 2 && d_['Positive'] * d_['Unexecuted'] > 0; } } ]; - - - this._onScroll = this._onScroll.bind(this); - - } - - componentDidMount() { + this._onScroll = this._onScroll.bind(this); + } + + componentDidMount() { this.parseData(); window.addEventListener('scroll', this._onScroll); - } _onMouseMove(e) { - let self = this; - + if(!this.toggle){ - let hidden = 'none'; let html = ''; let bcolor = '#DEDEDE'; - + document.querySelectorAll('circle, path').forEach((d_, i_) => { d_.setAttribute('opacity', 0.8)}); - - if(e.target.id.includes('Node')) { + + if(e.target.id.includes('Node')) { html = e.target.dataset.tooltip; this.divElement.style.cursor = 'pointer'; - hidden = 'block'; e.target.setAttribute('opacity', 0.95); + hidden = 'block'; e.target.setAttribute('opacity', 0.95); bcolor = e.target.getAttribute('fill'); - - //set highest z-index + + // Set highest z-index e.target.parentNode.parentNode.appendChild(e.target.parentNode); }else{ this.divElement.style.cursor = 'default'; - - //return z indices to default + + // Return z indices to default Object.keys(this.layout).forEach(function(d_, i_){ document.querySelector('#' + self.prefix).appendChild(document.querySelector('#' + self.prefix + 'Node_' + i_).parentNode); }) } this.setState({target: e, tooltip: { target: e.target, bcolor: bcolor, top: e.clientY + 8, left: e.clientX + 8, display: hidden, html: html } }); - } } _onScroll(e){ - this.divElement.style.cursor = 'default'; this.setState({target: null, tooltip: { target: null, bcolor: 'none', top: 0, left: 0, display: 'none', html: '' } }); - } _onClick(e) { + this.toggle = this.state.tooltip.target === e.target; - if(this.state.tooltip.target === e.target) { this.toggle = true; } else { this.toggle = false; } - //variable to external callback //e.target.parentNode.id) - } parseData(){ - let self = this; let data = []; const omit = (prop, { [prop]: _, ...rest }) => rest; this.props.pillarsGrades.forEach((d_, i_) => { - + let params = omit('pillar', d_); - let sum = Object.keys(params).reduce((sum_, key_) => sum_ + parseFloat(params[key_]||0), 0); + let sum = Object.keys(params).reduce((sum_, key_) => sum_ + parseFloat(params[key_]||0), 0); let key = TypographicUtilities.removeAmpersand(d_.pillar); let html = self.buildTooltipHtmlContent(d_); let rule = null; - + for(let j = 0; j < self.rules.length; j++){ if(self.rules[j].f(d_)) { rule = j; break; }} self.setLayoutElement(rule, key, html, d_); data.push(this.layout[key]); - - }) - - this.setState({ data: data }); - this.render(); + }); + + this.setState({ data: data }); + this.render(); } - + buildTooltipHtmlContent(object_){ return Object.keys(object_).reduce((out_, key_) => out_ + TypographicUtilities.setTitle(key_) + ': ' + object_[key_] + '\n', ''); } setLayoutElement(rule_, key_, html_, d_){ - + console.log(rule_, key_, html_, d_); if(rule_ == null) { throw Error('The node scores are invalid'); } - + if(key_ === 'Data'){ this.layout[key_].fontStyle = this.fontStyles[0]; } else {this.layout[key_].fontStyle = this.fontStyles[1]; } - - this.layout[key_].hex = this.colors[rule_]; - this.layout[key_].label = d_.pillar + this.suffices[rule_]; + + this.layout[key_].hex = this.colors[rule_]; + this.layout[key_].label = d_.pillar + this.suffices[rule_]; this.layout[key_].node = d_; this.layout[key_].tooltip = html_; - } render() { - if(this.state.data === undefined) { return null; } - else { - - //equivalent to center translate (width/2, height/2) + else { + // equivalent to center translate (width/2, height/2) let viewPortParameters = (-this.width / 2) + ' ' + (-this.height / 2) + ' ' + this.width + ' ' + this.height; let translate = 'translate(' + this.width /2 + ',' + this.height/2 + ')'; - let nodes = Object.values(this.layout).map((d_, i_) =>{ - - if(d_.hasOwnProperty('cx')){ - + let nodes = Object.values(this.layout).map((d_, i_) => { + if(d_.hasOwnProperty('cx')) { return ( - ); - - }else{ - + } else { d_.label = TypographicUtilities.removeBrokenBar(d_.label); - + return ( - ); - } - }); - - return ( + return (
this.divElement = divElement} onMouseMove={this._onMouseMove.bind(this)} onClick={this._onClick.bind(this)} > - + {nodes} - - -
- + + +
) - } - } - } VennDiagram.propTypes = { - pillarsGrades: PropTypes.array - -} +}; -export default VennDiagram; \ No newline at end of file +export default VennDiagram;