forked from p15670423/monkey
Merge pull request #412 from guardicore/400-zero-trust-mvp-venn-diagram
VennDiagram component first version is complete.
This commit is contained in:
commit
59581d3cc1
|
@ -0,0 +1,18 @@
|
|||
def get_ip_in_src_and_not_in_dst(ip_addresses, source_subnet, target_subnet):
|
||||
"""
|
||||
Finds an IP address in ip_addresses which is in source_subnet but not in target_subnet.
|
||||
:param ip_addresses: List[str]: List of IP addresses to test.
|
||||
:param source_subnet: NetworkRange: Subnet to want an IP to not be in.
|
||||
:param target_subnet: NetworkRange: Subnet we want an IP to be in.
|
||||
:return: The cross segment IP if in source but not in target, else None. Union[str, None]
|
||||
"""
|
||||
if get_ip_if_in_subnet(ip_addresses, target_subnet) is not None:
|
||||
return None
|
||||
return get_ip_if_in_subnet(ip_addresses, source_subnet)
|
||||
|
||||
|
||||
def get_ip_if_in_subnet(ip_addresses, source_subnet):
|
||||
for ip_address in ip_addresses:
|
||||
if source_subnet.is_in_range(ip_address):
|
||||
return ip_address
|
||||
return None
|
|
@ -0,0 +1,19 @@
|
|||
from common.network.network_range import *
|
||||
from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst
|
||||
from monkey_island.cc.testing.IslandTestCase import IslandTestCase
|
||||
|
||||
|
||||
class TestSegmentationUtils(IslandTestCase):
|
||||
def test_get_ip_in_src_and_not_in_dst(self):
|
||||
self.fail_if_not_testing_env()
|
||||
source = CidrRange("1.1.1.0/24")
|
||||
target = CidrRange("2.2.2.0/24")
|
||||
self.assertIsNone(get_ip_in_src_and_not_in_dst(
|
||||
[text_type("2.2.2.2")], source, target
|
||||
))
|
||||
self.assertIsNone(get_ip_in_src_and_not_in_dst(
|
||||
[text_type("3.3.3.3"), text_type("4.4.4.4")], source, target
|
||||
))
|
||||
self.assertIsNotNone(get_ip_in_src_and_not_in_dst(
|
||||
[text_type("8.8.8.8"), text_type("1.1.1.1")], source, target
|
||||
))
|
|
@ -113,7 +113,7 @@ class InfectionMonkey(object):
|
|||
if monkey_tunnel:
|
||||
monkey_tunnel.start()
|
||||
|
||||
StateTelem(False).send()
|
||||
StateTelem(is_done=False).send()
|
||||
TunnelTelem().send()
|
||||
|
||||
if WormConfiguration.collect_system_info:
|
||||
|
@ -225,7 +225,7 @@ class InfectionMonkey(object):
|
|||
InfectionMonkey.close_tunnel()
|
||||
firewall.close()
|
||||
else:
|
||||
StateTelem(False).send() # Signal the server (before closing the tunnel)
|
||||
StateTelem(is_done=True).send() # Signal the server (before closing the tunnel)
|
||||
InfectionMonkey.close_tunnel()
|
||||
firewall.close()
|
||||
if WormConfiguration.send_log_to_server:
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import abc
|
||||
import json
|
||||
import logging
|
||||
|
||||
from infection_monkey.control import ControlClient
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
__author__ = 'itay.mizeretz'
|
||||
|
||||
|
||||
|
@ -19,7 +23,9 @@ class BaseTelem(object):
|
|||
"""
|
||||
Sends telemetry to island
|
||||
"""
|
||||
ControlClient.send_telemetry(self.telem_category, self.get_data())
|
||||
data = self.get_data()
|
||||
logger.debug("Sending {} telemetry. Data: {}".format(self.telem_category, json.dumps(data)))
|
||||
ControlClient.send_telemetry(self.telem_category, data)
|
||||
|
||||
@abc.abstractproperty
|
||||
def telem_category(self):
|
||||
|
|
|
@ -32,6 +32,8 @@ class Finding(Document):
|
|||
test = StringField(required=True, choices=TESTS)
|
||||
status = StringField(required=True, choices=ORDERED_TEST_STATUSES)
|
||||
events = EmbeddedDocumentListField(document_type=Event)
|
||||
# http://docs.mongoengine.org/guide/defining-documents.html#document-inheritance
|
||||
meta = {'allow_inheritance': True}
|
||||
|
||||
# LOGIC
|
||||
def get_test_explanation(self):
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
from mongoengine import StringField
|
||||
|
||||
from common.data.zero_trust_consts import TEST_SEGMENTATION, STATUS_CONCLUSIVE, STATUS_POSITIVE
|
||||
from monkey_island.cc.models.zero_trust.finding import Finding
|
||||
|
||||
|
||||
def need_to_overwrite_status(saved_status, new_status):
|
||||
return (saved_status == STATUS_POSITIVE) and (new_status == STATUS_CONCLUSIVE)
|
||||
|
||||
|
||||
class SegmentationFinding(Finding):
|
||||
"""
|
||||
trying to add conclusive:
|
||||
If the finding doesn't exist at all: create conclusive
|
||||
else:
|
||||
if positive, turn to conclusive
|
||||
add event
|
||||
|
||||
trying to add positive:
|
||||
If the finding doesn't exist at all: create positive
|
||||
else: add event
|
||||
"""
|
||||
first_subnet = StringField()
|
||||
second_subnet = StringField()
|
||||
|
||||
@staticmethod
|
||||
def create_or_add_to_existing_finding(subnets, status, segmentation_event):
|
||||
assert len(subnets) == 2
|
||||
|
||||
# Sort them so A -> B and B -> A segmentation findings will be the same one.
|
||||
subnets.sort()
|
||||
|
||||
existing_findings = SegmentationFinding.objects(first_subnet=subnets[0], second_subnet=subnets[1])
|
||||
|
||||
if len(existing_findings) == 0:
|
||||
# No finding exists - create.
|
||||
new_finding = SegmentationFinding(
|
||||
first_subnet=subnets[0],
|
||||
second_subnet=subnets[1],
|
||||
test=TEST_SEGMENTATION,
|
||||
status=status,
|
||||
events=[segmentation_event]
|
||||
)
|
||||
new_finding.save()
|
||||
else:
|
||||
# A finding exists (should be one). Add the event to it.
|
||||
assert len(existing_findings) == 1
|
||||
existing_finding = existing_findings[0]
|
||||
existing_finding.events.append(segmentation_event)
|
||||
if need_to_overwrite_status(existing_finding.status, status):
|
||||
existing_finding.status = status
|
||||
existing_finding.save()
|
|
@ -0,0 +1,52 @@
|
|||
from common.data.zero_trust_consts import STATUS_CONCLUSIVE, EVENT_TYPE_MONKEY_NETWORK
|
||||
from monkey_island.cc.models.zero_trust.event import Event
|
||||
from monkey_island.cc.testing.IslandTestCase import IslandTestCase
|
||||
from monkey_island.cc.models.zero_trust.segmentation_finding import SegmentationFinding
|
||||
|
||||
|
||||
class TestSegmentationFinding(IslandTestCase):
|
||||
def test_create_or_add_to_existing_finding(self):
|
||||
self.fail_if_not_testing_env()
|
||||
self.clean_finding_db()
|
||||
|
||||
first_segment = "1.1.1.0/24"
|
||||
second_segment = "2.2.2.0-2.2.2.254"
|
||||
third_segment = "3.3.3.3"
|
||||
event = Event.create_event("bla", "bla", EVENT_TYPE_MONKEY_NETWORK)
|
||||
|
||||
SegmentationFinding.create_or_add_to_existing_finding(
|
||||
subnets=[first_segment, second_segment],
|
||||
status=STATUS_CONCLUSIVE,
|
||||
segmentation_event=event
|
||||
)
|
||||
|
||||
self.assertEquals(len(SegmentationFinding.objects()), 1)
|
||||
self.assertEquals(len(SegmentationFinding.objects()[0].events), 1)
|
||||
|
||||
SegmentationFinding.create_or_add_to_existing_finding(
|
||||
# !!! REVERSE ORDER
|
||||
subnets=[second_segment, first_segment],
|
||||
status=STATUS_CONCLUSIVE,
|
||||
segmentation_event=event
|
||||
)
|
||||
|
||||
self.assertEquals(len(SegmentationFinding.objects()), 1)
|
||||
self.assertEquals(len(SegmentationFinding.objects()[0].events), 2)
|
||||
|
||||
SegmentationFinding.create_or_add_to_existing_finding(
|
||||
# !!! REVERSE ORDER
|
||||
subnets=[first_segment, third_segment],
|
||||
status=STATUS_CONCLUSIVE,
|
||||
segmentation_event=event
|
||||
)
|
||||
|
||||
self.assertEquals(len(SegmentationFinding.objects()), 2)
|
||||
|
||||
SegmentationFinding.create_or_add_to_existing_finding(
|
||||
# !!! REVERSE ORDER
|
||||
subnets=[second_segment, third_segment],
|
||||
status=STATUS_CONCLUSIVE,
|
||||
segmentation_event=event
|
||||
)
|
||||
|
||||
self.assertEquals(len(SegmentationFinding.objects()), 3)
|
|
@ -0,0 +1,5 @@
|
|||
from monkey_island.cc.services.config import ConfigService
|
||||
|
||||
|
||||
def get_config_network_segments_as_subnet_groups():
|
||||
return [ConfigService.get_config_value(['basic_network', 'network_analysis', 'inaccessible_subnets'])]
|
|
@ -9,10 +9,12 @@ from enum import Enum
|
|||
|
||||
from six import text_type
|
||||
|
||||
from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst
|
||||
from monkey_island.cc.database import mongo
|
||||
from monkey_island.cc.models import Monkey
|
||||
from monkey_island.cc.report_exporter_manager import ReportExporterManager
|
||||
from monkey_island.cc.services.config import ConfigService
|
||||
from monkey_island.cc.services.configuration.utils import get_config_network_segments_as_subnet_groups
|
||||
from monkey_island.cc.services.edge import EdgeService
|
||||
from monkey_island.cc.services.node import NodeService
|
||||
from monkey_island.cc.utils import local_ip_addresses, get_subnets
|
||||
|
@ -423,23 +425,6 @@ class ReportService:
|
|||
|
||||
return issues
|
||||
|
||||
@staticmethod
|
||||
def get_ip_in_src_and_not_in_dst(ip_addresses, source_subnet, target_subnet):
|
||||
"""
|
||||
Finds an IP address in ip_addresses which is in source_subnet but not in target_subnet.
|
||||
:param ip_addresses: List of IP addresses to test.
|
||||
:param source_subnet: Subnet to want an IP to not be in.
|
||||
:param target_subnet: Subnet we want an IP to be in.
|
||||
:return:
|
||||
"""
|
||||
for ip_address in ip_addresses:
|
||||
if target_subnet.is_in_range(ip_address):
|
||||
return None
|
||||
for ip_address in ip_addresses:
|
||||
if source_subnet.is_in_range(ip_address):
|
||||
return ip_address
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_cross_segment_issues_of_single_machine(source_subnet_range, target_subnet_range):
|
||||
"""
|
||||
|
@ -502,9 +487,9 @@ class ReportService:
|
|||
target_ip = scan['data']['machine']['ip_addr']
|
||||
if target_subnet_range.is_in_range(text_type(target_ip)):
|
||||
monkey = NodeService.get_monkey_by_guid(scan['monkey_guid'])
|
||||
cross_segment_ip = ReportService.get_ip_in_src_and_not_in_dst(monkey['ip_addresses'],
|
||||
source_subnet_range,
|
||||
target_subnet_range)
|
||||
cross_segment_ip = get_ip_in_src_and_not_in_dst(monkey['ip_addresses'],
|
||||
source_subnet_range,
|
||||
target_subnet_range)
|
||||
|
||||
if cross_segment_ip is not None:
|
||||
cross_segment_issues.append(
|
||||
|
@ -552,7 +537,7 @@ class ReportService:
|
|||
cross_segment_issues = []
|
||||
|
||||
# For now the feature is limited to 1 group.
|
||||
subnet_groups = [ConfigService.get_config_value(['basic_network', 'network_analysis', 'inaccessible_subnets'])]
|
||||
subnet_groups = get_config_network_segments_as_subnet_groups()
|
||||
|
||||
for subnet_group in subnet_groups:
|
||||
cross_segment_issues += ReportService.get_cross_segment_issues_per_subnet_group(scans, subnet_group)
|
||||
|
|
|
@ -3,11 +3,13 @@ import copy
|
|||
from monkey_island.cc.database import mongo
|
||||
from monkey_island.cc.services.telemetry.processing.utils import get_edge_by_scan_or_exploit_telemetry
|
||||
from monkey_island.cc.services.telemetry.zero_trust_tests.data_endpoints import test_open_data_endpoints
|
||||
from monkey_island.cc.services.telemetry.zero_trust_tests.segmentation import test_segmentation_violation
|
||||
|
||||
|
||||
def process_scan_telemetry(telemetry_json):
|
||||
update_edges_and_nodes_based_on_scan_telemetry(telemetry_json)
|
||||
test_open_data_endpoints(telemetry_json)
|
||||
test_segmentation_violation(telemetry_json)
|
||||
|
||||
|
||||
def update_edges_and_nodes_based_on_scan_telemetry(telemetry_json):
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
from monkey_island.cc.services.node import NodeService
|
||||
from monkey_island.cc.services.telemetry.zero_trust_tests.segmentation import \
|
||||
test_positive_findings_for_unreached_segments
|
||||
|
||||
|
||||
def process_state_telemetry(telemetry_json):
|
||||
|
@ -8,3 +10,6 @@ def process_state_telemetry(telemetry_json):
|
|||
NodeService.set_monkey_dead(monkey, True)
|
||||
else:
|
||||
NodeService.set_monkey_dead(monkey, False)
|
||||
|
||||
if telemetry_json['data']['done']:
|
||||
test_positive_findings_for_unreached_segments(telemetry_json)
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
import itertools
|
||||
from six import text_type
|
||||
|
||||
from common.data.zero_trust_consts import STATUS_CONCLUSIVE, EVENT_TYPE_MONKEY_NETWORK, STATUS_POSITIVE, \
|
||||
EVENT_TYPE_ISLAND
|
||||
from common.network.network_range import NetworkRange
|
||||
from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst, get_ip_if_in_subnet
|
||||
from monkey_island.cc.models import Monkey
|
||||
from monkey_island.cc.models.zero_trust.event import Event
|
||||
from monkey_island.cc.models.zero_trust.segmentation_finding import SegmentationFinding
|
||||
from monkey_island.cc.services.configuration.utils import get_config_network_segments_as_subnet_groups
|
||||
|
||||
SEGMENTATION_VIOLATION_EVENT_TEXT = \
|
||||
"Segmentation violation! Monkey on '{hostname}', with the {source_ip} IP address (in segment {source_seg}) " \
|
||||
"managed to communicate cross segment to {target_ip} (in segment {target_seg})."
|
||||
|
||||
|
||||
def is_segmentation_violation(current_monkey, target_ip, source_subnet, target_subnet):
|
||||
if source_subnet == target_subnet:
|
||||
return False
|
||||
source_subnet_range = NetworkRange.get_range_obj(source_subnet)
|
||||
target_subnet_range = NetworkRange.get_range_obj(target_subnet)
|
||||
|
||||
if target_subnet_range.is_in_range(text_type(target_ip)):
|
||||
cross_segment_ip = get_ip_in_src_and_not_in_dst(
|
||||
current_monkey.ip_addresses,
|
||||
source_subnet_range,
|
||||
target_subnet_range)
|
||||
|
||||
return cross_segment_ip is not None
|
||||
|
||||
|
||||
def test_segmentation_violation(scan_telemetry_json):
|
||||
# TODO - lower code duplication between this and report.py.
|
||||
# TODO - single machine
|
||||
current_monkey = Monkey.get_single_monkey_by_guid(scan_telemetry_json['monkey_guid'])
|
||||
target_ip = scan_telemetry_json['data']['machine']['ip_addr']
|
||||
subnet_groups = get_config_network_segments_as_subnet_groups()
|
||||
for subnet_group in subnet_groups:
|
||||
subnet_pairs = itertools.product(subnet_group, subnet_group)
|
||||
for subnet_pair in subnet_pairs:
|
||||
source_subnet = subnet_pair[0]
|
||||
target_subnet = subnet_pair[1]
|
||||
if is_segmentation_violation(current_monkey, target_ip, source_subnet, target_subnet):
|
||||
event = get_segmentation_violation_event(current_monkey, source_subnet, target_ip, target_subnet)
|
||||
SegmentationFinding.create_or_add_to_existing_finding(
|
||||
subnets=[source_subnet, target_subnet],
|
||||
status=STATUS_CONCLUSIVE,
|
||||
segmentation_event=event
|
||||
)
|
||||
|
||||
|
||||
def get_segmentation_violation_event(current_monkey, source_subnet, target_ip, target_subnet):
|
||||
return Event.create_event(
|
||||
title="Segmentation event",
|
||||
message=SEGMENTATION_VIOLATION_EVENT_TEXT.format(
|
||||
hostname=current_monkey.hostname,
|
||||
source_ip=get_ip_if_in_subnet(current_monkey.ip_addresses, NetworkRange.get_range_obj(source_subnet)),
|
||||
source_seg=source_subnet,
|
||||
target_ip=target_ip,
|
||||
target_seg=target_subnet
|
||||
),
|
||||
event_type=EVENT_TYPE_MONKEY_NETWORK
|
||||
)
|
||||
|
||||
|
||||
def test_positive_findings_for_unreached_segments(state_telemetry_json):
|
||||
flat_all_subnets = [item for sublist in get_config_network_segments_as_subnet_groups() for item in sublist]
|
||||
current_monkey = Monkey.get_single_monkey_by_guid(state_telemetry_json['monkey_guid'])
|
||||
create_or_add_findings_for_all_pairs(flat_all_subnets, current_monkey)
|
||||
|
||||
|
||||
def create_or_add_findings_for_all_pairs(all_subnets, current_monkey):
|
||||
# Filter the subnets that this monkey is part of.
|
||||
this_monkey_subnets = []
|
||||
for subnet in all_subnets:
|
||||
if get_ip_if_in_subnet(current_monkey.ip_addresses, NetworkRange.get_range_obj(subnet)) is not None:
|
||||
this_monkey_subnets.append(subnet)
|
||||
|
||||
# Get all the other subnets.
|
||||
other_subnets = list(set(all_subnets) - set(this_monkey_subnets))
|
||||
|
||||
# Calculate the cartesian product - (this monkey subnets X other subnets). These pairs are the pairs that the monkey
|
||||
# should have tested.
|
||||
all_subnets_pairs_for_this_monkey = itertools.product(this_monkey_subnets, other_subnets)
|
||||
|
||||
for subnet_pair in all_subnets_pairs_for_this_monkey:
|
||||
SegmentationFinding.create_or_add_to_existing_finding(
|
||||
subnets=list(subnet_pair),
|
||||
status=STATUS_POSITIVE,
|
||||
segmentation_event=Event.create_event(
|
||||
"Segmentation test done",
|
||||
message="Monkey on {hostname} is done attempting cross-segment communications from `{src_seg}` "
|
||||
"segments to `{dst_seg}` segments.".format(
|
||||
hostname=current_monkey.hostname,
|
||||
src_seg=subnet_pair[0],
|
||||
dst_seg=subnet_pair[1]),
|
||||
event_type=EVENT_TYPE_ISLAND
|
||||
)
|
||||
)
|
|
@ -0,0 +1,51 @@
|
|||
import uuid
|
||||
|
||||
from common.data.zero_trust_consts import TEST_SEGMENTATION, STATUS_POSITIVE, STATUS_CONCLUSIVE, \
|
||||
EVENT_TYPE_MONKEY_NETWORK
|
||||
from monkey_island.cc.models import Monkey
|
||||
from monkey_island.cc.models.zero_trust.event import Event
|
||||
from monkey_island.cc.models.zero_trust.finding import Finding
|
||||
from monkey_island.cc.models.zero_trust.segmentation_finding import SegmentationFinding
|
||||
from monkey_island.cc.services.telemetry.zero_trust_tests.segmentation import create_or_add_findings_for_all_pairs
|
||||
from monkey_island.cc.testing.IslandTestCase import IslandTestCase
|
||||
|
||||
FIRST_SUBNET = "1.1.1.1"
|
||||
SECOND_SUBNET = "2.2.2.0/24"
|
||||
THIRD_SUBNET = "3.3.3.3-3.3.3.200"
|
||||
|
||||
|
||||
class TestSegmentationTests(IslandTestCase):
|
||||
def test_create_findings_for_all_done_pairs(self):
|
||||
self.fail_if_not_testing_env()
|
||||
self.clean_finding_db()
|
||||
|
||||
all_subnets = [FIRST_SUBNET, SECOND_SUBNET, THIRD_SUBNET]
|
||||
|
||||
monkey = Monkey(
|
||||
guid=str(uuid.uuid4()),
|
||||
ip_addresses=[FIRST_SUBNET])
|
||||
|
||||
# no findings
|
||||
self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION)), 0)
|
||||
|
||||
# This is like the monkey is done and sent done telem
|
||||
create_or_add_findings_for_all_pairs(all_subnets, monkey)
|
||||
|
||||
# There are 2 subnets in which the monkey is NOT
|
||||
self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION, status=STATUS_POSITIVE)), 2)
|
||||
|
||||
# This is a monkey from 2nd subnet communicated with 1st subnet.
|
||||
SegmentationFinding.create_or_add_to_existing_finding(
|
||||
[FIRST_SUBNET, SECOND_SUBNET],
|
||||
STATUS_CONCLUSIVE,
|
||||
Event.create_event(title="sdf", message="asd", event_type=EVENT_TYPE_MONKEY_NETWORK)
|
||||
)
|
||||
|
||||
print("Printing all segmentation findings")
|
||||
all_findings = Finding.objects(test=TEST_SEGMENTATION)
|
||||
for f in all_findings:
|
||||
print(f.to_json())
|
||||
|
||||
self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION, status=STATUS_POSITIVE)), 1)
|
||||
self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION, status=STATUS_CONCLUSIVE)), 1)
|
||||
self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION)), 2)
|
|
@ -3087,8 +3087,7 @@
|
|||
"commander": {
|
||||
"version": "2.15.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
|
||||
"integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
|
||||
"dev": true
|
||||
"integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag=="
|
||||
},
|
||||
"commondir": {
|
||||
"version": "1.0.1",
|
||||
|
@ -3657,6 +3656,270 @@
|
|||
"es5-ext": "^0.10.9"
|
||||
}
|
||||
},
|
||||
"d3": {
|
||||
"version": "5.11.0",
|
||||
"resolved": "https://registry.npmjs.org/d3/-/d3-5.11.0.tgz",
|
||||
"integrity": "sha512-LXgMVUAEAzQh6WfEEOa8tJX4RA64ZJ6twC3CJ+Xzid+fXWLTZkkglagXav/eOoQgzQi5rzV0xC4Sfspd6hFDHA==",
|
||||
"requires": {
|
||||
"d3-array": "1",
|
||||
"d3-axis": "1",
|
||||
"d3-brush": "1",
|
||||
"d3-chord": "1",
|
||||
"d3-collection": "1",
|
||||
"d3-color": "1",
|
||||
"d3-contour": "1",
|
||||
"d3-dispatch": "1",
|
||||
"d3-drag": "1",
|
||||
"d3-dsv": "1",
|
||||
"d3-ease": "1",
|
||||
"d3-fetch": "1",
|
||||
"d3-force": "1",
|
||||
"d3-format": "1",
|
||||
"d3-geo": "1",
|
||||
"d3-hierarchy": "1",
|
||||
"d3-interpolate": "1",
|
||||
"d3-path": "1",
|
||||
"d3-polygon": "1",
|
||||
"d3-quadtree": "1",
|
||||
"d3-random": "1",
|
||||
"d3-scale": "2",
|
||||
"d3-scale-chromatic": "1",
|
||||
"d3-selection": "1",
|
||||
"d3-shape": "1",
|
||||
"d3-time": "1",
|
||||
"d3-time-format": "2",
|
||||
"d3-timer": "1",
|
||||
"d3-transition": "1",
|
||||
"d3-voronoi": "1",
|
||||
"d3-zoom": "1"
|
||||
}
|
||||
},
|
||||
"d3-array": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz",
|
||||
"integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw=="
|
||||
},
|
||||
"d3-axis": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.12.tgz",
|
||||
"integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ=="
|
||||
},
|
||||
"d3-brush": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.3.tgz",
|
||||
"integrity": "sha512-v8bbYyCFKjyCzFk/tdWqXwDykY8YWqhXYjcYxfILIit085VZOpj4XJKOMccTsvWxgzSLMJQg5SiqHjslsipEDg==",
|
||||
"requires": {
|
||||
"d3-dispatch": "1",
|
||||
"d3-drag": "1",
|
||||
"d3-interpolate": "1",
|
||||
"d3-selection": "1",
|
||||
"d3-transition": "1"
|
||||
}
|
||||
},
|
||||
"d3-chord": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.6.tgz",
|
||||
"integrity": "sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==",
|
||||
"requires": {
|
||||
"d3-array": "1",
|
||||
"d3-path": "1"
|
||||
}
|
||||
},
|
||||
"d3-collection": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz",
|
||||
"integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A=="
|
||||
},
|
||||
"d3-color": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.3.0.tgz",
|
||||
"integrity": "sha512-NHODMBlj59xPAwl2BDiO2Mog6V+PrGRtBfWKqKRrs9MCqlSkIEb0Z/SfY7jW29ReHTDC/j+vwXhnZcXI3+3fbg=="
|
||||
},
|
||||
"d3-contour": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-1.3.2.tgz",
|
||||
"integrity": "sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==",
|
||||
"requires": {
|
||||
"d3-array": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"d3-dispatch": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.5.tgz",
|
||||
"integrity": "sha512-vwKx+lAqB1UuCeklr6Jh1bvC4SZgbSqbkGBLClItFBIYH4vqDJCA7qfoy14lXmJdnBOdxndAMxjCbImJYW7e6g=="
|
||||
},
|
||||
"d3-drag": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.4.tgz",
|
||||
"integrity": "sha512-ICPurDETFAelF1CTHdIyiUM4PsyZLaM+7oIBhmyP+cuVjze5vDZ8V//LdOFjg0jGnFIZD/Sfmk0r95PSiu78rw==",
|
||||
"requires": {
|
||||
"d3-dispatch": "1",
|
||||
"d3-selection": "1"
|
||||
}
|
||||
},
|
||||
"d3-dsv": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.1.1.tgz",
|
||||
"integrity": "sha512-1EH1oRGSkeDUlDRbhsFytAXU6cAmXFzc52YUe6MRlPClmWb85MP1J5x+YJRzya4ynZWnbELdSAvATFW/MbxaXw==",
|
||||
"requires": {
|
||||
"commander": "2",
|
||||
"iconv-lite": "0.4",
|
||||
"rw": "1"
|
||||
}
|
||||
},
|
||||
"d3-ease": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.5.tgz",
|
||||
"integrity": "sha512-Ct1O//ly5y5lFM9YTdu+ygq7LleSgSE4oj7vUt9tPLHUi8VCV7QoizGpdWRWAwCO9LdYzIrQDg97+hGVdsSGPQ=="
|
||||
},
|
||||
"d3-fetch": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.1.2.tgz",
|
||||
"integrity": "sha512-S2loaQCV/ZeyTyIF2oP8D1K9Z4QizUzW7cWeAOAS4U88qOt3Ucf6GsmgthuYSdyB2HyEm4CeGvkQxWsmInsIVA==",
|
||||
"requires": {
|
||||
"d3-dsv": "1"
|
||||
}
|
||||
},
|
||||
"d3-force": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz",
|
||||
"integrity": "sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==",
|
||||
"requires": {
|
||||
"d3-collection": "1",
|
||||
"d3-dispatch": "1",
|
||||
"d3-quadtree": "1",
|
||||
"d3-timer": "1"
|
||||
}
|
||||
},
|
||||
"d3-format": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.3.2.tgz",
|
||||
"integrity": "sha512-Z18Dprj96ExragQ0DeGi+SYPQ7pPfRMtUXtsg/ChVIKNBCzjO8XYJvRTC1usblx52lqge56V5ect+frYTQc8WQ=="
|
||||
},
|
||||
"d3-geo": {
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.11.6.tgz",
|
||||
"integrity": "sha512-z0J8InXR9e9wcgNtmVnPTj0TU8nhYT6lD/ak9may2PdKqXIeHUr8UbFLoCtrPYNsjv6YaLvSDQVl578k6nm7GA==",
|
||||
"requires": {
|
||||
"d3-array": "1"
|
||||
}
|
||||
},
|
||||
"d3-hierarchy": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.8.tgz",
|
||||
"integrity": "sha512-L+GHMSZNwTpiq4rt9GEsNcpLa4M96lXMR8M/nMG9p5hBE0jy6C+3hWtyZMenPQdwla249iJy7Nx0uKt3n+u9+w=="
|
||||
},
|
||||
"d3-interpolate": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.3.2.tgz",
|
||||
"integrity": "sha512-NlNKGopqaz9qM1PXh9gBF1KSCVh+jSFErrSlD/4hybwoNX/gt1d8CDbDW+3i+5UOHhjC6s6nMvRxcuoMVNgL2w==",
|
||||
"requires": {
|
||||
"d3-color": "1"
|
||||
}
|
||||
},
|
||||
"d3-path": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.8.tgz",
|
||||
"integrity": "sha512-J6EfUNwcMQ+aM5YPOB8ZbgAZu6wc82f/0WFxrxwV6Ll8wBwLaHLKCqQ5Imub02JriCVVdPjgI+6P3a4EWJCxAg=="
|
||||
},
|
||||
"d3-polygon": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.5.tgz",
|
||||
"integrity": "sha512-RHhh1ZUJZfhgoqzWWuRhzQJvO7LavchhitSTHGu9oj6uuLFzYZVeBzaWTQ2qSO6bz2w55RMoOCf0MsLCDB6e0w=="
|
||||
},
|
||||
"d3-quadtree": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.6.tgz",
|
||||
"integrity": "sha512-NUgeo9G+ENQCQ1LsRr2qJg3MQ4DJvxcDNCiohdJGHt5gRhBW6orIB5m5FJ9kK3HNL8g9F4ERVoBzcEwQBfXWVA=="
|
||||
},
|
||||
"d3-random": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.2.tgz",
|
||||
"integrity": "sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ=="
|
||||
},
|
||||
"d3-scale": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz",
|
||||
"integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==",
|
||||
"requires": {
|
||||
"d3-array": "^1.2.0",
|
||||
"d3-collection": "1",
|
||||
"d3-format": "1",
|
||||
"d3-interpolate": "1",
|
||||
"d3-time": "1",
|
||||
"d3-time-format": "2"
|
||||
}
|
||||
},
|
||||
"d3-scale-chromatic": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz",
|
||||
"integrity": "sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg==",
|
||||
"requires": {
|
||||
"d3-color": "1",
|
||||
"d3-interpolate": "1"
|
||||
}
|
||||
},
|
||||
"d3-selection": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.0.tgz",
|
||||
"integrity": "sha512-EYVwBxQGEjLCKF2pJ4+yrErskDnz5v403qvAid96cNdCMr8rmCYfY5RGzWz24mdIbxmDf6/4EAH+K9xperD5jg=="
|
||||
},
|
||||
"d3-shape": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.5.tgz",
|
||||
"integrity": "sha512-VKazVR3phgD+MUCldapHD7P9kcrvPcexeX/PkMJmkUov4JM8IxsSg1DvbYoYich9AtdTsa5nNk2++ImPiDiSxg==",
|
||||
"requires": {
|
||||
"d3-path": "1"
|
||||
}
|
||||
},
|
||||
"d3-time": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.0.11.tgz",
|
||||
"integrity": "sha512-Z3wpvhPLW4vEScGeIMUckDW7+3hWKOQfAWg/U7PlWBnQmeKQ00gCUsTtWSYulrKNA7ta8hJ+xXc6MHrMuITwEw=="
|
||||
},
|
||||
"d3-time-format": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.1.3.tgz",
|
||||
"integrity": "sha512-6k0a2rZryzGm5Ihx+aFMuO1GgelgIz+7HhB4PH4OEndD5q2zGn1mDfRdNrulspOfR6JXkb2sThhDK41CSK85QA==",
|
||||
"requires": {
|
||||
"d3-time": "1"
|
||||
}
|
||||
},
|
||||
"d3-timer": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.9.tgz",
|
||||
"integrity": "sha512-rT34J5HnQUHhcLvhSB9GjCkN0Ddd5Y8nCwDBG2u6wQEeYxT/Lf51fTFFkldeib/sE/J0clIe0pnCfs6g/lRbyg=="
|
||||
},
|
||||
"d3-transition": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.2.0.tgz",
|
||||
"integrity": "sha512-VJ7cmX/FPIPJYuaL2r1o1EMHLttvoIuZhhuAlRoOxDzogV8iQS6jYulDm3xEU3TqL80IZIhI551/ebmCMrkvhw==",
|
||||
"requires": {
|
||||
"d3-color": "1",
|
||||
"d3-dispatch": "1",
|
||||
"d3-ease": "1",
|
||||
"d3-interpolate": "1",
|
||||
"d3-selection": "^1.1.0",
|
||||
"d3-timer": "1"
|
||||
}
|
||||
},
|
||||
"d3-voronoi": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz",
|
||||
"integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg=="
|
||||
},
|
||||
"d3-zoom": {
|
||||
"version": "1.8.3",
|
||||
"resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.8.3.tgz",
|
||||
"integrity": "sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==",
|
||||
"requires": {
|
||||
"d3-dispatch": "1",
|
||||
"d3-drag": "1",
|
||||
"d3-interpolate": "1",
|
||||
"d3-selection": "1",
|
||||
"d3-transition": "1"
|
||||
}
|
||||
},
|
||||
"dashdash": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
||||
|
@ -8419,8 +8682,7 @@
|
|||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
|
@ -8441,14 +8703,12 @@
|
|||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
|
@ -8463,20 +8723,17 @@
|
|||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
|
@ -8593,8 +8850,7 @@
|
|||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
|
@ -8606,7 +8862,6 @@
|
|||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
|
@ -8621,7 +8876,6 @@
|
|||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
|
@ -8629,14 +8883,12 @@
|
|||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.2.4",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.1",
|
||||
"yallist": "^3.0.0"
|
||||
|
@ -8655,7 +8907,6 @@
|
|||
"version": "0.5.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
|
@ -8736,8 +8987,7 @@
|
|||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
|
@ -8749,7 +8999,6 @@
|
|||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
|
@ -8835,8 +9084,7 @@
|
|||
"safe-buffer": {
|
||||
"version": "5.1.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
|
@ -8872,7 +9120,6 @@
|
|||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
|
@ -8892,7 +9139,6 @@
|
|||
"version": "3.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
|
@ -8936,14 +9182,12 @@
|
|||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -15091,6 +15335,11 @@
|
|||
"aproba": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"rw": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
|
||||
"integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q="
|
||||
},
|
||||
"rxjs": {
|
||||
"version": "6.3.3",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz",
|
||||
|
@ -19041,6 +19290,7 @@
|
|||
"version": "1.1.11",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"balanced-match": "1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
|
@ -19209,6 +19459,7 @@
|
|||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
|
|
|
@ -70,6 +70,7 @@
|
|||
"bootstrap": "3.4.1",
|
||||
"classnames": "^2.2.6",
|
||||
"core-js": "^2.5.7",
|
||||
"d3": "^5.11.0",
|
||||
"downloadjs": "^1.4.7",
|
||||
"fetch": "^1.1.0",
|
||||
"file-saver": "^2.0.2",
|
||||
|
|
0
monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js
Normal file → Executable file
0
monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js
Normal file → Executable file
|
@ -1,7 +1,7 @@
|
|||
import React, {Component} from "react";
|
||||
import PillarLabel from "./PillarLabel";
|
||||
import PaginatedTable from "../common/PaginatedTable";
|
||||
import * as PropTypes from "prop-types";
|
||||
import ResponsiveVennDiagram from "./venn-components/ResponsiveVennDiagram";
|
||||
|
||||
const columns = [
|
||||
{
|
||||
|
@ -20,13 +20,8 @@ const columns = [
|
|||
|
||||
class PillarOverview extends Component {
|
||||
render() {
|
||||
const data = this.props.grades.map((grade) => {
|
||||
const newGrade = JSON.parse(JSON.stringify(grade));
|
||||
newGrade.pillar = {name: grade.pillar, status: this.props.pillarsToStatuses[grade.pillar]};
|
||||
return newGrade;
|
||||
});
|
||||
return (<div id={this.constructor.name}>
|
||||
<PaginatedTable data={data} columns={columns} pageSize={10}/>
|
||||
<ResponsiveVennDiagram pillarsGrades={this.props.grades} />
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
@ -35,5 +30,4 @@ export default PillarOverview;
|
|||
|
||||
PillarOverview.propTypes = {
|
||||
grades: PropTypes.array,
|
||||
status: PropTypes.string,
|
||||
};
|
||||
|
|
BIN
monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/.DS_Store
vendored
Normal file
BIN
monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/.DS_Store
vendored
Normal file
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types';
|
||||
import * as d3 from 'd3'
|
||||
|
||||
class ArcNode extends React.Component {
|
||||
render() {
|
||||
let {prefix, index, data} = this.props;
|
||||
|
||||
let arc = d3.arc().innerRadius(data.inner).outerRadius(data.outer).startAngle(0).endAngle(Math.PI * 2.0);
|
||||
let id = prefix + 'Node_' + index;
|
||||
|
||||
return (
|
||||
<g transform={'rotate(180)'} id={data.node.pillar}>
|
||||
<path
|
||||
|
||||
id={prefix + 'Node_' + index}
|
||||
className={'arcNode'}
|
||||
data-tooltip={data.tooltip}
|
||||
d={arc()}
|
||||
fill={data.hex}
|
||||
|
||||
/>
|
||||
<text x={0} dy={data.fontStyle.size * 1.2} fontSize={data.fontStyle.size} textAnchor='middle'
|
||||
pointerEvents={'none'}>
|
||||
<textPath href={'#' + id} startOffset={'26.4%'}>
|
||||
{data.label}
|
||||
</textPath>
|
||||
</text>
|
||||
</g>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ArcNode.propTypes = {
|
||||
data: PropTypes.object
|
||||
};
|
||||
|
||||
export default ArcNode;
|
|
@ -0,0 +1,43 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
class CircularNode extends React.Component {
|
||||
render() {
|
||||
let {prefix, index, data} = this.props;
|
||||
|
||||
let tspans = data.label.split("|").map((d_, i_) => {
|
||||
let halfTextHeight = data.fontStyle.size * data.label.split("|").length / 2;
|
||||
let key = 'vennDiagramCircularNode' + index + '_Tspan' + i_;
|
||||
|
||||
return (
|
||||
<tspan key={key} x={data.offset.x} y={data.offset.y - halfTextHeight + i_ * data.fontStyle.size}>{d_}</tspan>
|
||||
);
|
||||
});
|
||||
|
||||
let translate = 'translate(' + data.cx + ',' + data.cy + ')';
|
||||
|
||||
return (
|
||||
<g transform={translate} id={data.node.pillar}>
|
||||
<circle
|
||||
id={prefix + 'Node_' + index}
|
||||
className={'circularNode'}
|
||||
data-tooltip={data.tooltip}
|
||||
r={data.r}
|
||||
opacity={0.8}
|
||||
fill={data.hex}
|
||||
/>
|
||||
<text textAnchor='middle' fill={data.fontStyle.color} dominantBaseline={'middle'}
|
||||
fontSize={data.fontStyle.size + 'px'} pointerEvents={'none'}>
|
||||
{tspans}
|
||||
</text>
|
||||
</g>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
CircularNode.propTypes = {
|
||||
index: PropTypes.number,
|
||||
data: PropTypes.object
|
||||
};
|
||||
|
||||
export default CircularNode;
|
|
@ -0,0 +1,28 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Dimensions from 'react-dimensions'
|
||||
import VennDiagram from './VennDiagram'
|
||||
|
||||
const VENN_MIN_WIDTH = '300px';
|
||||
|
||||
class ResponsiveVennDiagram extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {pillarsGrades} = this.props;
|
||||
|
||||
return (
|
||||
<div ref={this.divElement} style={{textAlign: 'center'}}>
|
||||
<VennDiagram style={{minWidth: VENN_MIN_WIDTH, minHeight: VENN_MIN_WIDTH}} pillarsGrades={pillarsGrades}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ResponsiveVennDiagram.propTypes = {
|
||||
pillarsGrades: PropTypes.array
|
||||
};
|
||||
|
||||
export default Dimensions()(ResponsiveVennDiagram);
|
|
@ -0,0 +1,43 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
class Tooltip extends React.Component {
|
||||
|
||||
render() {
|
||||
const {prefix, bcolor, top, left, display, html} = this.props;
|
||||
|
||||
const style = {
|
||||
backgroundColor: bcolor,
|
||||
border: '1px solid #FFFFFF',
|
||||
borderRadius: '2px',
|
||||
fontSize: 10,
|
||||
padding: 8,
|
||||
display,
|
||||
opacity: 0.9,
|
||||
position: 'fixed',
|
||||
top,
|
||||
left,
|
||||
pointerEvents: 'none'
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
<div className='tooltip' style={style}>
|
||||
{html.split('\n').map((i_, key_) => {
|
||||
return <div key={prefix + 'Element' + key_}>{i_}</div>;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Tooltip.propTypes = {
|
||||
prefix: PropTypes.string,
|
||||
bcolor: PropTypes.string,
|
||||
top: PropTypes.number,
|
||||
left: PropTypes.number,
|
||||
display: PropTypes.string,
|
||||
html: PropTypes.string
|
||||
};
|
||||
|
||||
export default Tooltip;
|
|
@ -0,0 +1,15 @@
|
|||
export class TypographicUtilities {
|
||||
|
||||
static removeAmpersand(string_) {
|
||||
return string_.replace(' & ', 'And');
|
||||
}
|
||||
|
||||
static removeBrokenBar(string_) {
|
||||
return string_.replace(/\|/g, ' ');
|
||||
}
|
||||
|
||||
static setTitle(string_) {
|
||||
return string_.charAt(0).toUpperCase() + string_.substr(1).toLowerCase();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
@import url('https://fonts.googleapis.com/css?family=Noto+Sans&display=swap');
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "Noto Sans", sans-serif;
|
||||
}
|
||||
|
||||
svg {
|
||||
|
||||
-webkit-touch-callout: none; /* iOS Safari */
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-khtml-user-select: none; /* Konqueror HTML */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* Internet Explorer/Edge */
|
||||
user-select: none; /* Non-prefixed version, currently supported by Chrome and Opera */
|
||||
|
||||
}
|
|
@ -0,0 +1,253 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Tooltip from './Tooltip'
|
||||
import CircularNode from './CircularNode'
|
||||
import ArcNode from './ArcNode'
|
||||
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.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.sixthWidth = this.width / 6;
|
||||
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}},
|
||||
Devices: {cx: 0, cy: this.width2By7, r: this.sixthWidth, offset: {x: 0, y: -this.width1By11}},
|
||||
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) 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,
|
||||
sum(C, I) > 0 and C * I = 0, while C has to be 0
|
||||
|
||||
RULE #4: Positive [P] and Unexecuted have to be positive
|
||||
sum(P, U) >= 2 and P * U = positive integer, while
|
||||
if the P is bigger by 2 then negative U, first conditional
|
||||
would be true.
|
||||
|
||||
*/
|
||||
|
||||
this.rules = [
|
||||
|
||||
{
|
||||
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.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')) {
|
||||
html = e.target.dataset.tooltip;
|
||||
this.divElement.style.cursor = 'pointer';
|
||||
hidden = 'block';
|
||||
e.target.setAttribute('opacity', 0.95);
|
||||
bcolor = e.target.getAttribute('fill');
|
||||
|
||||
// Set highest z-index
|
||||
e.target.parentNode.parentNode.appendChild(e.target.parentNode);
|
||||
} else {
|
||||
this.divElement.style.cursor = '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;
|
||||
|
||||
//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 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();
|
||||
}
|
||||
|
||||
buildTooltipHtmlContent(object_) {
|
||||
return Object.keys(object_).reduce((out_, key_) => out_ + TypographicUtilities.setTitle(key_) + ': ' + object_[key_] + '\n', '');
|
||||
}
|
||||
|
||||
setLayoutElement(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_].node = d_;
|
||||
this.layout[key_].tooltip = html_;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.data === undefined) {
|
||||
return null;
|
||||
} else {
|
||||
// equivalent to center translate (width/2, height/2)
|
||||
let viewPortParameters = (-this.width / 2) + ' ' + (-this.height / 2) + ' ' + this.width + ' ' + this.height;
|
||||
|
||||
let nodes = Object.values(this.layout).map((d_, i_) => {
|
||||
if (d_.hasOwnProperty('cx')) {
|
||||
return (
|
||||
<CircularNode
|
||||
prefix={this.prefix}
|
||||
key={this.prefix + 'CircularNode' + i_}
|
||||
index={i_}
|
||||
data={d_}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
d_.label = TypographicUtilities.removeBrokenBar(d_.label);
|
||||
|
||||
return (
|
||||
<ArcNode
|
||||
prefix={this.prefix}
|
||||
key={this.prefix + 'ArcNode' + i_}
|
||||
index={i_}
|
||||
data={d_}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div ref={(divElement) => this.divElement = divElement} onMouseMove={this._onMouseMove.bind(this)}
|
||||
onClick={this._onClick.bind(this)}>
|
||||
<svg id={this.prefix} viewBox={viewPortParameters} width={'100%'} height={'100%'}
|
||||
xmlns='http://www.w3.org/2000/svg' xmlnsXlink='http://www.w3.org/1999/xlink'>
|
||||
{nodes}
|
||||
</svg>
|
||||
<Tooltip id={this.prefix + 'Tooltip'} prefix={this.prefix} {...this.state.tooltip} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VennDiagram.propTypes = {
|
||||
pillarsGrades: PropTypes.array
|
||||
};
|
||||
|
||||
export default VennDiagram;
|
Loading…
Reference in New Issue