From 800e337f6f0e4fc3f9d25c5dff417ae3dc736a33 Mon Sep 17 00:00:00 2001
From: Itay Mizeretz <itay.mizeretz@guardicore.com>
Date: Thu, 19 Jul 2018 18:35:37 +0300
Subject: [PATCH] Add credential map to report. currently uses fake static data

---
 .../cc/ui/src/components/map/MapOptions.js    |  35 ++-
 .../map/preview-pane/InfMapPreviewPane.js     | 247 +++++++++++++++++
 .../map/preview-pane/PreviewPane.js           | 252 +-----------------
 .../map/preview-pane/PthPreviewPane.js        |  63 +++++
 .../cc/ui/src/components/pages/MapPage.js     |   4 +-
 .../components/pages/PassTheHashMapPage.js    |  94 +++----
 .../cc/ui/src/components/pages/ReportPage.js  |  46 ++++
 .../cc/ui/src/images/nodes/pth/critical.png   | Bin 0 -> 20067 bytes
 .../cc/ui/src/images/nodes/pth/normal.png     | Bin 0 -> 19466 bytes
 9 files changed, 436 insertions(+), 305 deletions(-)
 create mode 100644 monkey_island/cc/ui/src/components/map/preview-pane/InfMapPreviewPane.js
 create mode 100644 monkey_island/cc/ui/src/components/map/preview-pane/PthPreviewPane.js
 create mode 100644 monkey_island/cc/ui/src/images/nodes/pth/critical.png
 create mode 100644 monkey_island/cc/ui/src/images/nodes/pth/normal.png

diff --git a/monkey_island/cc/ui/src/components/map/MapOptions.js b/monkey_island/cc/ui/src/components/map/MapOptions.js
index 701adcf29..f6946ea31 100644
--- a/monkey_island/cc/ui/src/components/map/MapOptions.js
+++ b/monkey_island/cc/ui/src/components/map/MapOptions.js
@@ -1,4 +1,4 @@
-let groupNames = ['clean_unknown', 'clean_linux', 'clean_windows', 'exploited_linux', 'exploited_windows', 'island',
+const groupNames = ['clean_unknown', 'clean_linux', 'clean_windows', 'exploited_linux', 'exploited_windows', 'island',
   'island_monkey_linux', 'island_monkey_linux_running', 'island_monkey_windows', 'island_monkey_windows_running',
   'manual_linux', 'manual_linux_running', 'manual_windows', 'manual_windows_running', 'monkey_linux',
   'monkey_linux_running', 'monkey_windows', 'monkey_windows_running'];
@@ -16,7 +16,22 @@ let getGroupsOptions = () => {
   return groupOptions;
 };
 
-export const options = {
+const groupNamesPth = ['normal', 'critical'];
+
+let getGroupsOptionsPth = () => {
+  let groupOptions = {};
+  for (let groupName of groupNamesPth) {
+    groupOptions[groupName] =
+      {
+        shape: 'image',
+        size: 50,
+        image: require('../../images/nodes/pth/' + groupName + '.png')
+      };
+  }
+  return groupOptions;
+};
+
+export const basic_options = {
   autoResize: true,
   layout: {
     improvedLayout: false
@@ -33,10 +48,22 @@ export const options = {
       avoidOverlap: 0.5
     },
     minVelocity: 0.75
-  },
-  groups: getGroupsOptions()
+  }
 };
 
+export const options = (() => {
+  let opts = JSON.parse(JSON.stringify(basic_options)); /* Deep copy */
+  opts.groups = getGroupsOptions();
+  return opts;
+})();
+
+export const optionsPth = (() => {
+  let opts = JSON.parse(JSON.stringify(basic_options)); /* Deep copy */
+  opts.groups = getGroupsOptionsPth();
+  opts.physics.barnesHut.gravitationalConstant = -20000;
+  return opts;
+})();
+
 export function edgeGroupToColor(group) {
   switch (group) {
     case 'exploited':
diff --git a/monkey_island/cc/ui/src/components/map/preview-pane/InfMapPreviewPane.js b/monkey_island/cc/ui/src/components/map/preview-pane/InfMapPreviewPane.js
new file mode 100644
index 000000000..e06043c20
--- /dev/null
+++ b/monkey_island/cc/ui/src/components/map/preview-pane/InfMapPreviewPane.js
@@ -0,0 +1,247 @@
+import React from 'react';
+import {Icon} from 'react-fa';
+import Toggle from 'react-toggle';
+import {OverlayTrigger, Tooltip} from 'react-bootstrap';
+import download from 'downloadjs'
+import PreviewPaneComponent from 'components/map/preview-pane/PreviewPane';
+
+class InfMapPreviewPaneComponent extends PreviewPaneComponent {
+
+  osRow(asset) {
+    return (
+      <tr>
+        <th>Operating System</th>
+        <td>{asset.os.charAt(0).toUpperCase() + asset.os.slice(1)}</td>
+      </tr>
+    );
+  }
+
+  ipsRow(asset) {
+    return (
+      <tr>
+        <th>IP Addresses</th>
+        <td>{asset.ip_addresses.map(val => <div key={val}>{val}</div>)}</td>
+      </tr>
+    );
+  }
+
+  servicesRow(asset) {
+    return (
+      <tr>
+        <th>Services</th>
+        <td>{asset.services.map(val => <div key={val}>{val}</div>)}</td>
+      </tr>
+    );
+  }
+
+  accessibleRow(asset) {
+    return (
+      <tr>
+        <th>
+          Accessible From&nbsp;
+          {this.generateToolTip('List of machine which can access this one using a network protocol')}
+        </th>
+        <td>{asset.accessible_from_nodes.map(val => <div key={val}>{val}</div>)}</td>
+      </tr>
+    );
+  }
+
+  statusRow(asset) {
+    return (
+      <tr>
+        <th>Status</th>
+        <td>{(asset.dead) ? 'Dead' : 'Alive'}</td>
+      </tr>
+    );
+  }
+
+  forceKill(event, asset) {
+    let newConfig = asset.config;
+    newConfig['alive'] = !event.target.checked;
+    this.authFetch('/api/monkey/' + asset.guid,
+      {
+        method: 'PATCH',
+        headers: {'Content-Type': 'application/json'},
+        body: JSON.stringify({config: newConfig})
+      });
+  }
+
+  forceKillRow(asset) {
+    return (
+      <tr>
+        <th>
+          Force Kill&nbsp;
+          {this.generateToolTip('If this is on, monkey will die next time it communicates')}
+        </th>
+        <td>
+          <Toggle id={asset.id} checked={!asset.config.alive} icons={false} disabled={asset.dead}
+                  onChange={(e) => this.forceKill(e, asset)}/>
+
+        </td>
+      </tr>
+    );
+  }
+
+  unescapeLog(st) {
+    return st.substr(1, st.length - 2) // remove quotation marks on beginning and end of string.
+        .replace(/\\n/g, "\n")
+        .replace(/\\r/g, "\r")
+        .replace(/\\t/g, "\t")
+        .replace(/\\b/g, "\b")
+        .replace(/\\f/g, "\f")
+        .replace(/\\"/g, '\"')
+        .replace(/\\'/g, "\'")
+        .replace(/\\&/g, "\&");
+  }
+
+  downloadLog(asset) {
+    this.authFetch('/api/log?id=' + asset.id)
+      .then(res => res.json())
+      .then(res => {
+        let timestamp = res['timestamp'];
+        timestamp = timestamp.substr(0, timestamp.indexOf('.'));
+        let filename = res['monkey_label'].split(':').join('-') + ' - ' + timestamp + '.log';
+        let logContent = this.unescapeLog(res['log']);
+        download(logContent, filename, 'text/plain');
+      });
+
+  }
+
+  downloadLogRow(asset) {
+    return (
+      <tr>
+        <th>
+          Download Log
+        </th>
+        <td>
+          <a type="button" className="btn btn-primary"
+             disabled={!asset.has_log}
+             onClick={() => this.downloadLog(asset)}>Download</a>
+        </td>
+      </tr>
+    );
+  }
+
+  exploitsTimeline(asset) {
+    if (asset.exploits.length === 0) {
+      return (<div/>);
+    }
+
+    return (
+      <div>
+        <h4 style={{'marginTop': '2em'}}>
+          Exploit Timeline&nbsp;
+          {this.generateToolTip('Timeline of exploit attempts. Red is successful. Gray is unsuccessful')}
+        </h4>
+        <ul className="timeline">
+          {asset.exploits.map(exploit =>
+            <li key={exploit.timestamp}>
+              <div className={'bullet ' + (exploit.result ? 'bad' : '')}/>
+              <div>{new Date(exploit.timestamp).toLocaleString()}</div>
+              <div>{exploit.origin}</div>
+              <div>{exploit.exploiter}</div>
+            </li>
+          )}
+        </ul>
+      </div>
+    )
+  }
+
+  assetInfo(asset) {
+    return (
+      <div>
+        <table className="table table-condensed">
+          <tbody>
+          {this.osRow(asset)}
+          {this.ipsRow(asset)}
+          {this.servicesRow(asset)}
+          {this.accessibleRow(asset)}
+          </tbody>
+        </table>
+        {this.exploitsTimeline(asset)}
+      </div>
+    );
+  }
+
+  infectedAssetInfo(asset) {
+    return (
+      <div>
+        <table className="table table-condensed">
+          <tbody>
+          {this.osRow(asset)}
+          {this.statusRow(asset)}
+          {this.ipsRow(asset)}
+          {this.servicesRow(asset)}
+          {this.accessibleRow(asset)}
+          {this.forceKillRow(asset)}
+          {this.downloadLogRow(asset)}
+          </tbody>
+        </table>
+        {this.exploitsTimeline(asset)}
+      </div>
+    );
+  }
+
+  scanInfo(edge) {
+    return (
+      <div>
+        <table className="table table-condensed">
+          <tbody>
+          <tr>
+            <th>Operating System</th>
+            <td>{edge.os.type}</td>
+          </tr>
+          <tr>
+            <th>IP Address</th>
+            <td>{edge.ip_address}</td>
+          </tr>
+          <tr>
+            <th>Services</th>
+            <td>{edge.services.map(val => <div key={val}>{val}</div>)}</td>
+          </tr>
+          </tbody>
+        </table>
+        {
+          (edge.exploits.length === 0) ?
+            '' :
+            <div>
+              <h4 style={{'marginTop': '2em'}}>Timeline</h4>
+              <ul className="timeline">
+                {edge.exploits.map(exploit =>
+                  <li key={exploit.timestamp}>
+                    <div className={'bullet ' + (exploit.result ? 'bad' : '')}/>
+                    <div>{new Date(exploit.timestamp).toLocaleString()}</div>
+                    <div>{exploit.origin}</div>
+                    <div>{exploit.exploiter}</div>
+                  </li>
+                )}
+              </ul>
+            </div>
+        }
+      </div>
+    );
+  }
+
+  islandEdgeInfo() {
+    return (
+      <div>
+      </div>
+    );
+  }
+
+  getInfoByProps() {
+    switch (this.props.type) {
+      case 'edge':
+        return this.scanInfo(this.props.item);
+      case 'node':
+        return this.props.item.group.includes('monkey', 'manual') ?
+          this.infectedAssetInfo(this.props.item) : this.assetInfo(this.props.item);
+      case 'island_edge':
+        return this.islandEdgeInfo();
+    }
+
+    return null;
+  }
+}
+
+export default InfMapPreviewPaneComponent;
diff --git a/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js b/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js
index 64b228332..c38907eea 100644
--- a/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js
+++ b/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js
@@ -15,251 +15,25 @@ class PreviewPaneComponent extends AuthComponent {
     );
   }
 
-  osRow(asset) {
-    return (
-      <tr>
-        <th>Operating System</th>
-        <td>{asset.os.charAt(0).toUpperCase() + asset.os.slice(1)}</td>
-      </tr>
-    );
+  // This should be overridden
+  getInfoByProps() {
+    return null;
   }
 
-  ipsRow(asset) {
-    return (
-      <tr>
-        <th>IP Addresses</th>
-        <td>{asset.ip_addresses.map(val => <div key={val}>{val}</div>)}</td>
-      </tr>
-    );
-  }
-
-  servicesRow(asset) {
-    return (
-      <tr>
-        <th>Services</th>
-        <td>{asset.services.map(val => <div key={val}>{val}</div>)}</td>
-      </tr>
-    );
-  }
-
-  accessibleRow(asset) {
-    return (
-      <tr>
-        <th>
-          Accessible From&nbsp;
-          {this.generateToolTip('List of machine which can access this one using a network protocol')}
-        </th>
-        <td>{asset.accessible_from_nodes.map(val => <div key={val}>{val}</div>)}</td>
-      </tr>
-    );
-  }
-
-  statusRow(asset) {
-    return (
-      <tr>
-        <th>Status</th>
-        <td>{(asset.dead) ? 'Dead' : 'Alive'}</td>
-      </tr>
-    );
-  }
-
-  forceKill(event, asset) {
-    let newConfig = asset.config;
-    newConfig['alive'] = !event.target.checked;
-    this.authFetch('/api/monkey/' + asset.guid,
-      {
-        method: 'PATCH',
-        headers: {'Content-Type': 'application/json'},
-        body: JSON.stringify({config: newConfig})
-      });
-  }
-
-  forceKillRow(asset) {
-    return (
-      <tr>
-        <th>
-          Force Kill&nbsp;
-          {this.generateToolTip('If this is on, monkey will die next time it communicates')}
-        </th>
-        <td>
-          <Toggle id={asset.id} checked={!asset.config.alive} icons={false} disabled={asset.dead}
-                  onChange={(e) => this.forceKill(e, asset)}/>
-
-        </td>
-      </tr>
-    );
-  }
-
-  unescapeLog(st) {
-    return st.substr(1, st.length - 2) // remove quotation marks on beginning and end of string.
-        .replace(/\\n/g, "\n")
-        .replace(/\\r/g, "\r")
-        .replace(/\\t/g, "\t")
-        .replace(/\\b/g, "\b")
-        .replace(/\\f/g, "\f")
-        .replace(/\\"/g, '\"')
-        .replace(/\\'/g, "\'")
-        .replace(/\\&/g, "\&");
-  }
-
-  downloadLog(asset) {
-    this.authFetch('/api/log?id=' + asset.id)
-      .then(res => res.json())
-      .then(res => {
-        let timestamp = res['timestamp'];
-        timestamp = timestamp.substr(0, timestamp.indexOf('.'));
-        let filename = res['monkey_label'].split(':').join('-') + ' - ' + timestamp + '.log';
-        let logContent = this.unescapeLog(res['log']);
-        download(logContent, filename, 'text/plain');
-      });
-
-  }
-
-  downloadLogRow(asset) {
-    return (
-      <tr>
-        <th>
-          Download Log
-        </th>
-        <td>
-          <a type="button" className="btn btn-primary"
-             disabled={!asset.has_log}
-             onClick={() => this.downloadLog(asset)}>Download</a>
-        </td>
-      </tr>
-    );
-  }
-
-  exploitsTimeline(asset) {
-    if (asset.exploits.length === 0) {
-      return (<div/>);
+  getLabelByProps() {
+    if (!this.props.item) {
+      return '';
+    } else if (this.props.item.hasOwnProperty('label')) {
+      return this.props.item['label'];
+    } else if (this.props.item.hasOwnProperty('_label')) {
+      return this.props.item['_label'];
     }
-
-    return (
-      <div>
-        <h4 style={{'marginTop': '2em'}}>
-          Exploit Timeline&nbsp;
-          {this.generateToolTip('Timeline of exploit attempts. Red is successful. Gray is unsuccessful')}
-        </h4>
-        <ul className="timeline">
-          {asset.exploits.map(exploit =>
-            <li key={exploit.timestamp}>
-              <div className={'bullet ' + (exploit.result ? 'bad' : '')}/>
-              <div>{new Date(exploit.timestamp).toLocaleString()}</div>
-              <div>{exploit.origin}</div>
-              <div>{exploit.exploiter}</div>
-            </li>
-          )}
-        </ul>
-      </div>
-    )
-  }
-
-  assetInfo(asset) {
-    return (
-      <div>
-        <table className="table table-condensed">
-          <tbody>
-          {this.osRow(asset)}
-          {this.ipsRow(asset)}
-          {this.servicesRow(asset)}
-          {this.accessibleRow(asset)}
-          </tbody>
-        </table>
-        {this.exploitsTimeline(asset)}
-      </div>
-    );
-  }
-
-  infectedAssetInfo(asset) {
-    return (
-      <div>
-        <table className="table table-condensed">
-          <tbody>
-          {this.osRow(asset)}
-          {this.statusRow(asset)}
-          {this.ipsRow(asset)}
-          {this.servicesRow(asset)}
-          {this.accessibleRow(asset)}
-          {this.forceKillRow(asset)}
-          {this.downloadLogRow(asset)}
-          </tbody>
-        </table>
-        {this.exploitsTimeline(asset)}
-      </div>
-    );
-  }
-
-  scanInfo(edge) {
-    return (
-      <div>
-        <table className="table table-condensed">
-          <tbody>
-          <tr>
-            <th>Operating System</th>
-            <td>{edge.os.type}</td>
-          </tr>
-          <tr>
-            <th>IP Address</th>
-            <td>{edge.ip_address}</td>
-          </tr>
-          <tr>
-            <th>Services</th>
-            <td>{edge.services.map(val => <div key={val}>{val}</div>)}</td>
-          </tr>
-          </tbody>
-        </table>
-        {
-          (edge.exploits.length === 0) ?
-            '' :
-            <div>
-              <h4 style={{'marginTop': '2em'}}>Timeline</h4>
-              <ul className="timeline">
-                {edge.exploits.map(exploit =>
-                  <li key={exploit.timestamp}>
-                    <div className={'bullet ' + (exploit.result ? 'bad' : '')}/>
-                    <div>{new Date(exploit.timestamp).toLocaleString()}</div>
-                    <div>{exploit.origin}</div>
-                    <div>{exploit.exploiter}</div>
-                  </li>
-                )}
-              </ul>
-            </div>
-        }
-      </div>
-    );
-  }
-
-  islandEdgeInfo() {
-    return (
-      <div>
-      </div>
-    );
+    return '';
   }
 
   render() {
-    let info = null;
-    switch (this.props.type) {
-      case 'edge':
-        info = this.scanInfo(this.props.item);
-        break;
-      case 'node':
-        info = this.props.item.group.includes('monkey', 'manual') ?
-          this.infectedAssetInfo(this.props.item) : this.assetInfo(this.props.item);
-        break;
-      case 'island_edge':
-        info = this.islandEdgeInfo();
-        break;
-    }
-
-    let label = '';
-    if (!this.props.item) {
-      label = '';
-    } else if (this.props.item.hasOwnProperty('label')) {
-      label = this.props.item['label'];
-    } else if (this.props.item.hasOwnProperty('_label')) {
-      label = this.props.item['_label'];
-    }
+    let info = this.getInfoByProps();
+    let label = this.getLabelByProps();
 
     return (
       <div className="preview-pane">
diff --git a/monkey_island/cc/ui/src/components/map/preview-pane/PthPreviewPane.js b/monkey_island/cc/ui/src/components/map/preview-pane/PthPreviewPane.js
new file mode 100644
index 000000000..f9a5ae1bb
--- /dev/null
+++ b/monkey_island/cc/ui/src/components/map/preview-pane/PthPreviewPane.js
@@ -0,0 +1,63 @@
+import React from 'react';
+import {Icon} from 'react-fa';
+import Toggle from 'react-toggle';
+import {OverlayTrigger, Tooltip} from 'react-bootstrap';
+import download from 'downloadjs'
+import PreviewPaneComponent from 'components/map/preview-pane/PreviewPane';
+
+class PthPreviewPaneComponent extends PreviewPaneComponent {
+  nodeInfo(asset) {
+    return (
+      <div>
+        <table className="table table-condensed">
+          <tbody>
+          <tr>
+            <th>Hostname</th>
+            <td>{asset.hostname}</td>
+          </tr>
+          <tr>
+            <th>IP Addresses</th>
+            <td>{asset.ips.map(val => <div key={val}>{val}</div>)}</td>
+          </tr>
+          <tr>
+            <th>Services</th>
+            <td>{asset.services.map(val => <div key={val}>{val}</div>)}</td>
+          </tr>
+          <tr>
+            <th>Compromised Users</th>
+            <td>{asset.users.map(val => <div key={val}>{val}</div>)}</td>
+          </tr>
+          </tbody>
+        </table>
+      </div>
+    );
+  }
+
+  edgeInfo(edge) {
+    return (
+      <div>
+        <table className="table table-condensed">
+          <tbody>
+          <tr>
+            <th>Compromised Users</th>
+            <td>{edge.users.map(val => <div key={val}>{val}</div>)}</td>
+          </tr>
+          </tbody>
+        </table>
+      </div>
+    );
+  }
+
+  getInfoByProps() {
+    switch (this.props.type) {
+      case 'edge':
+        return this.edgeInfo(this.props.item);
+      case 'node':
+        return this.nodeInfo(this.props.item);
+    }
+
+    return null;
+  }
+}
+
+export default PthPreviewPaneComponent;
diff --git a/monkey_island/cc/ui/src/components/pages/MapPage.js b/monkey_island/cc/ui/src/components/pages/MapPage.js
index 4a54aeb8c..00c0cba3c 100644
--- a/monkey_island/cc/ui/src/components/pages/MapPage.js
+++ b/monkey_island/cc/ui/src/components/pages/MapPage.js
@@ -2,7 +2,7 @@ import React from 'react';
 import {Col} from 'react-bootstrap';
 import {Link} from 'react-router-dom';
 import {Icon} from 'react-fa';
-import PreviewPane from 'components/map/preview-pane/PreviewPane';
+import InfMapPreviewPaneComponent from 'components/map/preview-pane/InfMapPreviewPane';
 import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
 import {ModalContainer, ModalDialog} from 'react-modal-dialog';
 import {options, edgeGroupToColor} from 'components/map/MapOptions';
@@ -190,7 +190,7 @@ class MapPageComponent extends AuthComponent {
             </div>
             : ''}
 
-          <PreviewPane item={this.state.selected} type={this.state.selectedType}/>
+          <InfMapPreviewPaneComponent item={this.state.selected} type={this.state.selectedType}/>
         </Col>
       </div>
     );
diff --git a/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js b/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js
index 2ac43f094..8c7ded49b 100644
--- a/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js
+++ b/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js
@@ -1,83 +1,57 @@
 import React from 'react';
-import {Col} from 'react-bootstrap';
 import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
 import AuthComponent from '../AuthComponent';
-import Graph from 'react-graph-vis';
-
-const options = {
-  autoResize: true,
-  layout: {
-    improvedLayout: false
-  },
-  edges: {
-    width: 2,
-    smooth: {
-      type: 'curvedCW'
-    }
-  },
-  physics: {
-    barnesHut: {
-      gravitationalConstant: -120000,
-      avoidOverlap: 0.5
-    },
-    minVelocity: 0.75
-  }
-};
+import {optionsPth, edgeGroupToColorPth, options} from '../map/MapOptions';
+import PreviewPane from "../map/preview-pane/PreviewPane";
+import {Col} from "react-bootstrap";
+import {Link} from 'react-router-dom';
+import {Icon} from 'react-fa';
+import PthPreviewPaneComponent from "../map/preview-pane/PthPreviewPane";
 
 class PassTheHashMapPageComponent extends AuthComponent {
   constructor(props) {
     super(props);
     this.state = {
-      graph: {nodes: [], edges: []},
-      report: "",
+      graph: props.graph,
       selected: null,
-      selectedType: null,
-      killPressed: false,
-      showKillDialog: false,
-      telemetry: [],
-      telemetryLastTimestamp: null
+      selectedType: null
     };
   }
 
-  componentDidMount() {
-    this.updateMapFromServer();
-    this.interval = setInterval(this.timedEvents, 1000);
-  }
-
-  componentWillUnmount() {
-    clearInterval(this.interval);
-  }
-
-  timedEvents = () => {
-    this.updateMapFromServer();
+  events = {
+    select: event => this.selectionChanged(event)
   };
 
-  updateMapFromServer = () => {
-    this.authFetch('/api/pthmap')
-      .then(res => res.json())
-      .then(res => {
-        this.setState({graph: res});
-        this.props.onStatusChange();
-      });
-    this.authFetch('/api/pthreport')
-      .then(res => res.json())
-      .then(res => {
-        this.setState({report: res.html});
-        this.props.onStatusChange();
-      });
-  };
+  selectionChanged(event) {
+    if (event.nodes.length === 1) {
+      let displayedNode = this.state.graph.nodes.find(
+        function (node) {
+          return node['id'] === event.nodes[0];
+        });
+      this.setState({selected: displayedNode, selectedType: 'node'})
+    }
+    else if (event.edges.length === 1) {
+      let displayedEdge = this.state.graph.edges.find(
+        function (edge) {
+          return edge['id'] === event.edges[0];
+        });
+        this.setState({selected: displayedEdge, selectedType: 'edge'});
+    }
+    else {
+      this.setState({selected: null, selectedType: null});
+    }
+  }
 
   render() {
     return (
       <div>
-        <Col xs={12} lg={8}>
-          <h1 className="page-title">Pass The Hash Map</h1>
+        <Col xs={12}>
+          <div style={{height: '70vh'}}>
+            <ReactiveGraph graph={this.state.graph} options={optionsPth} events={this.events}/>
+          </div>
         </Col>
         <Col xs={12}>
-          <div>
-            <Graph graph={this.state.graph} options={options} />
-          </div>
-          <div dangerouslySetInnerHTML={{__html: this.state.report}}></div>
+          <PthPreviewPaneComponent item={this.state.selected} type={this.state.selectedType}/>
         </Col>
       </div>
     );
diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js
index bec4f3625..adb024c72 100644
--- a/monkey_island/cc/ui/src/components/pages/ReportPage.js
+++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js
@@ -8,6 +8,7 @@ import StolenPasswords from 'components/report-components/StolenPasswords';
 import CollapsibleWellComponent from 'components/report-components/CollapsibleWell';
 import {Line} from 'rc-progress';
 import AuthComponent from '../AuthComponent';
+import PassTheHashMapPageComponent from "./PassTheHashMapPage";
 
 let guardicoreLogoImage = require('../../images/guardicore-logo.png');
 let monkeyLogoImage = require('../../images/monkey-icon.svg');
@@ -129,6 +130,7 @@ class ReportPageComponent extends AuthComponent {
           {this.generateReportFindingsSection()}
           {this.generateReportRecommendationsSection()}
           {this.generateReportGlanceSection()}
+          {this.generateReportPthSection()}
           {this.generateReportFooter()}
         </div>
         <div className="text-center no-print" style={{marginTop: '20px'}}>
@@ -420,6 +422,50 @@ class ReportPageComponent extends AuthComponent {
     );
   }
 
+  generateReportPthSection() {
+    // TODO: remove this and use updateMapFromSerever to get actual map data.
+    const my_map = {
+      nodes: [
+        {id: '1', label: 'MYPC-1', group: 'normal', users: ['MYPC-2\\user1', 'Dom\\user2'], ips: ['192.168.0.1'], services: ["DC", "SQL"], 'hostname': 'aaa1'},
+        {id: 2, label: 'MYPC-2', group: 'critical', users: ['MYPC-2\\user1', 'Dom\\user2'], ips: ['192.168.0.1'], services: ["DC", "SQL"], 'hostname': 'aaa2'},
+        {id: 3, label: 'MYPC-3', group: 'normal', users: ['MYPC-3\\user1', 'Dom\\user3'], ips: ['192.168.0.2'], services: ["DC", "SQL"], 'hostname': 'aaa3'},
+        {id: 4, label: 'MYPC-4', group: 'critical', users: ['MYPC-4\\user1', 'Dom\\user4'], ips: ['192.168.0.3', '192.168.0.4'], services: ["DC", "SQL"], 'hostname': 'aaa4'},
+        {id: 5, label: 'MYPC-5', group: 'normal', users: ['MYPC-5\\user1', 'Dom\\user5'], ips: ['192.168.0.1'], services: [], 'hostname': 'aaa5'},
+        {id: 6, label: 'MYPC-6', group: 'critical', users: ['MYPC-6\\user1', 'Dom\\user6'], ips: ['192.168.0.1'], services: ["DC"], 'hostname': 'aaa6'},
+        {id: 7, label: 'MYPC-7', group: 'critical', users: ['MYPC-7\\user1', 'Dom\\user7'], ips: ['192.168.0.1'], services: ["DC", "SQL"], 'hostname': 'aaa7'}
+      ],
+      edges: [
+        {id: 10, from: '1', to: 2, users: ['MYPC-3\\user1', 'Dom\\user3'], _label: 'bla0'},
+        {id: 11, from: '1', to: 3, users: ['MYPC-3\\user1', 'Dom\\user3'], _label: 'bla1'},
+        {id: 12, from: '1', to: 4, users: ['MYPC-3\\user1', 'Dom\\user3'], _label: 'bla2'},
+        {id: 13, from: 5, to: 6, users: ['MYPC-3\\user1', 'Dom\\user3'], _label: 'bla3'},
+        {id: 14, from: 6, to: 7, users: ['MYPC-3\\user1', 'Dom\\user3'], _label: 'bla4'},
+        {id: 15, from: 6, to: 5, users: ['MYPC-3\\user1', 'Dom\\user3'], _label: 'bla5'},
+      ]
+
+    };
+    return (
+      <div id="pth">
+        <h3>
+          Pass The Hash !!!!!TODO: change this!!!!!!!!
+        </h3>
+        <div style={{position: 'relative', height: '100vh'}}>
+          <PassTheHashMapPageComponent graph={my_map} />
+        </div>
+        <div>
+          TODO: put relevant tables and stuff here...
+        </div>
+        <div>
+          TODO: put relevant tables and stuff here...
+        </div>
+        <div>
+          TODO: put relevant tables and stuff here...
+        </div>
+        <br />
+      </div>
+    );
+  }
+
   generateReportFooter() {
     return (
       <div id="footer" className="text-center" style={{marginTop: '20px'}}>
diff --git a/monkey_island/cc/ui/src/images/nodes/pth/critical.png b/monkey_island/cc/ui/src/images/nodes/pth/critical.png
new file mode 100644
index 0000000000000000000000000000000000000000..0348a7f5d312b0400da47f60a5f2d81682bd19a6
GIT binary patch
literal 20067
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
CHH7{E

literal 0
HcmV?d00001

diff --git a/monkey_island/cc/ui/src/images/nodes/pth/normal.png b/monkey_island/cc/ui/src/images/nodes/pth/normal.png
new file mode 100644
index 0000000000000000000000000000000000000000..3b1e9b6387377d9fc2e39d7bede361041f3b6104
GIT binary patch
literal 19466
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

literal 0
HcmV?d00001