New dev environment
|
@ -1,11 +0,0 @@
|
||||||
This is a version of PowerSploit's [PowerView](https://github.com/PowerShellMafia/PowerSploit/blob/master/Recon/PowerView.ps1) customized for BloodHound data collection and ingestion.
|
|
||||||
|
|
||||||
It will be kept as a separate instance from PowerView and not kept in sync with the PowerSploit branch.
|
|
||||||
|
|
||||||
## BloodHound Functions:
|
|
||||||
|
|
||||||
Export-BloodHoundData - Exports PowerView object data to a BloodHound RESTapi instance
|
|
||||||
Get-BloodHoundData - Queries for machines in the current domain with Get-NetComputer
|
|
||||||
and enumerates sessions/logged on users as well as local administrators.
|
|
||||||
Ships all data off to a BloodHound RESTapi instance with Export-BloodHoundData.
|
|
||||||
|
|
|
@ -1,811 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<title>Bloodhound</title>
|
|
||||||
<link rel="icon" href="img/favicon.ico">
|
|
||||||
<!--<script src="js/jquery-2.2.1.min.js" onload="$ = jQuery = module.exports;"></script>-->
|
|
||||||
<script>window.$ = window.jQuery = require('jquery');</script>
|
|
||||||
<script type="text/javascript" src="js/jquery-ui.min.js"></script>
|
|
||||||
<script type="text/javascript" src="js/circle-progress/circle-progress.js"></script>
|
|
||||||
<script type="text/javascript" src="js/bootstrap.min.js"></script>
|
|
||||||
<script type="text/javascript" src="js/bootstrap3-typeahead.min.js"></script>
|
|
||||||
<script type="text/javascript" src="js/sigma.min.js"></script>
|
|
||||||
<script type="text/javascript" src="js/plugins.min.js"></script>
|
|
||||||
<script type="text/javascript" src="js/mustache.min.js"></script>
|
|
||||||
<script type="text/javascript" src="js/bloodhound.js"></script>
|
|
||||||
<script type="text/javascript" src="js/sigma.renderers.linkurious.min.js"></script>
|
|
||||||
<script type="text/javascript" src="js/sigma.plugins.design.min.js"></script>
|
|
||||||
<script type="text/javascript" src="js/sigma.layouts.dagre.min.js"></script>
|
|
||||||
<script type="text/javascript" src="js/sigma.layouts.noverlap.js"></script>
|
|
||||||
<script type="text/javascript" src="js/sigma.renderers.glyphs.min.js"></script>
|
|
||||||
<script type="text/javascript" src="js/sigma.exporters.image.min.js"></script>
|
|
||||||
<script type="text/javascript" src="js/dagre.min.js"></script>
|
|
||||||
<script type="text/javascript" src="js/simple-slider.min.js"></script>
|
|
||||||
<link rel="stylesheet" type="text/css" href="css/font-awesome.min.css">
|
|
||||||
<link type="text/css" rel="stylesheet" href="css/bootstrap.min.css">
|
|
||||||
<link type="text/css" rel="stylesheet" href="css/bootstrap-theme.min.css">
|
|
||||||
<link rel="stylesheet" type="text/css" href="css/styles.css">
|
|
||||||
<link rel="stylesheet" type="text/css" href="css/animation.css">
|
|
||||||
<link rel="stylesheet" type="text/css" href="css/tooltip.css">
|
|
||||||
<link rel="stylesheet" type="text/css" href="css/simple-slider.css">
|
|
||||||
<link rel="stylesheet" type="text/css" href="css/simple-slider-volume.css">
|
|
||||||
|
|
||||||
<script type="javascript/worker" id="ingestworker">
|
|
||||||
function createXHR(url, auth){
|
|
||||||
var xmlhttp = new XMLHttpRequest();
|
|
||||||
xmlhttp.open("POST", url, false)
|
|
||||||
xmlhttp.setRequestHeader('Accepts', 'application/json');
|
|
||||||
xmlhttp.setRequestHeader('Content-Type', 'application/json');
|
|
||||||
xmlhttp.setRequestHeader('Authorization', auth);
|
|
||||||
xmlhttp.setRequestHeader('X-Stream', true);
|
|
||||||
return xmlhttp
|
|
||||||
}
|
|
||||||
self.addEventListener('message', function(e){
|
|
||||||
var currentTransaction = null;
|
|
||||||
var groups = [], i;
|
|
||||||
var sent = 0
|
|
||||||
importScripts(e.data.url + "/js/papaparse.min.js");
|
|
||||||
var data = Papa.parse(e.data.data, {header:true, skipEmptyLines: true}).data;
|
|
||||||
var testElement = data[0];
|
|
||||||
for (i=0; i < data.length; i += 1000){
|
|
||||||
groups.push(data.slice(i, i+1000));
|
|
||||||
}
|
|
||||||
if (e.data.type === 'localadmin'){
|
|
||||||
var starttime = new Date();
|
|
||||||
if (testElement.hasOwnProperty('Server') && testElement.hasOwnProperty('AccountName') && testElement.hasOwnProperty('IsGroup')){
|
|
||||||
var topost = []
|
|
||||||
topost.message = 'start'
|
|
||||||
topost.len = data.length
|
|
||||||
postMessage(topost)
|
|
||||||
var xmlhttp = createXHR(e.data.dburl + '/db/data/transaction', e.data.auth);
|
|
||||||
xmlhttp.send()
|
|
||||||
currentTransaction = JSON.parse(xmlhttp.responseText).commit.split('/').slice(-2)[0]
|
|
||||||
var postdata = null
|
|
||||||
groups.forEach(function(g){
|
|
||||||
postdata = {}
|
|
||||||
postdata.statements = []
|
|
||||||
g.forEach(function(o){
|
|
||||||
if (o.IsGroup == 'False'){
|
|
||||||
var user = o['AccountName'].replace('\\','/').split('/')[1].toUpperCase()
|
|
||||||
var computer = o['Server'].toUpperCase()
|
|
||||||
|
|
||||||
var obj = {
|
|
||||||
"statement": 'MERGE (user:User {name: "' + user + '"}) WITH user MERGE (computer:Computer {name: "' + computer + '" }) WITH user,computer MERGE (user)-[:AdminTo]->(computer)'
|
|
||||||
}
|
|
||||||
|
|
||||||
postdata.statements.push(obj)
|
|
||||||
}else{
|
|
||||||
var group = o['AccountName'].replace('\\','/').split('/')[1].toUpperCase()
|
|
||||||
var computer = o['Server'].toUpperCase()
|
|
||||||
|
|
||||||
var obj = {
|
|
||||||
"statement": 'MERGE (group:Group {name: "' + group + '"}) WITH group MERGE (computer:Computer {name: "' + computer +'"}) WITH group,computer MERGE (group)-[:AdminTo]->(computer)'
|
|
||||||
}
|
|
||||||
|
|
||||||
postdata.statements.push(obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
xmlhttp = createXHR(e.data.dburl + "/db/data/transaction/" + currentTransaction, e.data.auth)
|
|
||||||
xmlhttp.send(JSON.stringify(postdata));
|
|
||||||
|
|
||||||
sent = sent + g.length
|
|
||||||
var topost = []
|
|
||||||
topost.message = 'progress'
|
|
||||||
topost.len = data.length
|
|
||||||
topost.progress = sent
|
|
||||||
postMessage(topost)
|
|
||||||
if ((sent % 20000) == 0){
|
|
||||||
xmlhttp = createXHR(e.data.dburl + "/db/data/transaction/" + currentTransaction + "/commit", e.data.auth);
|
|
||||||
xmlhttp.send()
|
|
||||||
var topost = []
|
|
||||||
topost.message = 'commit'
|
|
||||||
postMessage(topost)
|
|
||||||
xmlhttp = createXHR(e.data.dburl + "/db/data/transaction", e.data.auth);
|
|
||||||
xmlhttp.send()
|
|
||||||
currentTransaction = JSON.parse(xmlhttp.responseText).commit.split('/').slice(-2)[0]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
var xmlhttp = createXHR(e.data.dburl + "/db/data/transaction/" + currentTransaction + "/commit", e.data.auth);
|
|
||||||
xmlhttp.send()
|
|
||||||
var endtime = new Date()
|
|
||||||
var timediff = Math.abs(endtime.getTime() - starttime.getTime())
|
|
||||||
var diffsec = Math.ceil(timediff / 1000)
|
|
||||||
var topost = []
|
|
||||||
topost.message = 'end'
|
|
||||||
topost.len = data.length
|
|
||||||
topost.time = diffsec
|
|
||||||
postMessage(topost)
|
|
||||||
}else{
|
|
||||||
var topost = []
|
|
||||||
topost.message = 'baddata'
|
|
||||||
postMessage(topost)
|
|
||||||
}
|
|
||||||
}else if (e.data.type == 'domainmembership'){
|
|
||||||
var starttime = new Date();
|
|
||||||
if (testElement.hasOwnProperty('MemberName') && testElement.hasOwnProperty('GroupName') && testElement.hasOwnProperty('IsGroup')){
|
|
||||||
var topost = []
|
|
||||||
topost.message = 'start'
|
|
||||||
topost.len = data.length
|
|
||||||
postMessage(topost)
|
|
||||||
var xmlhttp = createXHR(e.data.dburl + '/db/data/transaction', e.data.auth);
|
|
||||||
xmlhttp.send()
|
|
||||||
currentTransaction = JSON.parse(xmlhttp.responseText).commit.split('/').slice(-2)[0]
|
|
||||||
var postdata = null
|
|
||||||
groups.forEach(function(g){
|
|
||||||
postdata = {}
|
|
||||||
postdata.statements = []
|
|
||||||
g.forEach(function(o){
|
|
||||||
if (o.IsGroup == 'False'){
|
|
||||||
var user = o['MemberName'].toUpperCase()
|
|
||||||
var group = o['GroupName'].toUpperCase()
|
|
||||||
|
|
||||||
var obj = {
|
|
||||||
"statement": 'MERGE (user:User {name: "' + user + '"}) WITH user MERGE (group:Group {name: "' + group + '" }) WITH user,group MERGE (user)-[:MemberOf]->(group)'
|
|
||||||
}
|
|
||||||
|
|
||||||
postdata.statements.push(obj)
|
|
||||||
}else{
|
|
||||||
var ga = o['MemberName'].toUpperCase()
|
|
||||||
var gb = o['GroupName'].toUpperCase()
|
|
||||||
|
|
||||||
var obj = {
|
|
||||||
"statement": 'MERGE (group1:Group {name: "' + ga + '"}) WITH group1 MERGE (group2:Group {name: "' + gb +'"}) WITH group1,group2 MERGE (group1)-[:MemberOf]->(group2)'
|
|
||||||
}
|
|
||||||
|
|
||||||
postdata.statements.push(obj)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
xmlhttp = createXHR(e.data.dburl + "/db/data/transaction/" + currentTransaction, e.data.auth)
|
|
||||||
xmlhttp.send(JSON.stringify(postdata));
|
|
||||||
|
|
||||||
sent = sent + g.length
|
|
||||||
var topost = []
|
|
||||||
topost.message = 'progress'
|
|
||||||
topost.len = data.length
|
|
||||||
topost.progress = sent
|
|
||||||
postMessage(topost)
|
|
||||||
if ((sent % 20000) == 0){
|
|
||||||
xmlhttp = createXHR(e.data.dburl + "/db/data/transaction/" + currentTransaction + "/commit", e.data.auth);
|
|
||||||
xmlhttp.send()
|
|
||||||
var topost = []
|
|
||||||
topost.message = 'commit'
|
|
||||||
postMessage(topost)
|
|
||||||
xmlhttp = createXHR(e.data.dburl + "/db/data/transaction", e.data.auth);
|
|
||||||
xmlhttp.send()
|
|
||||||
currentTransaction = JSON.parse(xmlhttp.responseText).commit.split('/').slice(-2)[0]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
var xmlhttp = createXHR(e.data.dburl + "/db/data/transaction/" + currentTransaction + "/commit", e.data.auth);
|
|
||||||
xmlhttp.send()
|
|
||||||
var endtime = new Date()
|
|
||||||
var timediff = Math.abs(endtime.getTime() - starttime.getTime())
|
|
||||||
var diffsec = Math.ceil(timediff / 1000)
|
|
||||||
var topost = []
|
|
||||||
topost.message = 'end'
|
|
||||||
topost.len = data.length
|
|
||||||
topost.time = diffsec
|
|
||||||
postMessage(topost)
|
|
||||||
}else{
|
|
||||||
var topost = []
|
|
||||||
topost.message = 'baddata'
|
|
||||||
postMessage(topost)
|
|
||||||
}
|
|
||||||
}else if (e.data.type == 'sessions'){
|
|
||||||
var starttime = new Date();
|
|
||||||
if (testElement.hasOwnProperty('Computer') && testElement.hasOwnProperty('User')){
|
|
||||||
var topost = []
|
|
||||||
topost.message = 'start'
|
|
||||||
topost.len = data.length
|
|
||||||
postMessage(topost)
|
|
||||||
var xmlhttp = createXHR(e.data.dburl + '/db/data/transaction', e.data.auth);
|
|
||||||
xmlhttp.send()
|
|
||||||
currentTransaction = JSON.parse(xmlhttp.responseText).commit.split('/').slice(-2)[0]
|
|
||||||
var postdata = null
|
|
||||||
groups.forEach(function(g){
|
|
||||||
postdata = {}
|
|
||||||
postdata.statements = []
|
|
||||||
g.forEach(function(o){
|
|
||||||
var user = o['User'].toUpperCase()
|
|
||||||
var computer = o['Computer'].toUpperCase()
|
|
||||||
|
|
||||||
var obj = {
|
|
||||||
"statement": 'MERGE (user:User {name: "' + user + '"}) WITH user MERGE (computer:Computer {name: "' + computer + '" }) WITH user,computer MERGE (computer)-[:HasSession]->(user)'
|
|
||||||
}
|
|
||||||
|
|
||||||
postdata.statements.push(obj)
|
|
||||||
})
|
|
||||||
|
|
||||||
xmlhttp = createXHR(e.data.dburl + "/db/data/transaction/" + currentTransaction, e.data.auth)
|
|
||||||
xmlhttp.send(JSON.stringify(postdata));
|
|
||||||
|
|
||||||
sent = sent + g.length
|
|
||||||
var topost = []
|
|
||||||
topost.message = 'progress'
|
|
||||||
topost.len = data.length
|
|
||||||
topost.progress = sent
|
|
||||||
postMessage(topost)
|
|
||||||
if ((sent % 20000) == 0){
|
|
||||||
xmlhttp = createXHR(e.data.dburl + "/db/data/transaction/" + currentTransaction + "/commit", e.data.auth);
|
|
||||||
xmlhttp.send()
|
|
||||||
var topost = []
|
|
||||||
topost.message = 'commit'
|
|
||||||
postMessage(topost)
|
|
||||||
xmlhttp = createXHR(e.data.dburl + "/db/data/transaction", e.data.auth);
|
|
||||||
xmlhttp.send()
|
|
||||||
currentTransaction = JSON.parse(xmlhttp.responseText).commit.split('/').slice(-2)[0]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
var xmlhttp = createXHR(e.data.dburl + "/db/data/transaction/" + currentTransaction + "/commit", e.data.auth);
|
|
||||||
xmlhttp.send()
|
|
||||||
var endtime = new Date()
|
|
||||||
var timediff = Math.abs(endtime.getTime() - starttime.getTime())
|
|
||||||
var diffsec = Math.ceil(timediff / 1000)
|
|
||||||
var topost = []
|
|
||||||
topost.message = 'end'
|
|
||||||
topost.len = data.length
|
|
||||||
topost.time = diffsec
|
|
||||||
postMessage(topost)
|
|
||||||
}else{
|
|
||||||
var topost = []
|
|
||||||
topost.message = 'baddata'
|
|
||||||
postMessage(topost)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, false)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id="datatemplate" type="text/template">
|
|
||||||
<ul class='nav nav-tabs nav-justified' id="abc" role="tablist">
|
|
||||||
<li role='presentation' class='active'><a href='#dbinfo' role='tab' data-toggle='tab' aria-controls='dbinfo'>Database Info</a></li>
|
|
||||||
<li role='presentation'><a href='#nodeinfo' role='tab' data-toggle='tab' aria-controls='nodeinfo'>Node Info</a></li>
|
|
||||||
<li role='presentation'><a href='#pbqueries' role='tab' data-toggle='tab' aria-controls='pbqueries'>Queries</a></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="tab-content content">
|
|
||||||
<div role="tabpanel" class="tab-pane active" id="dbinfo">
|
|
||||||
{{{dbinfo}}}
|
|
||||||
</div>
|
|
||||||
<div role="tabpanel" class="tab-pane" id="nodeinfo">
|
|
||||||
{{{nodeinfo}}}
|
|
||||||
</div>
|
|
||||||
<div role="tabpanel" class="tab-pane" id="pbqueries">
|
|
||||||
<h3 align="center">
|
|
||||||
Pre-Built Analytics Queries
|
|
||||||
</h3>
|
|
||||||
<div style="padding: 0 .5em .5em .5em">
|
|
||||||
<a href="#" onclick="shortestPathsToDA()">Find Shortest Paths to DA</a><br>
|
|
||||||
<a href="#" onclick="doQuery('start n=node(*) match n<-[r:HasSession]-() WHERE NOT n.name="ANONYMOUS LOGON" AND NOT n.name="" with n, count(r) as rel_count order by rel_count desc LIMIT 1 MATCH (m)-[r:HasSession]->n RETURN n,r,m')">Find User with Most Sessions</a><br>
|
|
||||||
<a href="#" onclick="doQuery('MATCH (n:Domain) MATCH p=(n)-[r]-() RETURN p')">Map Domain Trusts</a><br>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id="dbdata" type="text/template">
|
|
||||||
<div>
|
|
||||||
<h3 align="center">Database Info</h3>
|
|
||||||
<dl class="dl-horizontal dl-horizontal-fix">
|
|
||||||
<dt>DB Address</dt>
|
|
||||||
<dd>{{url}}</dd>
|
|
||||||
<dt>DB User</dt>
|
|
||||||
<dd>{{user}}</dd>
|
|
||||||
<dt>Users</dt>
|
|
||||||
<dd>{{num_users}}</dd>
|
|
||||||
<dt>Computers</dt>
|
|
||||||
<dd>{{num_computers}}</dd>
|
|
||||||
<dt>Groups</dt>
|
|
||||||
<dd>{{num_groups}}</dd>
|
|
||||||
<dt>Relationships</dt>
|
|
||||||
<dd>{{num_relationships}}</dd>
|
|
||||||
</dl>
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="dbbuttons">
|
|
||||||
<button type="button" class="btn btn-success" onclick="getDBInfo()">Refresh DB Stats</button>
|
|
||||||
<button type="button" class="btn btn-warning" data-toggle="modal" data-target="#logoutModal">Log Out/Switch DB</button>
|
|
||||||
<button type="button" class="btn btn-danger" data-toggle="modal" data-target="#clearDBWarn">Clear Database</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id="computertemplate" type="text/template">
|
|
||||||
<dl class='dl-horizontal'>
|
|
||||||
<dt>Node</dt>
|
|
||||||
<dd>{{label}}</dd>
|
|
||||||
<dt>OS</dt>
|
|
||||||
<dd> None </dd>
|
|
||||||
<dt>Allows Unconstrained Delegation Name</dt>
|
|
||||||
<dd> None </dd>
|
|
||||||
<br>
|
|
||||||
<dt>Explicit Admins</dt>
|
|
||||||
<dd> <a href="#" onclick="doQuery('MATCH (n)-[r:AdminTo]->(m:Computer {name:"{{label}}"}) RETURN n,r,m')">{{explicit_admins}}</a></dd>
|
|
||||||
<dt>Unrolled Admins</dt>
|
|
||||||
<dd><a href="#" onclick="doQuery('MATCH (n:User),(target:Computer {name:"{{label}}"}), p=allShortestPaths((n)-[:AdminTo|MemberOf*1..]->(target)) RETURN p', "{{label}}")">{{unrolled_admin}}</a></dd>
|
|
||||||
<br>
|
|
||||||
<dt>First Degree Group Membership</dt>
|
|
||||||
<dd><a href="#" onclick="doQuery('MATCH (n:Computer {name:"{{label}}"}),(target:Group), (n)-[r:MemberOf]->(target) RETURN n,r,target', "{{label}}")">{{first_degree_group}}</a></dd>
|
|
||||||
<dt>Unrolled Group Membership</dt>
|
|
||||||
<dd><a href="#" onclick="doQuery('MATCH (n:Computer {name:"{{label}}"}), (target:Group),p=allShortestPaths((n)-[:MemberOf*1..]->(target)) RETURN p', "{{label}}")">{{unrolled_group_membership}}</a></dd>
|
|
||||||
<dt>Sessions</dt>
|
|
||||||
<dd><a href="#" onclick="doQuery('MATCH (m:Computer {name:"{{label}}"})-[r:HasSession]->(n:User) WITH n,r,m WHERE NOT n.name ENDS WITH "$" RETURN n,r,m')">{{sessions}}</a></dd>
|
|
||||||
</dl>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id="grouptemplate" type="text/template">
|
|
||||||
<dl class='dl-horizontal'>
|
|
||||||
<dt>Node</dt>
|
|
||||||
<dd>{{label}}</dd>
|
|
||||||
<br>
|
|
||||||
<dt>Direct Members</dt>
|
|
||||||
<dd> <a href="#" onclick="doQuery('MATCH (n)-[r:MemberOf]->(m:Group {name:"{{label}}"}) RETURN n,r,m')">{{explicit_members}}</a></dd>
|
|
||||||
<dt>Unrolled Members</dt>
|
|
||||||
<dd><a href="#" onclick="doQuery('MATCH (n:User), (m:Group {name:"{{label}}"}), p=allShortestPaths((n)-[:MemberOf*1..]->(m)) RETURN p', "{{label}}")">{{unrolled_members}}</a></dd>
|
|
||||||
<br>
|
|
||||||
<dt>Direct Admin To</dt>
|
|
||||||
<dd><a href="#" onclick="doQuery('MATCH (n:Group {name:"{{label}}"})-[r:AdminTo]->(m:Computer) RETURN n,r,m', "{{label}}")">{{adminto}}</a></dd>
|
|
||||||
<dt>Derivative Admin To</dt>
|
|
||||||
<dd><a href="#" onclick="doQuery('MATCH (n:Group {name:"{{label}}"}), (target:Computer), p=allShortestPaths((n)-[*]->(target)) RETURN p', "{{label}}")">{{derivative_admin}}</a>
|
|
||||||
<dt>Unrolled Member Of</dt>
|
|
||||||
<dd><a href="#" onclick="doQuery('MATCH (n:Group {name:"{{label}}"}), (target:Group), p=allShortestPaths((n)-[r:MemberOf*1..]->(target)) RETURN p', "{{label}}")">{{unrolled_member_of}}</a></dd>
|
|
||||||
<dt>Sessions</dt>
|
|
||||||
<dd><a href="#" onclick="doQuery('MATCH (n:User), (m:Group {name: "{{label}}"}), p=allShortestPaths((n)-[r:MemberOf*1..]->(m)) WITH n,m,r MATCH (n)-[s:HasSession]-(o:Computer) RETURN m,n,r,o,s',"", "{{label}}")">{{sessions}}</a></dd>
|
|
||||||
</dl>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id="ingestProgressBar" type="text/template">
|
|
||||||
<div class="panel-heading">
|
|
||||||
Ingesting Data
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<div class="progress" style="width:100%">
|
|
||||||
<div class="progress-bar progress-bar-striped active" role="progressbar" aria-valuenow="{{progress}}" aria-valuemin="0" aria-valuemax="{{total}}" style="width:{{width}}%">
|
|
||||||
<span>{{progress}} / {{total}}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-footer">
|
|
||||||
<button id="startingestbutton" class="btn btn-large" onclick="cancelIngest()">Cancel</button>
|
|
||||||
</div>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id="ingestComplete" type="text/template">
|
|
||||||
<div class="panel-heading">
|
|
||||||
Ingest Finished
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<div class="progress" style="width:100%">
|
|
||||||
<div class="progress-bar progress-bar-striped active" role="progressbar" aria-valuenow="{{total}}" aria-valuemin="0" aria-valuemax="{{total}}" style="width:100%">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-center">
|
|
||||||
<span>Ingest Complete in {{time}} seconds! <i class="fa fa-check-circle green-icon-color"></i></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id="ingestCancelled" type="text/template">
|
|
||||||
<div class="panel-heading">
|
|
||||||
Ingest Cancelled
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<div class="text-center">
|
|
||||||
<span>Ingest Cancelled! <i class="fa fa-times-circle red-icon-color"></i></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body role="document">
|
|
||||||
<div id="overlay" class="loginwindow">
|
|
||||||
<div id="loginpanel">
|
|
||||||
<img src="img/logo-white-transparent-full.png">
|
|
||||||
<div class="text-center">
|
|
||||||
<span>Log in to Neo4j Database</span>
|
|
||||||
</div>
|
|
||||||
<form>
|
|
||||||
<div class="form-group has-feedback">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon" id="dburladdon">Database URL</span>
|
|
||||||
<input id="dburl" type="text" class="form-control" placeholder="http://db-ip:dp-port" aria-describedby="dburladdon">
|
|
||||||
<i id="dburlspinner" class="fa fa-spinner fa-spin form-control-feedback"></i>
|
|
||||||
</div>
|
|
||||||
<p id="dbHelpBlock" class="help-block help-block-add hide">No Neo4j REST API Found</p>
|
|
||||||
<div class="input-group spacing">
|
|
||||||
<span class="input-group-addon" id="dbuseraddon"> DB Username</span>
|
|
||||||
<input id="dbusername" type="text" class="form-control" placeholder="neo4j" aria-describedby="dbuseraddon">
|
|
||||||
</div>
|
|
||||||
<div class="input-group spacing">
|
|
||||||
<span class="input-group-addon" id="dbpwaddon"> DB Password</span>
|
|
||||||
<input id="dbpassword" type="password" class="form-control" placeholder="neo4j" aria-describedby="dbpwaddon">
|
|
||||||
</div>
|
|
||||||
<p id="loginbadpw" class="help-block help-block-add hide" style="color: #d9534f">Wrong username or password</p>
|
|
||||||
<button id="loginbutton" disabled=true type="button" class="btn btn-primary loginbutton has-spinner">
|
|
||||||
Login
|
|
||||||
<span class="spinner"><i class="fa fa-spinner fa-spin"></i></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div id="checkconnectionpanel">
|
|
||||||
<span id="connectionstatus">Checking Stored Credentials <i class="fa fa-spinner fa-spin"></i></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="settingspanel">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="nodataalert" class="alert alert-warning alert-dismissible alertdiv" role="alert">
|
|
||||||
<button type="button" class="close" onclick="$('#nodataalert').fadeToggle(false)" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>No data returned from query.
|
|
||||||
</div>
|
|
||||||
<div id="layoutchange" class="alert alert-warning alert-dismissible alertdiv" role="alert">
|
|
||||||
<button type="button" class="close" onclick="$('#layoutchange').fadeToggle(false)" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>Changed Layout To Hierarchical
|
|
||||||
</div>
|
|
||||||
<div id="graph" class="graph"> </div>
|
|
||||||
<div id= "bottomdiv" class="bottomdiv">
|
|
||||||
<button id="bottomSlide" class="slideupbutton">
|
|
||||||
<span class="glyphicon glyphicon-chevron-up"></span> Raw Query <span class="glyphicon glyphicon-chevron-up"></span>
|
|
||||||
</button>
|
|
||||||
<input type="text" class="form-control queryInput" autocomplete="off" id="rawQueryBox" placeholder="Enter a raw query...">
|
|
||||||
</div>
|
|
||||||
<div id="searchDiv" class="searchdiv">
|
|
||||||
<div class="input-group input-group-unstyled">
|
|
||||||
<span id="menu" class="input-group-addon spanfix" data-toggle="tooltip" data-placement="bottom" title="More Info">
|
|
||||||
<i class="glyphicon glyphicon-menu-hamburger menuglyph"></i></span>
|
|
||||||
<input id="searchBar" type="search" class="form-control searchbox" autocomplete="off" placeholder="Start typing to search for a node...">
|
|
||||||
<span id="openpath" class="input-group-addon spanfix" data-toggle="tooltip" data-placement="bottom" title="Pathfinding">
|
|
||||||
<i class="glyphicon glyphicon glyphicon-road menuglyph"></i></span>
|
|
||||||
<span id="back" class="input-group-addon spanfix" data-toggle="tooltip" data-placement="bottom" title="Back">
|
|
||||||
<i class="glyphicon glyphicon-step-backward menuglyph"></i></span>
|
|
||||||
</div>
|
|
||||||
<div id="pathfindingbox">
|
|
||||||
<div class="input-group input-group-unstyled">
|
|
||||||
<span class="input-group-addon spanfix" style="visibility: hidden">
|
|
||||||
<i class="glyphicon glyphicon-menu-hamburger menuglyph"></i></span>
|
|
||||||
<input id="endNode" type="search" class="form-control searchbox" autocomplete="off" placeholder="Target Node">
|
|
||||||
<span class="input-group-addon spanfix" style="visibility: hidden">
|
|
||||||
<i class="glyphicon glyphicon glyphicon-road menuglyph"></i></span>
|
|
||||||
<span id="play" class="input-group-addon spanfix" data-toggle="tooltip" data-placement="bottom" title="Findpath">
|
|
||||||
<i class="glyphicon glyphicon-play menuglyph"></i></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="nodedatabox" id="nodedatabox">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="menuDiv" class="menudiv">
|
|
||||||
<div>
|
|
||||||
<button id="refreshbutton" class="btn">
|
|
||||||
<span class="glyphicon glyphicon-refresh rightspan"></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button id="exportbutton" class="btn">
|
|
||||||
<span class="glyphicon glyphicon-export rightspan"></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button id="importbutton" class="btn">
|
|
||||||
<span class="glyphicon glyphicon-import rightspan"></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button id="uploadbutton" class="btn">
|
|
||||||
<span class="glyphicon glyphicon-upload rightspan"></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<button id="layoutbutton" class="btn">
|
|
||||||
<span style="width:14px" class="fa fa-line-chart rightspan"></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<button id="settingsbutton" class="btn">
|
|
||||||
<span style="width:14px" class="fa fa-cogs rightspan"></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="uploadSelectDiv" class="uploadSelectDiv panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
Ingest CSV
|
|
||||||
<button type="button" class="close" onclick="$('#uploadSelectDiv').fadeToggle(false)" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-body ingestinput">
|
|
||||||
<div class="input-group" id="fileselectdiv">
|
|
||||||
<span class="input-group-addon" id="addonforfile">File Name:</span>
|
|
||||||
<input id="uploadFileSelected" aria-described-by="addonforfile" class="form-control">
|
|
||||||
<span class="input-group-btn">
|
|
||||||
<button id="selectUploadFile" class="btn btn-default">
|
|
||||||
<span class="glyphicon glyphicon-file"></span>
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="list-group" style="margin-top:15px;">
|
|
||||||
<a href="#" id="ingestlocaladmin" class="list-group-item active">
|
|
||||||
<h4 class="list-group-item-heading">Local Admin Membership</h4>
|
|
||||||
<p class="list-group-item-text">
|
|
||||||
Data containing members of local Administrator groups
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="#" id="ingestdomaingroup" class="list-group-item">
|
|
||||||
<h4 class="list-group-item-heading">Domain Group Membership</h4>
|
|
||||||
<p class="list-group-item-text">
|
|
||||||
Data containing members of domain groups
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="#" id="ingestusersessions" class="list-group-item">
|
|
||||||
<h4 class="list-group-item-heading">User Sessions</h4>
|
|
||||||
<p class="list-group-item-text">
|
|
||||||
Data containing login sessions for users
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="text-align: center">
|
|
||||||
<button id="startingestbutton" class="btn btn-large">Ingest Data</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="exportSelectDiv" class="uploadSelectDiv panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
Export Graph
|
|
||||||
<button type="button" class="close" onclick="$('#exportSelectDiv').fadeToggle(false)" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-body">
|
|
||||||
<div class="list-group">
|
|
||||||
<a href="#" id="exportjson" class="list-group-item active">
|
|
||||||
<h4 class="list-group-item-heading">Export to JSON</h4>
|
|
||||||
<p class="list-group-item-text">
|
|
||||||
Use this format to export data and re-import it later
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="#" id="exportimage" class="list-group-item">
|
|
||||||
<h4 class="list-group-item-heading">Export to Image</h4>
|
|
||||||
<p class="list-group-item-text">
|
|
||||||
Use this format to export data and view it as an image
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="text-align: center">
|
|
||||||
<button id="exportFinishButton" class="btn btn-lg">Export Data</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button id="refreshbuttonhidden" class="btn rightmenubutton hideme">
|
|
||||||
Refresh<span class="glyphicon glyphicon-refresh rightglyph"></span>
|
|
||||||
</button>
|
|
||||||
<button id="exportbuttonhidden" class="btn rightmenubutton hideme">
|
|
||||||
Export Graph<span class="glyphicon glyphicon-export rightglyph"></span>
|
|
||||||
</button>
|
|
||||||
<button id="importbuttonhidden" class="btn rightmenubutton hideme">
|
|
||||||
Import Graph<span class="glyphicon glyphicon-import rightglyph"></span>
|
|
||||||
</button>
|
|
||||||
<button id="uploadbuttonhidden" class="btn rightmenubutton hideme">
|
|
||||||
Upload Data<span class="glyphicon glyphicon-upload rightglyph"></span>
|
|
||||||
</button>
|
|
||||||
<button id="layoutbuttonhidden" class="btn rightmenubutton hideme">
|
|
||||||
Change Layout Type<span class="fa fa-line-chart rightglyph"></span>
|
|
||||||
</button>
|
|
||||||
<button id="settingsbuttonhidden" class="btn rightmenubutton hideme">
|
|
||||||
Settings<span class="fa fa-cogs rightglyph"></span>
|
|
||||||
</button>
|
|
||||||
<input type="file" accept=".json" id="fileloader" name="files" title="Open JSON" style="display:none">
|
|
||||||
<input type="file" accept=".csv" id="uploader" name="import" title="Open CSV for Import" style="display:none">
|
|
||||||
|
|
||||||
<div id="logoutModal" class="modal fade" role="dialog" aria-labelledby="logoutModalLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
<h4 class="modal-title" id="logoutModalLabel">Logout</h4>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>Are you sure you want to logout?</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-danger" onclick="resetUI()">Logout</button>
|
|
||||||
<button type="button" class="btn btn-primary" data-dismiss="modal">Cancel</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="clearDBWarn" class="modal fade" role="dialog" aria-labelledby="clearDBWarnLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
<h4 class="modal-title" id="clearDBWarnLabel">Clear Database</h4>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>Are you sure you want to clear the database? This is irreversible and may take some time!</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-danger" data-toggle="modal" data-target="#clearDBConfirm" data-dismiss="modal">Clear Database</button>
|
|
||||||
<button type="button" class="btn btn-primary" data-dismiss="modal">Cancel</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="clearDBConfirm" class="modal fade" role="dialog" aria-labelledby="clearDBConfirmLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
<h4 class="modal-title" id="clearDBConfirmLabel">Clear Database</h4>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>Are you ABSOLUTELY sure you want to clear the database?</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-primary" data-dismiss="modal">Cancel</button>
|
|
||||||
<button type="button" class="btn btn-danger" data-dismiss="modal" data-toggle="modal" data-target="#deleteProgressModal" onclick="clearDB()">Clear Database</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="badDataModal" class="modal fade" role="dialog" aria-labelledby="badDataLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
<h4 class="modal-title" id="badDataLabel">Invalid Data</h4>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>Provided CSV doesn't match the selected data type!</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-primary" data-dismiss="modal">Ok</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="deleteProgressModal" class="modal fade" role="dialog" data-keyboard="false" data-backdrop="static" aria-labelledby="deleteDBLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h4 class="modal-title" id="deleteDBLabel">Clearing Database</h4>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="progress">
|
|
||||||
<div class="progress-bar progress-bar-striped active" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="zoomBox" class="zoomBox">
|
|
||||||
<div>
|
|
||||||
<button id="zoomIn" class="btn zoomIn">
|
|
||||||
<span class="fa fa-plus"></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button id="zoomOut" class="btn zoomOut">
|
|
||||||
<span class="fa fa-minus"></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="spotlight" class="spotlight">
|
|
||||||
<div class="input-group input-group-unstyled unborder">
|
|
||||||
<span class="input-group-addon spanfix">
|
|
||||||
<i class="glyphicon glyphicon-search"></i>
|
|
||||||
</span>
|
|
||||||
<input id="spotlightBar" type="search" class="form-control searchbox" autocomplete="off" placeholder="Explore Nodes" data-type="search">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="nodeList">
|
|
||||||
<table id="spotlightTable" data-role="table" class="table table-striped" data-filter="true" data-input="#spotlightBar">
|
|
||||||
<thead style="text-align:center">
|
|
||||||
<tr>
|
|
||||||
<td>Node Label</td>
|
|
||||||
<td>Collapsed Into</td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="nodeBody" class="searchable">
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="nodeSelector" class="spotlight">
|
|
||||||
<div class="input-group input-group-unstyled unborder">
|
|
||||||
<span class="input-group-addon spanfix">
|
|
||||||
<i class="glyphicon glyphicon-search"></i>
|
|
||||||
</span>
|
|
||||||
<input id="nodeSelectorBar" type="search" class="form-control searchbox" autocomplete="off" placeholder="Filter Nodes" data-type="search">
|
|
||||||
</div>
|
|
||||||
<div class="nodeList">
|
|
||||||
<table id="nodeSelectorTable" data-role="table" class="table table-striped" data-filter="true" data-input="#nodeSelectorBar">
|
|
||||||
<thead style="text-align:center">
|
|
||||||
<tr>
|
|
||||||
<td>Choose a Node</td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="nodeSelectorBody" class="searchablex">
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="queryLoad" class="loadingIndicator">
|
|
||||||
<div id="loadingText">Loading</div>
|
|
||||||
<div id="circle"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="settingsDiv" class="settingsDiv panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
Settings
|
|
||||||
<button type="button" class="close" onclick="$('#settingsDiv').fadeToggle(false)" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-body sliderfix">
|
|
||||||
<div>
|
|
||||||
<strong>Sibling Collapse Threshold</strong> <i data-toggle="tooltip" data-placement="right" title="Merge nodes that have the same parent. 0 to Disable, Default 10" id="siblingCollapseQuestion" class="glyphicon glyphicon-question-sign"></i><br>
|
|
||||||
<input type="text" data-slider="true" data-slider-range="0,20" data-slider-step="1" data-slider-theme="volume slideinline" id="siblingCollapseSlider">
|
|
||||||
<span>
|
|
||||||
<input type="number" min="0" max="20" class="sliderinput" id="siblingCollapseInput">
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<strong>Node Collapse Threshold</strong> <i data-toggle="tooltip" data-placement="right" title="Collapse nodes at the end of paths that only have one relationship. 0 to Disable, Default 5" id="nodeCollapseQuestion" class="glyphicon glyphicon-question-sign"></i><br>
|
|
||||||
<input type="text" data-slider="true" data-slider-range="0,20" data-slider-step="1" data-slider-theme="volume slideinline" id="nodeCollapseSlider">
|
|
||||||
<span>
|
|
||||||
<input type="number" min="0" max="20" class="sliderinput" id="nodeCollapseInput">
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="checkbox-inline" style="padding-top:5px">
|
|
||||||
<label>
|
|
||||||
<input id="lowDetailCheck" type="checkbox"> Low Detail Mode
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<i data-toggle="tooltip" data-placement="right" title="Lower detail of graph to improve performance" id="siblingCollapseQuestion" class="glyphicon glyphicon-question-sign"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 145 KiB |
Before Width: | Height: | Size: 140 KiB |
|
@ -1,28 +0,0 @@
|
||||||
<script id="template" type="text/template">
|
|
||||||
<dl class='dl-horizontal'>
|
|
||||||
<dt>Node:</dt>
|
|
||||||
<dd>{{label}}</dd>
|
|
||||||
<dt>SAMAccountName</dt>
|
|
||||||
<dd> None </dd>
|
|
||||||
<dt>Display Name</dt>
|
|
||||||
<dd> None </dd>
|
|
||||||
<dt>Password Last Changed</dt>
|
|
||||||
<dd> None </dd>
|
|
||||||
<br>
|
|
||||||
<dt>First Degree Group Memberships</dt>
|
|
||||||
<dd><a href="#" onclick="doQuery('MATCH (n:User {name:"{{label}}"}), (target:Group),p=allShortestPaths((n)-[:MemberOf*1]->(target)) RETURN p')">{{first_degree_group}}</a></dd>
|
|
||||||
<dt>Unrolled Group Memberships</dt>
|
|
||||||
<dd><a href="#" onclick="doQuery('MATCH (n:User {name:"{{label}}"}), (target:Group),p=allShortestPaths((n)-[:MemberOf*1..]->(target)) RETURN p', "{{label}}")">{{unrolled}}</a></dd>
|
|
||||||
<dt>Foreign Group Memberships</dt>
|
|
||||||
<dd></dd>
|
|
||||||
<br>
|
|
||||||
<dt>First Degree Local Admin</dt>
|
|
||||||
<dd><a href="#" onclick="doQuery('MATCH (n:User {name:"{{label}}"}), (target:Computer), p=allShortestPaths((n)-[:AdminTo*1]->(target)) RETURN p')">{{first_degree_admin}}</a></dd>
|
|
||||||
<dt>Group Delegated Local Admin Rights</dt>
|
|
||||||
<dd><a href="#" onclick="doQuery('MATCH (n:User {name:"{{label}}"}), (m:Group), x=allShortestPaths((n)-[r:MemberOf*1..]->(m)) WITH n,m,r MATCH (m)-[s:AdminTo*1..]->(p:Computer) RETURN n,m,r,s,p', "{{label}}", "")">{{dlar}}</a></dd>
|
|
||||||
<dt>Derivative Local Admin Rights</dt>
|
|
||||||
<dd><a href="#" onclick="doQuery('MATCH (n:User {name:"{{label}}"}), (m:Computer), p=allShortestPaths((n)-[r*]->(m)) RETURN p', "{{label}}")">{{derivative}}</a></dd>
|
|
||||||
<dt>Sessions</dt>
|
|
||||||
<dd><a href="#" onclick="doQuery('MATCH (n:Computer)-[r:HasSession]->(m:User {name:"{{label}}"}) RETURN n,r,m')">{{sessions}}</a></dd>
|
|
||||||
</dl>
|
|
||||||
</script>
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Hello World!</title>
|
||||||
|
<link type="text/css" rel="stylesheet" href="src/css/font-awesome.min.css">
|
||||||
|
<link type="text/css" rel="stylesheet" href="src/css/bootstrap.min.css">
|
||||||
|
<link type="text/css" rel="stylesheet" href="src/css/styles.css">
|
||||||
|
<script>window.$ = window.jQuery = require('jquery');</script>
|
||||||
|
<script>window.sigma = require('linkurious');</script>
|
||||||
|
<script src="src/js/plugins.min.js" type="text/javascript"></script>
|
||||||
|
<script src="src/js/bootstrap3-typeahead.min.js" type="text/javascript"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root">
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
const script = document.createElement('script');
|
||||||
|
if (process.env.ENV === 'development '){
|
||||||
|
script.src = 'http://localhost:9000/dist/bundle.js';
|
||||||
|
}else{
|
||||||
|
script.src = './dist/bundle.js';
|
||||||
|
}
|
||||||
|
document.write(script.outerHTML);
|
||||||
|
}());
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// You can also require other files to run in this process
|
||||||
|
require('./renderer.js')
|
||||||
|
</script>
|
||||||
|
</html>
|
42
main.js
|
@ -1,53 +1,53 @@
|
||||||
const electron = require('electron');
|
const electron = require('electron')
|
||||||
// Module to control application life.
|
// Module to control application life.
|
||||||
const {app} = electron;
|
const app = electron.app
|
||||||
// Module to create native browser window.
|
// Module to create native browser window.
|
||||||
const {BrowserWindow} = electron;
|
const BrowserWindow = electron.BrowserWindow
|
||||||
|
|
||||||
// Keep a global reference of the window object, if you don't, the window will
|
// Keep a global reference of the window object, if you don't, the window will
|
||||||
// be closed automatically when the JavaScript object is garbage collected.
|
// be closed automatically when the JavaScript object is garbage collected.
|
||||||
let win;
|
let mainWindow
|
||||||
|
|
||||||
function createWindow() {
|
function createWindow () {
|
||||||
// Create the browser window.
|
// Create the browser window.
|
||||||
win = new BrowserWindow({width: 1920, height: 1080});
|
mainWindow = new BrowserWindow({width: 800, height: 600})
|
||||||
|
|
||||||
// and load the index.html of the app.
|
// and load the index.html of the app.
|
||||||
win.loadURL(`file://${__dirname}/app/Bloodhound.html`);
|
mainWindow.loadURL(`file://${__dirname}/index.html`)
|
||||||
|
|
||||||
// Open the DevTools.
|
// Open the DevTools.
|
||||||
win.webContents.openDevTools();
|
mainWindow.webContents.openDevTools()
|
||||||
|
|
||||||
// Emitted when the window is closed.
|
// Emitted when the window is closed.
|
||||||
win.on('closed', () => {
|
mainWindow.on('closed', function () {
|
||||||
// Dereference the window object, usually you would store windows
|
// Dereference the window object, usually you would store windows
|
||||||
// in an array if your app supports multi windows, this is the time
|
// in an array if your app supports multi windows, this is the time
|
||||||
// when you should delete the corresponding element.
|
// when you should delete the corresponding element.
|
||||||
win = null;
|
mainWindow = null
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method will be called when Electron has finished
|
// This method will be called when Electron has finished
|
||||||
// initialization and is ready to create browser windows.
|
// initialization and is ready to create browser windows.
|
||||||
// Some APIs can only be used after this event occurs.
|
// Some APIs can only be used after this event occurs.
|
||||||
app.on('ready', createWindow);
|
app.on('ready', createWindow)
|
||||||
|
|
||||||
// Quit when all windows are closed.
|
// Quit when all windows are closed.
|
||||||
app.on('window-all-closed', () => {
|
app.on('window-all-closed', function () {
|
||||||
// On macOS it is common for applications and their menu bar
|
// On OS X it is common for applications and their menu bar
|
||||||
// to stay active until the user quits explicitly with Cmd + Q
|
// to stay active until the user quits explicitly with Cmd + Q
|
||||||
if (process.platform !== 'darwin') {
|
if (process.platform !== 'darwin') {
|
||||||
app.quit();
|
app.quit()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
app.on('activate', () => {
|
app.on('activate', function () {
|
||||||
// On macOS it's common to re-create a window in the app when the
|
// On OS X it's common to re-create a window in the app when the
|
||||||
// dock icon is clicked and there are no other windows open.
|
// dock icon is clicked and there are no other windows open.
|
||||||
if (win === null) {
|
if (mainWindow === null) {
|
||||||
createWindow();
|
createWindow()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
// In this file you can include the rest of your app's specific main process
|
// In this file you can include the rest of your app's specific main process
|
||||||
// code. You can also put them in separate files and require them here.
|
// code. You can also put them in separate files and require them here.
|
59
package.json
|
@ -1,22 +1,47 @@
|
||||||
{
|
{
|
||||||
"name": "bloodhound",
|
"name": "bloodhound",
|
||||||
"version": "0.1.0",
|
"version": "1.0.0",
|
||||||
"main": "./main.js",
|
"description": "Graph Theory for Active Directory",
|
||||||
"productName": "Bloodhound",
|
"main": "main.js",
|
||||||
"devDependencies": {
|
|
||||||
"electron-packager": "^7.3.0",
|
|
||||||
"electron-prebuilt": "^1.2.7"
|
|
||||||
},
|
|
||||||
"build": {
|
|
||||||
"appId": "12345",
|
|
||||||
"app-category-type": "your.app.category.type",
|
|
||||||
"win": {
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "install-app-deps",
|
"start": "set ENV=development & electron .",
|
||||||
"pack": "build --dir",
|
"server": "babel-node server.js"
|
||||||
"dist": "build",
|
},
|
||||||
"start": "electron ."
|
"babel": {
|
||||||
|
"presets": [
|
||||||
|
"es2015",
|
||||||
|
"stage-0",
|
||||||
|
"react"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"author": "@CptJesus",
|
||||||
|
"license": "CC0-1.0",
|
||||||
|
"devDependencies": {
|
||||||
|
"babel-cli": "^6.11.4",
|
||||||
|
"babel-core": "^6.11.4",
|
||||||
|
"babel-loader": "^6.2.4",
|
||||||
|
"babel-polyfill": "^6.9.1",
|
||||||
|
"babel-preset-es2015": "^6.9.0",
|
||||||
|
"babel-preset-react": "^6.11.1",
|
||||||
|
"babel-preset-stage-0": "^6.5.0",
|
||||||
|
"electron-prebuilt": "^1.2.8",
|
||||||
|
"express": "^4.14.0",
|
||||||
|
"webpack": "^1.13.1",
|
||||||
|
"webpack-dev-middleware": "^1.6.1",
|
||||||
|
"webpack-hot-middleware": "^2.12.1",
|
||||||
|
"webpack-target-electron-renderer": "^0.4.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"bootstrap": "^3.3.6",
|
||||||
|
"bootstrap-3-typeahead": "^4.0.1",
|
||||||
|
"configstore": "^2.0.0",
|
||||||
|
"electron-json-storage": "^2.0.0",
|
||||||
|
"eventemitter2": "^2.0.0",
|
||||||
|
"jquery": "^2.2.4",
|
||||||
|
"linkurious": "^1.5.1",
|
||||||
|
"react": "^15.2.1",
|
||||||
|
"react-bootstrap": "^0.30.0",
|
||||||
|
"react-dom": "^15.2.1",
|
||||||
|
"react-if": "^2.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
// This file is required by the index.html file and will
|
||||||
|
// be executed in the renderer process for that window.
|
||||||
|
// All of the Node.js APIs are available in this process.
|
|
@ -0,0 +1,20 @@
|
||||||
|
import express from 'express';
|
||||||
|
import webpack from 'webpack';
|
||||||
|
import webpackDevMiddleware from 'webpack-dev-middleware';
|
||||||
|
import webpackHotMiddleware from 'webpack-hot-middleware';
|
||||||
|
|
||||||
|
import config from './webpack.config.development';
|
||||||
|
|
||||||
|
const compiler = webpack(config);
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
app.use(webpackDevMiddleware(compiler, {
|
||||||
|
publicPath: config.output.publicPath,
|
||||||
|
stats: {
|
||||||
|
colors: true
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
app.use(webpackHotMiddleware(compiler));
|
||||||
|
|
||||||
|
app.listen(9000);
|
|
@ -0,0 +1,22 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import GraphContainer from './graph'
|
||||||
|
import SearchContainer from './SearchContainer/searchcontainer'
|
||||||
|
import SpotlightContainer from './Spotlight/spotlightcontainer'
|
||||||
|
import LogoutModal from './Modals/logoutmodal'
|
||||||
|
|
||||||
|
export default class AppContainer extends Component {
|
||||||
|
constructor(){
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="max">
|
||||||
|
<GraphContainer />
|
||||||
|
<SearchContainer />
|
||||||
|
<SpotlightContainer/>
|
||||||
|
<LogoutModal/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { If, Then, Else } from 'react-if';
|
||||||
|
|
||||||
|
export default class GlyphiconSpan extends Component {
|
||||||
|
propTypes: {
|
||||||
|
classes : React.PropTypes.string,
|
||||||
|
tooltipDir : React.PropTypes.string,
|
||||||
|
tooltipTitle : React.PropTypes.string,
|
||||||
|
tooltip : React.PropTypes.bool.isRequired,
|
||||||
|
click: React.PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props){
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<If condition={ this.props.tooltip }>
|
||||||
|
<Then>
|
||||||
|
<span onClick={this.props.click} className={this.props.classes} data-toggle="tooltip" data-placement={this.props.tooltipDir} title={this.props.tooltipTitle}>
|
||||||
|
{this.props.children}
|
||||||
|
</span>
|
||||||
|
</Then>
|
||||||
|
<Else>{() =>
|
||||||
|
<span onClick={this.props.click} className={this.props.classes}>
|
||||||
|
{this.props.children}
|
||||||
|
</span>
|
||||||
|
}</Else>
|
||||||
|
</If>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,158 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { collapseEdgeNodes, setNodeData, collapseSiblingNodes } from '../js/utils.js';
|
||||||
|
|
||||||
|
export default class GraphContainer extends Component {
|
||||||
|
constructor(props){
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
sigmaInstance : null,
|
||||||
|
design: null,
|
||||||
|
dragged: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div id="graph" className="graph"></div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
doQueryNative(params){
|
||||||
|
sigma.neo4j.cypher({
|
||||||
|
url: appStore.databaseInfo.url,
|
||||||
|
user: appStore.databaseInfo.user,
|
||||||
|
password: appStore.databaseInfo.password
|
||||||
|
},
|
||||||
|
params.statement,
|
||||||
|
this.state.sigmaInstance,
|
||||||
|
function(sigmaInstance){
|
||||||
|
var design = this.state.design;
|
||||||
|
sigmaInstance = setNodeData(this.state.sigmaInstance);
|
||||||
|
if (params.allowCollapse){
|
||||||
|
sigmaInstance = collapseEdgeNodes(sigmaInstance);
|
||||||
|
sigmaInstance = collapseSiblingNodes(sigmaInstance);
|
||||||
|
}
|
||||||
|
this.state.sigmaInstance = sigmaInstance
|
||||||
|
design.deprecate();
|
||||||
|
design.apply();
|
||||||
|
sigmaInstance.refresh();
|
||||||
|
this.state.design = design;
|
||||||
|
sigma.misc.animation.camera(sigmaInstance.camera, { x: 0, y: 0, ratio: 1.075 });
|
||||||
|
sigma.layouts.startForceLink()
|
||||||
|
}.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
doQueryEvent(){
|
||||||
|
this.doQueryNative({
|
||||||
|
statement: 'MATCH (n:Group) WHERE n.name =~ "(?i).*DOMAIN ADMINS.*" WITH n MATCH (n)<-[r:MemberOf]-(m) RETURN n,r,m',
|
||||||
|
allowCollapse: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
_nodeDragged(){
|
||||||
|
this.setState({dragged:true})
|
||||||
|
}
|
||||||
|
|
||||||
|
_nodeClicked(n){
|
||||||
|
if (!this.state.dragged){
|
||||||
|
if (n.data.node.type_user){
|
||||||
|
emitter.emit('userNodeClicked', n.data.node.label)
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
this.setState({dragged: false})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
emitter.on('query', this.doQueryEvent.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
//Sigma Initialization
|
||||||
|
var sigmaInstance, design;
|
||||||
|
sigma.renderers.def = sigma.renderers.canvas;
|
||||||
|
|
||||||
|
sigma.classes.graph.addMethod('outboundNodes', function(id) {
|
||||||
|
return this.outNeighborsIndex.get(id).keyList();
|
||||||
|
});
|
||||||
|
|
||||||
|
sigma.classes.graph.addMethod('inboundNodes', function(id) {
|
||||||
|
return this.inNeighborsIndex.get(id).keyList();
|
||||||
|
});
|
||||||
|
|
||||||
|
sigmaInstance = new sigma(
|
||||||
|
{
|
||||||
|
container: 'graph'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
sigmaInstance.settings(
|
||||||
|
{
|
||||||
|
edgeColor: 'default',
|
||||||
|
nodeColor: 'default',
|
||||||
|
minEdgeSize: 1,
|
||||||
|
maxEdgeSize: 2.5,
|
||||||
|
iconThreshold: 4,
|
||||||
|
labelThreshold: 15,
|
||||||
|
labelAlignment: 'bottom',
|
||||||
|
labelColor: 'default',
|
||||||
|
font: 'Roboto',
|
||||||
|
glyphFillColor: 'black',
|
||||||
|
glyphTextColor: 'white',
|
||||||
|
glyphTextThreshold: 1,
|
||||||
|
zoomingRatio: 1.4
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
sigmaInstance.renderers[0].bind('render', function(e) {
|
||||||
|
sigmaInstance.renderers[0].glyphs();
|
||||||
|
});
|
||||||
|
|
||||||
|
sigmaInstance.camera.bind('coordinatesUpdated', function(e){
|
||||||
|
if (e.target.ratio > 1.25){
|
||||||
|
sigmaInstance.settings('drawEdgeLabels', false);
|
||||||
|
}else{
|
||||||
|
sigmaInstance.settings('drawEdgeLabels', true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
var dragListener = sigma.plugins.dragNodes(sigmaInstance,
|
||||||
|
sigmaInstance.renderers[0])
|
||||||
|
|
||||||
|
dragListener.bind('drag', this._nodeDragged.bind(this))
|
||||||
|
|
||||||
|
sigmaInstance.bind('clickNode', this._nodeClicked.bind(this))
|
||||||
|
|
||||||
|
var fa = sigma.layouts.configForceLink(sigmaInstance, {
|
||||||
|
worker: true,
|
||||||
|
background: true,
|
||||||
|
easing: 'cubicInOut',
|
||||||
|
autoStop: true,
|
||||||
|
alignNodeSiblings: true,
|
||||||
|
barnesHutOptimize: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var lowgfx = appStore.performance.lowGraphics
|
||||||
|
|
||||||
|
design = sigma.plugins.design(sigmaInstance);
|
||||||
|
if (lowgfx){
|
||||||
|
sigmaInstance.settings('defaultEdgeType', 'line');
|
||||||
|
sigmaInstance.settings('defaultEdgeColor', 'black');
|
||||||
|
design.setPalette(appStore.lowResPalette);
|
||||||
|
design.setStyles(appStore.lowResStyle);
|
||||||
|
}else{
|
||||||
|
sigmaInstance.settings('defaultEdgeType', 'tapered');
|
||||||
|
sigmaInstance.settings('defaultEdgeColor', '#356');
|
||||||
|
design.setPalette(appStore.highResPalette);
|
||||||
|
design.setStyles(appStore.highResStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state.sigmaInstance = sigmaInstance;
|
||||||
|
this.state.design = design;
|
||||||
|
this.doQueryNative({
|
||||||
|
statement: 'MATCH (n:Group) WHERE n.name =~ "(?i).*DOMAIN ADMINS.*" WITH n MATCH (n)<-[r:MemberOf]-(m) RETURN n,r,m',
|
||||||
|
allowCollapse: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
export default class Icon extends Component {
|
||||||
|
propTypes: {
|
||||||
|
glyph : React.PropTypes.string.isRequired,
|
||||||
|
extraClass : React.PropTypes.string
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props){
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<i className={"glyphicon glyphicon-" + this.props.glyph + " " + this.props.extraClass}></i>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
var Modal = require('react-bootstrap').Modal;
|
||||||
|
|
||||||
|
export default class LogoutModal extends Component {
|
||||||
|
constructor(){
|
||||||
|
super();
|
||||||
|
this.state = {
|
||||||
|
open: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closeModal(){
|
||||||
|
this.setState({ open: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
closeAndLogout(){
|
||||||
|
this.setState({ open: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
openModal(){
|
||||||
|
this.setState({open: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
show={this.state.open}
|
||||||
|
onHide={this.closeModal}
|
||||||
|
aria-labelledby="LogoutModalHeader"
|
||||||
|
ref="logoutModal">
|
||||||
|
|
||||||
|
<Modal.Header closeButton={true}>
|
||||||
|
<Modal.Title id="LogoutModalHeader">Logout</Modal.Title>
|
||||||
|
</Modal.Header>
|
||||||
|
|
||||||
|
<Modal.Body>
|
||||||
|
<p>Are you sure you want to logout?</p>
|
||||||
|
</Modal.Body>
|
||||||
|
|
||||||
|
<Modal.Footer>
|
||||||
|
<button type="button" className="btn btn-danger" onClick={this.closeAndLogout.bind(this)}>
|
||||||
|
Logout
|
||||||
|
</button>
|
||||||
|
<button type="button" className="btn btn-primary" onClick={this.closeModal.bind(this)}>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</Modal.Footer>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { defaultAjaxSettings } from '../../js/utils.js';
|
||||||
|
import LogoutModal from '../Modals/logoutmodal';
|
||||||
|
|
||||||
|
export default class DatabaseDataDisplay extends Component {
|
||||||
|
constructor(){
|
||||||
|
super()
|
||||||
|
this.state = {
|
||||||
|
url: appStore.databaseInfo.url,
|
||||||
|
user: appStore.databaseInfo.user,
|
||||||
|
num_users: 'Refreshing',
|
||||||
|
num_computers: 'Refreshing',
|
||||||
|
num_groups: 'Refreshing',
|
||||||
|
num_relationships: 'Refreshing'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.refreshDBData()
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleLogoutModal(){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3>Database Info</h3>
|
||||||
|
<dl className="dl-horizontal dl-horizontal-fix">
|
||||||
|
<dt>DB Address</dt>
|
||||||
|
<dd>{this.state.url}</dd>
|
||||||
|
<dt>DB User</dt>
|
||||||
|
<dd>{this.state.user}</dd>
|
||||||
|
<dt>Users</dt>
|
||||||
|
<dd>{this.state.num_users}</dd>
|
||||||
|
<dt>Computers</dt>
|
||||||
|
<dd>{this.state.num_computers}</dd>
|
||||||
|
<dt>Groups</dt>
|
||||||
|
<dd>{this.state.num_groups}</dd>
|
||||||
|
<dt>Relationships</dt>
|
||||||
|
<dd>{this.state.num_relationships}</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="btn-group dbbuttons">
|
||||||
|
<button type="button" className="btn btn-success" onClick={function(){emitter.emit('query')}}>Refresh DB Stats</button>
|
||||||
|
<button type="button" className="btn btn-warning" onClick={this.toggleLogoutModal}>Log Out/Switch DB</button>
|
||||||
|
<button type="button" className="btn btn-danger" data-toggle="modal" data-target="#clearDBWarn">Clear Database</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshDBData(){
|
||||||
|
var user,computer,group,relationship;
|
||||||
|
user = defaultAjaxSettings();
|
||||||
|
computer = defaultAjaxSettings();
|
||||||
|
group = defaultAjaxSettings();
|
||||||
|
relationship = defaultAjaxSettings();
|
||||||
|
|
||||||
|
user.data = JSON.stringify({
|
||||||
|
'statements': [{
|
||||||
|
'statement': "MATCH (n:User) WHERE NOT n.name ENDS WITH '$' RETURN count(n)"
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
user.success = function(json){
|
||||||
|
this.setState({num_users:json.results[0].data[0].row[0] })
|
||||||
|
}.bind(this)
|
||||||
|
|
||||||
|
group.data = JSON.stringify({
|
||||||
|
'statements': [{
|
||||||
|
'statement': "MATCH (n:Group) RETURN count(n)"
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
group.success = function(json){
|
||||||
|
this.setState({num_groups:json.results[0].data[0].row[0] })
|
||||||
|
}.bind(this)
|
||||||
|
|
||||||
|
computer.data = JSON.stringify({
|
||||||
|
'statements': [{
|
||||||
|
'statement': "MATCH (n:Computer) RETURN count(n)"
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
computer.success = function(json){
|
||||||
|
this.setState({num_computers:json.results[0].data[0].row[0] })
|
||||||
|
}.bind(this)
|
||||||
|
|
||||||
|
relationship.data = JSON.stringify({
|
||||||
|
'statements': [{
|
||||||
|
'statement': "MATCH ()-[r]->() RETURN count(r)"
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
relationship.success = function(json){
|
||||||
|
this.setState({num_relationships:json.results[0].data[0].row[0] })
|
||||||
|
}.bind(this)
|
||||||
|
|
||||||
|
$.ajax(user);
|
||||||
|
$.ajax(computer);
|
||||||
|
$.ajax(group);
|
||||||
|
$.ajax(relationship);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
export default class NoNodeData extends Component {
|
||||||
|
propTypes: {
|
||||||
|
visible : React.PropTypes.bool.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className={this.props.visible ? "" : "hidden"}>
|
||||||
|
<h3>
|
||||||
|
Node Properties
|
||||||
|
</h3>
|
||||||
|
<p>
|
||||||
|
Select a node for more information
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { If, Then, Else } from 'react-if';
|
||||||
|
|
||||||
|
export default class NodeALink extends Component {
|
||||||
|
propTypes: {
|
||||||
|
ready : React.PropTypes.bool.isRequired,
|
||||||
|
click : React.PropTypes.func,
|
||||||
|
value : React.PropTypes.number
|
||||||
|
}
|
||||||
|
constructor(props){
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<If condition={this.props.ready}>
|
||||||
|
<Then><a href="#" onClick={this.props.click}>{this.props.value}</a></Then>
|
||||||
|
<Else>{() =>
|
||||||
|
<a style={{color:'black'}}>-</a>
|
||||||
|
}</Else>
|
||||||
|
</If>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
export default class PrebuiltQueriesDisplay extends Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3>
|
||||||
|
Pre-Built Analytics Queries
|
||||||
|
</h3>
|
||||||
|
<div className="query-box">
|
||||||
|
<a href="#" >Find Shortest Paths to DA</a><br />
|
||||||
|
<a href="#" >Find User with Most Sessions</a><br />
|
||||||
|
<a href="#" >Map Domain Trusts</a><br />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,179 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import GlyphiconSpan from '../glyphiconspan'
|
||||||
|
import Icon from '../icon'
|
||||||
|
import { escapeRegExp } from '../../js/utils.js';
|
||||||
|
import TabContainer from './tabcontainer'
|
||||||
|
|
||||||
|
require('bootstrap-3-typeahead')
|
||||||
|
|
||||||
|
export default class SearchContainer extends Component {
|
||||||
|
constructor(props){
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
data : [
|
||||||
|
{id: 1, name: 'John'},
|
||||||
|
{id: 2, name: 'Miles'},
|
||||||
|
{id: 3, name: 'Charles'},
|
||||||
|
{id: 4, name: 'Herbie'},
|
||||||
|
],
|
||||||
|
mainvalue : "",
|
||||||
|
pathvalue : "",
|
||||||
|
mainPlaceholder:"Start typing to search for a node...",
|
||||||
|
pathfindingIsOpen: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_getSearchOptions(e, callback){
|
||||||
|
var x = []
|
||||||
|
var promise = $.ajax({
|
||||||
|
url: localStorage.getItem("dbpath") + "/db/data/transaction/commit",
|
||||||
|
type: 'POST',
|
||||||
|
accepts: { json: "application/json" },
|
||||||
|
dataType: "json",
|
||||||
|
contentType: "application/json",
|
||||||
|
headers: {
|
||||||
|
"Authorization": localStorage.getItem("auth")
|
||||||
|
},
|
||||||
|
data: JSON.stringify({
|
||||||
|
"statements": [{
|
||||||
|
"statement" : "MATCH (n) WHERE n.name =~ '(?i).*" + escapeRegExp(e) + ".*' RETURN n.name LIMIT 10"
|
||||||
|
}]
|
||||||
|
}),
|
||||||
|
success: function(json) {
|
||||||
|
$.each(json.results[0].data, function(index, d){
|
||||||
|
x.push({id:index, label:d.row[0]})
|
||||||
|
})
|
||||||
|
callback(null, {
|
||||||
|
options: x,
|
||||||
|
complete: false
|
||||||
|
})
|
||||||
|
}.bind(this)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
_onPathfindClick(){
|
||||||
|
jQuery(this.refs.pathfinding).slideToggle()
|
||||||
|
var p = !this.state.pathfindingIsOpen
|
||||||
|
var t = this.state.pathfindingIsOpen ? "Start typing to search for a node..." : "Start Node"
|
||||||
|
this.setState({
|
||||||
|
pathfindingIsOpen: p,
|
||||||
|
mainPlaceholder: t
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
_onPlayClick(){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_onExpandClick(){
|
||||||
|
jQuery(this.refs.tabs).slideToggle()
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
return (
|
||||||
|
<div className="searchdiv">
|
||||||
|
<div className="input-group input-group-unstyled">
|
||||||
|
<GlyphiconSpan
|
||||||
|
tooltip={true}
|
||||||
|
tooltipDir="bottom"
|
||||||
|
tooltipTitle="More Info"
|
||||||
|
classes="input-group-addon spanfix"
|
||||||
|
click={this._onExpandClick.bind(this)}>
|
||||||
|
<Icon glyph="menu-hamburger" extraClass="menuglyph" />
|
||||||
|
</GlyphiconSpan>
|
||||||
|
<input ref="searchbar" type="search" className="form-control searchbox" autoComplete="off" placeholder={this.state.mainPlaceholder} />
|
||||||
|
<GlyphiconSpan tooltip={true} tooltipDir="bottom"
|
||||||
|
tooltipTitle="Pathfinding"
|
||||||
|
classes="input-group-addon spanfix"
|
||||||
|
click={this._onPathfindClick.bind(this)}>
|
||||||
|
<Icon glyph="road" extraClass="menuglyph" />
|
||||||
|
</GlyphiconSpan>
|
||||||
|
<GlyphiconSpan
|
||||||
|
tooltip={true}
|
||||||
|
tooltipDir="bottom"
|
||||||
|
tooltipTitle="Back"
|
||||||
|
classes="input-group-addon spanfix">
|
||||||
|
<Icon glyph="step-backward" extraClass="menuglyph" />
|
||||||
|
</GlyphiconSpan>
|
||||||
|
</div>
|
||||||
|
<div ref="pathfinding">
|
||||||
|
<div className="input-group input-group-unstyled">
|
||||||
|
<GlyphiconSpan
|
||||||
|
tooltip={false}
|
||||||
|
classes="input-group-addon spanfix invisible">
|
||||||
|
<Icon glyph="menu-hamburger" extraClass="menuglyph" />
|
||||||
|
</GlyphiconSpan>
|
||||||
|
<input ref="pathbar" type="search" className="form-control searchbox" autoComplete="off" placeholder="Target Node" />
|
||||||
|
<GlyphiconSpan tooltip={false}
|
||||||
|
classes="input-group-addon spanfix invisible">
|
||||||
|
<Icon glyph="road" extraClass="menuglyph" />
|
||||||
|
</GlyphiconSpan>
|
||||||
|
<GlyphiconSpan
|
||||||
|
tooltip={true}
|
||||||
|
tooltipDir="bottom"
|
||||||
|
tooltipTitle="Find Path"
|
||||||
|
classes="input-group-addon spanfix"
|
||||||
|
click={this._onPlayClick.bind(this)}>
|
||||||
|
<Icon glyph="play" extraClass="menuglyph" />
|
||||||
|
</GlyphiconSpan>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ref="tabs">
|
||||||
|
<TabContainer />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
openNodeTab(){
|
||||||
|
var e = jQuery(this.refs.tabs)
|
||||||
|
if (!(e.is(":visible"))){
|
||||||
|
e.slideToggle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
jQuery(this.refs.pathfinding).slideToggle(0);
|
||||||
|
jQuery(this.refs.tabs).slideToggle(0);
|
||||||
|
emitter.on('userNodeClicked', this.openNodeTab.bind(this))
|
||||||
|
|
||||||
|
jQuery(this.refs.searchbar).typeahead({
|
||||||
|
source: function(query, process) {
|
||||||
|
return $.ajax({
|
||||||
|
url: localStorage.getItem("dbpath") + "/db/data/cypher",
|
||||||
|
type: 'POST',
|
||||||
|
accepts: { json: "application/json" },
|
||||||
|
dataType: "json",
|
||||||
|
contentType: "application/json",
|
||||||
|
headers: {
|
||||||
|
"Authorization": "Basic bmVvNGo6bmVvNGpq"
|
||||||
|
},
|
||||||
|
data: JSON.stringify({ "query": "MATCH (n) WHERE n.name =~ '(?i).*" + escapeRegExp(query) + ".*' RETURN n.name LIMIT 10" }),
|
||||||
|
success: function(json) {
|
||||||
|
var d = json.data;
|
||||||
|
var l = d.length;
|
||||||
|
for (var i = 0; i < l; i++) {
|
||||||
|
d[i] = d[i].toString();
|
||||||
|
}
|
||||||
|
return process(json.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
afterSelect: function(selected) {
|
||||||
|
if (!this.state.pathfindingIsOpen) {
|
||||||
|
doQuery("MATCH (n) WHERE n.name =~ '(?i).*" + escapeRegExp(selected) + ".*' RETURN n");
|
||||||
|
} else {
|
||||||
|
var start = $('#searchBar').val();
|
||||||
|
var end = $('#endNode').val();
|
||||||
|
if (start !== "" && end !== "") {
|
||||||
|
doQuery("MATCH (source {name:'" + start + "'}), (target {name:'" + end + "'}), p=allShortestPaths((source)-[*]->(target)) RETURN p", start, end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.bind(this),
|
||||||
|
autoSelect: false,
|
||||||
|
minLength: 3
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import DatabaseDataDisplay from './databasedatadisplay'
|
||||||
|
import PrebuiltQueriesDisplay from './prebuiltqueriesdisplay'
|
||||||
|
import NoNodeData from './nonodedata'
|
||||||
|
import UserNodeData from './usernodedata'
|
||||||
|
import { Tabs, Tab } from 'react-bootstrap';
|
||||||
|
|
||||||
|
export default class TabContainer extends Component {
|
||||||
|
constructor(props){
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
userVisible: false,
|
||||||
|
computerVisible: false,
|
||||||
|
groupVisible: false,
|
||||||
|
selected: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_userNodeClicked(){
|
||||||
|
this.setState({
|
||||||
|
userVisible: true,
|
||||||
|
computerVisible: false,
|
||||||
|
groupVisible: false
|
||||||
|
})
|
||||||
|
this.setState({selected: 2})
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
emitter.on('userNodeClicked', this._userNodeClicked.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleSelect(index, last){
|
||||||
|
this.setState({selected: index})
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Tabs id="tab-style" bsStyle="pills" activeKey={this.state.selected} onSelect={this._handleSelect.bind(this)}>
|
||||||
|
<Tab eventKey={1} title="Database Info">
|
||||||
|
<DatabaseDataDisplay />
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
<Tab eventKey={2} title="Node Info">
|
||||||
|
<NoNodeData visible={!this.state.userVisible && !this.state.computerVisible && !this.state.groupVisible}/>
|
||||||
|
<UserNodeData visible={this.state.userVisible}/>
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
<Tab eventKey={3} title="Queries">
|
||||||
|
<PrebuiltQueriesDisplay />
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,196 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import NodeALink from './nodealink'
|
||||||
|
import { fullAjax } from '../../js/utils.js'
|
||||||
|
|
||||||
|
export default class UserNodeData extends Component {
|
||||||
|
propTypes: {
|
||||||
|
visible : React.PropTypes.bool.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(){
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
label: "",
|
||||||
|
samAccountName: "None",
|
||||||
|
displayName: "None",
|
||||||
|
pwdLastChanged: "None",
|
||||||
|
firstDegreeGroupMembership: -1,
|
||||||
|
unrolledGroupMembership: -1,
|
||||||
|
foreignGroupMembership: -1,
|
||||||
|
firstDegreeLocalAdmin: -1,
|
||||||
|
groupDelegatedLocalAdmin: -1,
|
||||||
|
derivateLocalAdmin: -1,
|
||||||
|
sessions: -1
|
||||||
|
}
|
||||||
|
|
||||||
|
emitter.on('userNodeClicked', this.getNodeData.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
placeholder(){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
getNodeData(payload){
|
||||||
|
this.setState({
|
||||||
|
label: payload,
|
||||||
|
samAccountName: "None",
|
||||||
|
displayName: "None",
|
||||||
|
pwdLastChanged: "None",
|
||||||
|
firstDegreeGroupMembership: -1,
|
||||||
|
unrolledGroupMembership: -1,
|
||||||
|
foreignGroupMembership: -1,
|
||||||
|
firstDegreeLocalAdmin: -1,
|
||||||
|
groupDelegatedLocalAdmin: -1,
|
||||||
|
derivativeLocalAdmin: -1,
|
||||||
|
sessions: -1
|
||||||
|
})
|
||||||
|
var firstDegreeGroupMembership,
|
||||||
|
unrolledGroupMembership,
|
||||||
|
unrolledGroupMembership,
|
||||||
|
foreignGroupMembership,
|
||||||
|
firstDegreeLocalAdmin,
|
||||||
|
groupDelegatedLocalAdmin,
|
||||||
|
derivativeLocalAdmin,
|
||||||
|
sessions;
|
||||||
|
|
||||||
|
firstDegreeGroupMembership = fullAjax(
|
||||||
|
"MATCH (n:User {name:'{}'}), (m:Group), p=allShortestPaths((n)-[:MemberOf*1]->(m)) RETURN count(m)".format(payload),
|
||||||
|
function(json){
|
||||||
|
this.setState({firstDegreeGroupMembership: json.results[0].data[0].row[0]})
|
||||||
|
}.bind(this))
|
||||||
|
|
||||||
|
unrolledGroupMembership = fullAjax(
|
||||||
|
"MATCH (n:User {name:'{}'}), (target:Group), p=allShortestPaths((n)-[:MemberOf*1..]->(target)) RETURN count(target)".format(payload),
|
||||||
|
function(json){
|
||||||
|
this.setState({unrolledGroupMembership: json.results[0].data[0].row[0]})
|
||||||
|
}.bind(this))
|
||||||
|
|
||||||
|
firstDegreeLocalAdmin = fullAjax(
|
||||||
|
"MATCH (n:User {name:'{}}'}), (target:Computer), p=allShortestPaths((n)-[:AdminTo*1]->(target)) RETURN count(target)".format(payload),
|
||||||
|
function(json){
|
||||||
|
this.setState({firstDegreeLocalAdmin: json.results[0].data[0].row[0]})
|
||||||
|
}.bind(this)
|
||||||
|
)
|
||||||
|
groupDelegatedLocalAdmin = fullAjax(
|
||||||
|
"MATCH x=allShortestPaths((n:User {name:'{}'})-[r:MemberOf*1..]->(m:Group)) WITH n,m,r MATCH (m)-[s:AdminTo*1..]->(p:Computer) RETURN count(distinct(p))".format(payload),
|
||||||
|
function(json){
|
||||||
|
this.setState({groupDelegatedLocalAdmin: json.results[0].data[0].row[0]})
|
||||||
|
}.bind(this)
|
||||||
|
)
|
||||||
|
derivativeLocalAdmin = fullAjax(
|
||||||
|
"MATCH (n:User {name:'{}'}), (target:Computer), p=allShortestPaths((n)-[*]->(target)) RETURN count(distinct(target))".format(payload),
|
||||||
|
function(json){
|
||||||
|
this.setState({derivativeLocalAdmin: json.results[0].data[0].row[0]})
|
||||||
|
}.bind(this)
|
||||||
|
)
|
||||||
|
sessions = fullAjax(
|
||||||
|
"MATCH (n:Computer)-[r:HasSession]->(m:User {name:'{}'}) RETURN count(n)".format(payload),
|
||||||
|
function(json){
|
||||||
|
this.setState({sessions: json.results[0].data[0].row[0]})
|
||||||
|
}.bind(this)
|
||||||
|
)
|
||||||
|
|
||||||
|
$.ajax(firstDegreeGroupMembership);
|
||||||
|
$.ajax(unrolledGroupMembership);
|
||||||
|
$.ajax(firstDegreeLocalAdmin);
|
||||||
|
$.ajax(groupDelegatedLocalAdmin);
|
||||||
|
$.ajax(derivativeLocalAdmin);
|
||||||
|
$.ajax(sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className={this.props.visible ? "" : "displaynone"}>
|
||||||
|
<h3>
|
||||||
|
{this.state.label}
|
||||||
|
</h3>
|
||||||
|
<dl className='dl-horizontal'>
|
||||||
|
<dt>
|
||||||
|
SAMAccountName
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
{this.state.samAccountName}
|
||||||
|
</dd>
|
||||||
|
<dt>
|
||||||
|
Display Name
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
{this.state.displayName}
|
||||||
|
</dd>
|
||||||
|
<dt>
|
||||||
|
Password Last Changed
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
{this.state.pwdLastChanged}
|
||||||
|
</dd>
|
||||||
|
<br />
|
||||||
|
<dt>
|
||||||
|
First Degree Group Memberships
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
<NodeALink
|
||||||
|
ready={this.state.firstDegreeGroupMembership !== -1}
|
||||||
|
value={this.state.firstDegreeGroupMembership}
|
||||||
|
click={this.placeholder} />
|
||||||
|
</dd>
|
||||||
|
<dt>
|
||||||
|
Unrolled Group Memberships
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
<NodeALink
|
||||||
|
ready={this.state.unrolledGroupMembership !== -1}
|
||||||
|
value={this.state.unrolledGroupMembership}
|
||||||
|
click={this.placeholder} />
|
||||||
|
</dd>
|
||||||
|
<dt>
|
||||||
|
Foreign Group Membership
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
<NodeALink
|
||||||
|
ready={this.state.foreignGroupMembership !== -1}
|
||||||
|
value={this.state.foreignGroupMembership}
|
||||||
|
click={this.placeholder} />
|
||||||
|
</dd>
|
||||||
|
<br />
|
||||||
|
<dt>
|
||||||
|
First Degree Local Admin
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
<NodeALink
|
||||||
|
ready={this.state.firstDegreeLocalAdmin !== -1}
|
||||||
|
value={this.state.firstDegreeLocalAdmin}
|
||||||
|
click={this.placeholder} />
|
||||||
|
</dd>
|
||||||
|
<dt>
|
||||||
|
Group Delegated Local Admin Rights
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
<NodeALink
|
||||||
|
ready={this.state.groupDelegatedLocalAdmin !== -1}
|
||||||
|
value={this.state.groupDelegatedLocalAdmin}
|
||||||
|
click={this.placeholder} />
|
||||||
|
</dd>
|
||||||
|
<dt>
|
||||||
|
Derivate Local Admin Rights
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
<NodeALink
|
||||||
|
ready={this.state.derivativeLocalAdmin !== -1}
|
||||||
|
value={this.state.derivativeLocalAdmin}
|
||||||
|
click={this.placeholder} />
|
||||||
|
</dd>
|
||||||
|
<dt>
|
||||||
|
Sessions
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
<NodeALink
|
||||||
|
ready={this.state.sessions !== -1}
|
||||||
|
value={this.state.sessions}
|
||||||
|
click={this.placeholder} />
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import GlyphiconSpan from '../glyphiconspan';
|
||||||
|
import Icon from '../icon';
|
||||||
|
|
||||||
|
export default class SpotlightContainer extends Component {
|
||||||
|
constructor(props){
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
data: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div ref="spotlight" className="spotlight">
|
||||||
|
<div className="input-group input-group-unstyled no-border-radius">
|
||||||
|
<GlyphiconSpan tooltip={false} classes="input-group-addon spanfix">
|
||||||
|
<Icon glyph="search" />
|
||||||
|
</GlyphiconSpan>
|
||||||
|
<input id="spotlight-search" type="search" className="form-control searchbox" autoComplete="off" placeholder="Explore Nodes" data-type="search" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="spotlight-nodelist">
|
||||||
|
<table data-role="table" className="table table-striped" data-filter="true" data-input="#spotlight-search">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>Node Label</td>
|
||||||
|
<td>Collapsed Into</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody ref="spotlight-tbody" className="searchable">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
jQuery(this.refs.spotlight).fadeToggle(0)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
export default class SpotlightRow extends Component {
|
||||||
|
propTypes: {
|
||||||
|
node-id : React.PropTypes.number.isRequired,
|
||||||
|
parent-node-id : React.PropTypes.number.isRequired,
|
||||||
|
node-label : React.PropTypes.string.isRequired,
|
||||||
|
parent-node-label : React.PropTypes.string.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<tr data-id={this.props.node-id} data-parent-id={this.props.parent-node-id}>
|
||||||
|
<td>{this.props.node-label}</td>
|
||||||
|
<td>{this.props.parent-node-label}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,16 @@
|
||||||
/* roboto-regular - latin */
|
/* roboto-regular - latin */
|
||||||
|
|
||||||
|
#root {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.max{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Roboto';
|
font-family: 'Roboto';
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
@ -70,6 +81,10 @@ body {
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.query-box{
|
||||||
|
padding: 0 .5em .5em .5em
|
||||||
|
}
|
||||||
|
|
||||||
.graph {
|
.graph {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 97%;
|
height: 97%;
|
||||||
|
@ -80,6 +95,39 @@ body {
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.typeaheadfix{
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typeaheadfix .Select-menu-outer{
|
||||||
|
z-index: 99999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typeaheadfix .Select-control{
|
||||||
|
border: 0;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typeaheadfix .Select-control:focus{
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typeaheadfix .Select-arrow-zone{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media all and (min-width: 900px) {
|
||||||
|
.searchdiv {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10;
|
||||||
|
top: 1em;
|
||||||
|
left: 1em;
|
||||||
|
overflow: visible;
|
||||||
|
width: 45%;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media all and (min-width: 1000px) {
|
@media all and (min-width: 1000px) {
|
||||||
.searchdiv {
|
.searchdiv {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -173,10 +221,6 @@ body {
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#abc li.active > a {
|
|
||||||
border-right: 1px solid lightgray;
|
|
||||||
border-left: 1px solid lightgray;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hideme {
|
.hideme {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -184,14 +228,6 @@ body {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
#abc li > a {
|
|
||||||
border-top: 0;
|
|
||||||
border-right: 0;
|
|
||||||
border-bottom: 1px solid lightgray;
|
|
||||||
border-left: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
padding: 0 10px 0 10px;
|
padding: 0 10px 0 10px;
|
||||||
}
|
}
|
||||||
|
@ -346,9 +382,7 @@ body {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dbbuttons {
|
|
||||||
padding-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal {
|
.modal {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -407,7 +441,7 @@ body {
|
||||||
border-bottom-left-radius: 2em;
|
border-bottom-left-radius: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media all and (min-width: 1000px) {
|
@media all and (min-width: 900px) {
|
||||||
.spotlight {
|
.spotlight {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 30;
|
z-index: 30;
|
||||||
|
@ -429,30 +463,24 @@ body {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.nodeList {
|
.spotlight-nodelist {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
height: 11em;
|
height: 11em;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nodelist table {
|
.spotlight-nodelist table > thead {
|
||||||
height: 100%;
|
text-align: center;
|
||||||
border: 1px solid black;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.unborder span {
|
.no-border-radius span {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.unborder input {
|
.no-border-radius input {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-tabs li {
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loadingIndicator {
|
.loadingIndicator {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 25;
|
z-index: 25;
|
||||||
|
@ -493,3 +521,65 @@ body {
|
||||||
.sliderfix .slideinline > .dragger {
|
.sliderfix .slideinline > .dragger {
|
||||||
margin-top: -5px !important;
|
margin-top: -5px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.center-align{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tab-style{
|
||||||
|
border-top: .5px solid lightgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tab-style > ul{
|
||||||
|
display: table;
|
||||||
|
width: 100%;
|
||||||
|
border-bottom: 1px solid lightgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tab-style > ul > li {
|
||||||
|
border-top: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
border-radius: 0;
|
||||||
|
display: table-cell;
|
||||||
|
width: 33%;
|
||||||
|
text-align: center;
|
||||||
|
color: #337AB7;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tab-style > ul > li > a:focus {
|
||||||
|
background-color: #fff;
|
||||||
|
color: black;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tab-style > ul > li > a:hover {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tab-style > ul > li.active > a {
|
||||||
|
background-color: #fff;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tab-style > ul > li > a[aria-selected='true']{
|
||||||
|
border-right: 1px solid lightgray;
|
||||||
|
border-left: 1px solid lightgray;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tab-style > div{
|
||||||
|
padding: 0 10px 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tab-style h3{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dbbuttons {
|
||||||
|
padding-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.displaynone {
|
||||||
|
display: none;
|
||||||
|
}
|
Before Width: | Height: | Size: 357 KiB After Width: | Height: | Size: 357 KiB |
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
@ -0,0 +1,151 @@
|
||||||
|
import 'babel-polyfill'; // generators
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
|
import AppContainer from './components/appcontainer';
|
||||||
|
import { getStorageData, storageHasKey, storageSetKey } from './js/utils.js';
|
||||||
|
|
||||||
|
const ConfigStore = require('configstore');
|
||||||
|
|
||||||
|
global.conf = new ConfigStore('bloodhound')
|
||||||
|
var e = require('eventemitter2').EventEmitter2
|
||||||
|
global.emitter = new e({})
|
||||||
|
|
||||||
|
|
||||||
|
String.prototype.format = function () {
|
||||||
|
var i = 0, args = arguments;
|
||||||
|
return this.replace(/{}/g, function () {
|
||||||
|
return typeof args[i] != 'undefined' ? args[i++] : '';
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Array.prototype.allEdgesSameType = function() {
|
||||||
|
|
||||||
|
for (var i = 1; i < this.length; i++) {
|
||||||
|
if (this[i].neo4j_type !== this[0].neo4j_type)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
global.PathfindingEvent = {
|
||||||
|
data: {
|
||||||
|
start: "",
|
||||||
|
end: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
global.SearchEvent = {
|
||||||
|
data: {
|
||||||
|
label: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
global.LogoutEvent = {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
global.UserNodeClickEvent = {
|
||||||
|
data: {
|
||||||
|
label: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
global.appStore = {
|
||||||
|
startNode: null,
|
||||||
|
endNode: null,
|
||||||
|
spotlightData: {},
|
||||||
|
highResPalette: {
|
||||||
|
iconScheme: {
|
||||||
|
'User': {
|
||||||
|
font: 'FontAwesome',
|
||||||
|
content: '\uF007',
|
||||||
|
scale: 1.5,
|
||||||
|
color: '#17E625'
|
||||||
|
},
|
||||||
|
'Computer': {
|
||||||
|
font: 'FontAwesome',
|
||||||
|
content: '\uF108',
|
||||||
|
scale: 1.2,
|
||||||
|
color: '#E67873'
|
||||||
|
},
|
||||||
|
'Group': {
|
||||||
|
font: 'FontAwesome',
|
||||||
|
content: '\uF0C0',
|
||||||
|
scale: 1.5,
|
||||||
|
color: '#DBE617'
|
||||||
|
},
|
||||||
|
'Domain': {
|
||||||
|
font: 'FontAwesome',
|
||||||
|
content: '\uF0AC',
|
||||||
|
scale: 1.5,
|
||||||
|
color: '#17E6B9'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
lowResPalette: {
|
||||||
|
colorScheme: {
|
||||||
|
'User' : '#17E625',
|
||||||
|
'Computer' : '#E67873',
|
||||||
|
'Group' : '#DBE617',
|
||||||
|
'Domain' : '#17E6B9'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
highResStyle: {
|
||||||
|
nodes: {
|
||||||
|
label: {
|
||||||
|
by: 'neo4j_data.name'
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
by: 'degree',
|
||||||
|
bins: 10,
|
||||||
|
min: 10,
|
||||||
|
max: 20
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
by: 'neo4j_labels.0',
|
||||||
|
scheme: 'iconScheme'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
lowResStyle: {
|
||||||
|
nodes: {
|
||||||
|
label: {
|
||||||
|
by: 'neo4j_data.name'
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
by: 'degree',
|
||||||
|
bins: 10,
|
||||||
|
min: 10,
|
||||||
|
max: 20
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
by: 'neo4j_labels.0',
|
||||||
|
scheme: 'colorScheme'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof conf.get('performance') === 'undefined'){
|
||||||
|
conf.set('performance', {
|
||||||
|
edge: 5,
|
||||||
|
sibling: 5,
|
||||||
|
lowGraphics: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof conf.get('databaseInfo') === 'undefined'){
|
||||||
|
conf.set('databaseInfo', {
|
||||||
|
url: 'http://localhost:7474',
|
||||||
|
user: 'neo4j',
|
||||||
|
password: 'neo4jj'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
appStore.performance = conf.get('performance')
|
||||||
|
appStore.databaseInfo = conf.get('databaseInfo');
|
||||||
|
|
||||||
|
ReactDOM.render(<AppContainer />, document.getElementById('root'))
|
|
@ -0,0 +1,249 @@
|
||||||
|
export function escapeRegExp(s) {
|
||||||
|
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\\\$&');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildAuthHeader(){
|
||||||
|
db = storage.get('database-info', function(error, data){
|
||||||
|
return "Basic " + btoa(db.user + ":" + db.password)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//Graph Utils
|
||||||
|
export function setNodeData(sigmaInstance, startLabel, endLabel){
|
||||||
|
var startNode = null;
|
||||||
|
var endNode = null;
|
||||||
|
|
||||||
|
startLabel = (typeof startLabel === 'undefined') ? "" : startLabel
|
||||||
|
endLabel = (typeof endLabel === 'undefined') ? "" : endLabel
|
||||||
|
|
||||||
|
$.each(sigmaInstance.graph.nodes(), function(index, node){
|
||||||
|
node.degree = sigmaInstance.graph.degree(node.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
$.each(sigmaInstance.graph.nodes(), function(index, node){
|
||||||
|
var s = node.neo4j_labels[0]
|
||||||
|
switch (s) {
|
||||||
|
case "Group":
|
||||||
|
node.type_group = true;
|
||||||
|
break;
|
||||||
|
case "User":
|
||||||
|
node.type_user = true;
|
||||||
|
break;
|
||||||
|
case "Computer":
|
||||||
|
node.type_computer = true;
|
||||||
|
break;
|
||||||
|
case "Domain":
|
||||||
|
node.type_domain = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log('Something has gone terribly wrong');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.neo4j_data.name === startLabel){
|
||||||
|
appStore.startNode = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.neo4j_data.name === endLabel){
|
||||||
|
appStore.endNode = node;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return sigmaInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
export function collapseEdgeNodes(sigmaInstance){
|
||||||
|
var threshold = appStore.performance.edge;
|
||||||
|
|
||||||
|
if (threshold == 0){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$.each(sigmaInstance.graph.nodes(), function(index, node){
|
||||||
|
if (node.degree < threshold){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.each(sigmaInstance.graph.adjacentNodes(node.id), function(index, anode){
|
||||||
|
if (anode.neo4j_data.name === appStore.endNode || anode.neo4j_data.name === appStore.startNode){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var edges = sigmaInstance.graph.adjacentEdges(anode.id);
|
||||||
|
if ((edges.length > 1 || edges.length === 0) || (typeof anode.folded !== 'undefined')){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var edge = edges[0];
|
||||||
|
|
||||||
|
if ((anode.type_user && (edge.label === 'MemberOf' || edge.label === 'AdminTo'))
|
||||||
|
|| (anode.type_computer && (edge.label === 'AdminTo' || edge.label === 'MemberOf'))
|
||||||
|
|| (anode.type_group && edge.label === 'AdminTo')){
|
||||||
|
if (typeof node.folded === 'undefined'){
|
||||||
|
node.folded = {}
|
||||||
|
node.folded.nodes = []
|
||||||
|
node.folded.edges = []
|
||||||
|
}
|
||||||
|
|
||||||
|
node.isGrouped = true
|
||||||
|
node.folded.nodes.push(anode)
|
||||||
|
node.folded.edges.push(edge)
|
||||||
|
appStore.spotlightData[anode.id] = [anode.neo4j_data.name, node.id, node.neo4j_data.name];
|
||||||
|
sigmaInstance.graph.dropNode(anode.id);
|
||||||
|
node.glyphs = [{
|
||||||
|
'position': 'bottom-left',
|
||||||
|
'content': node.folded.nodes.length
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
return sigmaInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
export function collapseSiblingNodes(sigmaInstance){
|
||||||
|
var threshold = appStore.performance.siblings
|
||||||
|
|
||||||
|
if (threshold === 0){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.each(sigmaInstance.graph.nodes(), function(index, node){
|
||||||
|
//Dont apply this logic to anything thats folded or isn't a computer
|
||||||
|
if (!node.type_computer || typeof node.folded !== 'undefined'){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Start by getting all the edges attached to this node
|
||||||
|
var adjacent = sigmaInstance.graph.adjacentEdges(node.id)
|
||||||
|
var siblings = []
|
||||||
|
|
||||||
|
//Check to see if all the edges are the same type (i.e. AdminTo)
|
||||||
|
if (adjacent.length > 1 && adjacent.allEdgesSameType()){
|
||||||
|
//Get the "parents" by mapping the source from every edge
|
||||||
|
var parents = adjacent.map(
|
||||||
|
function(e){
|
||||||
|
return e.source
|
||||||
|
}
|
||||||
|
)
|
||||||
|
//Generate our string to compare other nodes to
|
||||||
|
//by sorting the parents and turning it into a string
|
||||||
|
var checkString = parents.sort().join(',')
|
||||||
|
var testString;
|
||||||
|
|
||||||
|
//Loop back over nodes in the graph and look for any nodes
|
||||||
|
//with identical parents
|
||||||
|
$.each(sigmaInstance.graph.nodes(), function(index, node2){
|
||||||
|
testString = sigmaInstance.graph.adjacentEdges(node2.id).map(
|
||||||
|
function(e){
|
||||||
|
return e.source;
|
||||||
|
}
|
||||||
|
).sort().join(',')
|
||||||
|
|
||||||
|
if (testString === checkString){
|
||||||
|
siblings.push(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (siblings.length >= threshold){
|
||||||
|
//Generate a new ID for our grouped node
|
||||||
|
var nodeId = generateUniqueId(sigmaInstance, true);
|
||||||
|
|
||||||
|
var folded = {
|
||||||
|
nodes: [],
|
||||||
|
edges: []
|
||||||
|
}
|
||||||
|
|
||||||
|
sigmaInstance.graph.addNode({
|
||||||
|
id: nodeId,
|
||||||
|
x: node.x,
|
||||||
|
y: node.y,
|
||||||
|
degree: siblings.length,
|
||||||
|
label: "Grouped Computers",
|
||||||
|
neo4j_labels: ['Computer'],
|
||||||
|
neo4j_data: { name: "Grouped Computers" },
|
||||||
|
groupedNode: true,
|
||||||
|
glyphs: [{
|
||||||
|
position: 'bottom-left',
|
||||||
|
content: siblings.length
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
//Generate new edges for each parent going to our new node
|
||||||
|
$.each(parents, function(index, parent){
|
||||||
|
var id = generateUniqueId(sigmaInstance, false);
|
||||||
|
|
||||||
|
sigmaInstance.graph.addEdge({
|
||||||
|
id: id,
|
||||||
|
source: parent,
|
||||||
|
target: nodeId,
|
||||||
|
label: 'AdminTo',
|
||||||
|
neo4j_type: 'AdminTo',
|
||||||
|
size: 1
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
var n = sigmaInstance.graph.nodes(nodeId);
|
||||||
|
//Loop over all the siblings, and push the edges into our new parent node
|
||||||
|
//Push the nodes in as well so we can unfold them
|
||||||
|
$.each(siblings, function(index, sibling){
|
||||||
|
$.each(sigmaInstance.graph.adjacentEdges(sibling.id), function(index, edge){
|
||||||
|
n.folded.edges.push(edge)
|
||||||
|
})
|
||||||
|
|
||||||
|
n.folded.nodes.push(sibling)
|
||||||
|
appStore.spotlightData[sibling.id] = [sibling.neo4j_data.name, nodeId, n.label];
|
||||||
|
sigmaInstance.graph.dropNode(sibling.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return sigmaInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateUniqueId(sigmaInstance, isNode){
|
||||||
|
var i = Math.floor(Math.random() * (100000 - 10 + 1)) + 10;
|
||||||
|
if (isNode){
|
||||||
|
while (sigmaInstance.graph.nodes(i) !== 'undefined'){
|
||||||
|
i = Math.floor(Math.random() * (100000 - 10 + 1)) + 10;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
while (sigmaInstance.graph.edges(i) !== 'undefined'){
|
||||||
|
i = Math.floor(Math.random() * (100000 - 10 + 1)) + 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defaultAjaxSettings(){
|
||||||
|
return {
|
||||||
|
url: appStore.databaseInfo.url + '/db/data/transaction/commit',
|
||||||
|
type: 'POST',
|
||||||
|
accepts: { json: 'application/json' },
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: 'application/json',
|
||||||
|
headers: {
|
||||||
|
'Authorization': btoa(appStore.databaseInfo.user + ':' + appStore.databaseInfo.password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fullAjax(statements, callback){
|
||||||
|
var options = defaultAjaxSettings();
|
||||||
|
options.success = callback;
|
||||||
|
if ($.isArray(statements)){
|
||||||
|
var x = []
|
||||||
|
$.each(statements, function(index, statement){
|
||||||
|
x.push({"statement": statemnt})
|
||||||
|
})
|
||||||
|
options.data = JSON.stringify({
|
||||||
|
"statements": x
|
||||||
|
})
|
||||||
|
}else{
|
||||||
|
options.data = JSON.stringify({
|
||||||
|
"statements": [{
|
||||||
|
"statement" : statements
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return options
|
||||||
|
}
|
768
test.html
|
@ -1,768 +0,0 @@
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>GraphTest</title>
|
|
||||||
<script src="js/jquery-2.2.1.min.js"></script>
|
|
||||||
<script src="js/jquery-ui.min.js"></script>
|
|
||||||
<link rel="stylesheet" type="text/css" href="css/font-awesome.min.css">
|
|
||||||
<link type="text/css" rel="stylesheet" href="css/bootstrap.min.css">
|
|
||||||
<link type="text/css" rel="stylesheet" href="css/bootstrap-theme.min.css">
|
|
||||||
<link rel="stylesheet" type="text/css" href="css/styles.css">
|
|
||||||
<link rel="stylesheet" type="text/css" href="css/animation.css">
|
|
||||||
<link rel="stylesheet" type="text/css" href="css/tooltip.css">
|
|
||||||
<script type="text/javascript" src="js/bootstrap.min.js"></script>
|
|
||||||
<script type="text/javascript" src="js/sigma.min.js"></script>
|
|
||||||
<script type="text/javascript" src="js/plugins.min.js"></script>
|
|
||||||
<script type="text/javascript" src="js/bootstrap3-typeahead.min.js"></script>
|
|
||||||
<script type="text/javascript" src="js/mustache.min.js"></script>
|
|
||||||
<script type="text/javascript" src="js/bloodhound.js"></script>
|
|
||||||
<script type="text/javascript" src="js/sigma.renderers.linkurious.min.js"></script>
|
|
||||||
<script type="text/javascript" src="js/sigma.plugins.design.min.js"></script>
|
|
||||||
<script type="text/javascript" src="js/sigma.layouts.dagre.min.js"></script>
|
|
||||||
<script type="text/javascript" src="js/sigma.layouts.noverlap.js"></script>
|
|
||||||
<script type="text/javascript" src="js/sigma.renderers.glyphs.min.js"></script>
|
|
||||||
<script type="text/javascript" src="js/sigma.exporters.image.min.js"></script>
|
|
||||||
<script type="text/javascript" src="js/dagre.min.js"></script>
|
|
||||||
<style>
|
|
||||||
body{
|
|
||||||
padding-top:50px;
|
|
||||||
}
|
|
||||||
#graph{
|
|
||||||
height:100%;
|
|
||||||
}
|
|
||||||
.sigma-tooltip {
|
|
||||||
max-width: 300px;
|
|
||||||
max-height: 280px;
|
|
||||||
background-color: rgb(249, 247, 237);
|
|
||||||
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
.sigma-tooltip-header {
|
|
||||||
font-variant: small-caps;
|
|
||||||
font-size: 120%;
|
|
||||||
color: #437356;
|
|
||||||
border-bottom: 1px solid #aac789;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
.sigma-tooltip-body {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
.sigma-tooltip-body th {
|
|
||||||
color: #999;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
.sigma-tooltip-footer {
|
|
||||||
padding: 10px;
|
|
||||||
border-top: 1px solid #aac789;
|
|
||||||
}
|
|
||||||
.sigma-tooltip > .arrow {
|
|
||||||
border-width: 10px;
|
|
||||||
position: absolute;
|
|
||||||
display: block;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-color: transparent;
|
|
||||||
border-style: solid;
|
|
||||||
}
|
|
||||||
.sigma-tooltip.top {
|
|
||||||
margin-top: -12px;
|
|
||||||
}
|
|
||||||
.sigma-tooltip.top > .arrow {
|
|
||||||
left: 50%;
|
|
||||||
bottom: -10px;
|
|
||||||
margin-left: -10px;
|
|
||||||
border-top-color: rgb(249, 247, 237);
|
|
||||||
border-bottom-width: 0;
|
|
||||||
}
|
|
||||||
.sigma-tooltip.bottom {
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
.sigma-tooltip.bottom > .arrow {
|
|
||||||
left: 50%;
|
|
||||||
top: -10px;
|
|
||||||
margin-left: -10px;
|
|
||||||
border-bottom-color: rgb(249, 247, 237);
|
|
||||||
border-top-width: 0;
|
|
||||||
}
|
|
||||||
.sigma-tooltip.left {
|
|
||||||
margin-left: -12px;
|
|
||||||
}
|
|
||||||
.sigma-tooltip.left > .arrow {
|
|
||||||
top: 50%;
|
|
||||||
right: -10px;
|
|
||||||
margin-top: -10px;
|
|
||||||
border-left-color: rgb(249, 247, 237);
|
|
||||||
border-right-width: 0;
|
|
||||||
}
|
|
||||||
.sigma-tooltip.right {
|
|
||||||
margin-left: 12px;
|
|
||||||
}
|
|
||||||
.sigma-tooltip.right > .arrow {
|
|
||||||
top: 50%;
|
|
||||||
left: -10px;
|
|
||||||
margin-top: -10px;
|
|
||||||
border-right-color: rgb(249, 247, 237);
|
|
||||||
border-left-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.boxsize{
|
|
||||||
border: 3px solid black;
|
|
||||||
box-sizing: border-box;
|
|
||||||
background-color: white;
|
|
||||||
-moz-box-sizing: border-box;
|
|
||||||
padding-left: 10px;
|
|
||||||
margin-top:10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lister dl{
|
|
||||||
padding: .5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lister dt{
|
|
||||||
float: left;
|
|
||||||
clear: left;
|
|
||||||
width: 300px;
|
|
||||||
text-align: left;
|
|
||||||
font-weight:bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lister dt:after{
|
|
||||||
content: ":";
|
|
||||||
}
|
|
||||||
|
|
||||||
.lister dd{
|
|
||||||
margin: 0 0 0 110px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader {
|
|
||||||
margin: 100px auto;
|
|
||||||
font-size: 25px;
|
|
||||||
width: 1em;
|
|
||||||
height: 1em;
|
|
||||||
border-radius: 50%;
|
|
||||||
position: relative;
|
|
||||||
text-indent: -9999em;
|
|
||||||
-webkit-animation: load5 1.1s infinite ease;
|
|
||||||
animation: load5 1.1s infinite ease;
|
|
||||||
-webkit-transform: translateZ(0);
|
|
||||||
-ms-transform: translateZ(0);
|
|
||||||
transform: translateZ(0);
|
|
||||||
}
|
|
||||||
@-webkit-keyframes load5 {
|
|
||||||
0%,
|
|
||||||
100% {
|
|
||||||
box-shadow: 0em -2.6em 0em 0em #C0C0C0, 1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2), 2.5em 0em 0 0em rgba(255, 255, 255, 0.2), 1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2), 0em 2.5em 0 0em rgba(255, 255, 255, 0.2), -1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2), -2.6em 0em 0 0em rgba(255, 255, 255, 0.5), -1.8em -1.8em 0 0em rgba(255, 255, 255, 0.7);
|
|
||||||
}
|
|
||||||
12.5% {
|
|
||||||
box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.7), 1.8em -1.8em 0 0em #C0C0C0, 2.5em 0em 0 0em rgba(255, 255, 255, 0.2), 1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2), 0em 2.5em 0 0em rgba(255, 255, 255, 0.2), -1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2), -2.6em 0em 0 0em rgba(255, 255, 255, 0.2), -1.8em -1.8em 0 0em rgba(255, 255, 255, 0.5);
|
|
||||||
}
|
|
||||||
25% {
|
|
||||||
box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.5), 1.8em -1.8em 0 0em rgba(255, 255, 255, 0.7), 2.5em 0em 0 0em #C0C0C0, 1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2), 0em 2.5em 0 0em rgba(255, 255, 255, 0.2), -1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2), -2.6em 0em 0 0em rgba(255, 255, 255, 0.2), -1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
|
||||||
}
|
|
||||||
37.5% {
|
|
||||||
box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2), 1.8em -1.8em 0 0em rgba(255, 255, 255, 0.5), 2.5em 0em 0 0em rgba(255, 255, 255, 0.7), 1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2), 0em 2.5em 0 0em rgba(255, 255, 255, 0.2), -1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2), -2.6em 0em 0 0em rgba(255, 255, 255, 0.2), -1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2), 1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2), 2.5em 0em 0 0em rgba(255, 255, 255, 0.5), 1.75em 1.75em 0 0em rgba(255, 255, 255, 0.7), 0em 2.5em 0 0em #C0C0C0, -1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2), -2.6em 0em 0 0em rgba(255, 255, 255, 0.2), -1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
|
||||||
}
|
|
||||||
62.5% {
|
|
||||||
box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2), 1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2), 2.5em 0em 0 0em rgba(255, 255, 255, 0.2), 1.75em 1.75em 0 0em rgba(255, 255, 255, 0.5), 0em 2.5em 0 0em rgba(255, 255, 255, 0.7), -1.8em 1.8em 0 0em #C0C0C0, -2.6em 0em 0 0em rgba(255, 255, 255, 0.2), -1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
|
||||||
}
|
|
||||||
75% {
|
|
||||||
box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2), 1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2), 2.5em 0em 0 0em rgba(255, 255, 255, 0.2), 1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2), 0em 2.5em 0 0em rgba(255, 255, 255, 0.5), -1.8em 1.8em 0 0em rgba(255, 255, 255, 0.7), -2.6em 0em 0 0em #C0C0C0, -1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
|
||||||
}
|
|
||||||
87.5% {
|
|
||||||
box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2), 1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2), 2.5em 0em 0 0em rgba(255, 255, 255, 0.2), 1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2), 0em 2.5em 0 0em rgba(255, 255, 255, 0.2), -1.8em 1.8em 0 0em rgba(255, 255, 255, 0.5), -2.6em 0em 0 0em rgba(255, 255, 255, 0.7), -1.8em -1.8em 0 0em #C0C0C0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@keyframes load5 {
|
|
||||||
0%,
|
|
||||||
100% {
|
|
||||||
box-shadow: 0em -2.6em 0em 0em #C0C0C0, 1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2), 2.5em 0em 0 0em rgba(255, 255, 255, 0.2), 1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2), 0em 2.5em 0 0em rgba(255, 255, 255, 0.2), -1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2), -2.6em 0em 0 0em rgba(255, 255, 255, 0.5), -1.8em -1.8em 0 0em rgba(255, 255, 255, 0.7);
|
|
||||||
}
|
|
||||||
12.5% {
|
|
||||||
box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.7), 1.8em -1.8em 0 0em #C0C0C0, 2.5em 0em 0 0em rgba(255, 255, 255, 0.2), 1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2), 0em 2.5em 0 0em rgba(255, 255, 255, 0.2), -1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2), -2.6em 0em 0 0em rgba(255, 255, 255, 0.2), -1.8em -1.8em 0 0em rgba(255, 255, 255, 0.5);
|
|
||||||
}
|
|
||||||
25% {
|
|
||||||
box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.5), 1.8em -1.8em 0 0em rgba(255, 255, 255, 0.7), 2.5em 0em 0 0em #C0C0C0, 1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2), 0em 2.5em 0 0em rgba(255, 255, 255, 0.2), -1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2), -2.6em 0em 0 0em rgba(255, 255, 255, 0.2), -1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
|
||||||
}
|
|
||||||
37.5% {
|
|
||||||
box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2), 1.8em -1.8em 0 0em rgba(255, 255, 255, 0.5), 2.5em 0em 0 0em rgba(255, 255, 255, 0.7), 1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2), 0em 2.5em 0 0em rgba(255, 255, 255, 0.2), -1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2), -2.6em 0em 0 0em rgba(255, 255, 255, 0.2), -1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2), 1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2), 2.5em 0em 0 0em rgba(255, 255, 255, 0.5), 1.75em 1.75em 0 0em rgba(255, 255, 255, 0.7), 0em 2.5em 0 0em #C0C0C0, -1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2), -2.6em 0em 0 0em rgba(255, 255, 255, 0.2), -1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
|
||||||
}
|
|
||||||
62.5% {
|
|
||||||
box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2), 1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2), 2.5em 0em 0 0em rgba(255, 255, 255, 0.2), 1.75em 1.75em 0 0em rgba(255, 255, 255, 0.5), 0em 2.5em 0 0em rgba(255, 255, 255, 0.7), -1.8em 1.8em 0 0em #C0C0C0, -2.6em 0em 0 0em rgba(255, 255, 255, 0.2), -1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
|
||||||
}
|
|
||||||
75% {
|
|
||||||
box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2), 1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2), 2.5em 0em 0 0em rgba(255, 255, 255, 0.2), 1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2), 0em 2.5em 0 0em rgba(255, 255, 255, 0.5), -1.8em 1.8em 0 0em rgba(255, 255, 255, 0.7), -2.6em 0em 0 0em #C0C0C0, -1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
|
||||||
}
|
|
||||||
87.5% {
|
|
||||||
box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2), 1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2), 2.5em 0em 0 0em rgba(255, 255, 255, 0.2), 1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2), 0em 2.5em 0 0em rgba(255, 255, 255, 0.2), -1.8em 1.8em 0 0em rgba(255, 255, 255, 0.5), -2.6em 0em 0 0em rgba(255, 255, 255, 0.7), -1.8em -1.8em 0 0em #C0C0C0;
|
|
||||||
}
|
|
||||||
}transform:
|
|
||||||
</style>
|
|
||||||
<script type="text/javascript">
|
|
||||||
var queryStack = new Array();
|
|
||||||
var currentQuery = null;
|
|
||||||
var currentTooltip = null;
|
|
||||||
|
|
||||||
var descriptionTemplate = "{{#type_none}} \
|
|
||||||
<h3 align='center'>Node Properties</h3> \
|
|
||||||
<div style='padding-bottom:1em'>Select a node for more information</div> \
|
|
||||||
{{/type_none}} \
|
|
||||||
{{#type_loading}} \
|
|
||||||
<h3 align='center'>Loading...</h3> \
|
|
||||||
<div class='loader'>Loading...</div> \
|
|
||||||
{{/type_loading}} \
|
|
||||||
{{#type_user}} \
|
|
||||||
<h3 align='center'>{{label}} Properties</h2> \
|
|
||||||
<dl class='lister'>\
|
|
||||||
<dt>SAMAccountName</dt> \
|
|
||||||
<dd> None </dd> \
|
|
||||||
<dt>Display Name</dt> \
|
|
||||||
<dd> None </dd> \
|
|
||||||
<dt>Password Last Changed</dt> \
|
|
||||||
<dd> None </dd> \
|
|
||||||
<dt>First Degree Group Memberships</dt>\
|
|
||||||
<dd>\
|
|
||||||
<a href=\"#\" onclick=\"doQuery('MATCH (n:User {name:"{{label}}"}), (target:Group),p=allShortestPaths((n)-[:MemberOf*1]->(target)) RETURN p')\">\
|
|
||||||
{{first_degree_group}}\
|
|
||||||
</a>\
|
|
||||||
</dd>\
|
|
||||||
<dt>Unrolled Group Memberships</dt>\
|
|
||||||
<dd>\
|
|
||||||
<a href=\"#\" onclick=\"doQuery('MATCH (n:User {name:"{{label}}"}), (target:Group),p=allShortestPaths((n)-[:MemberOf*1..]->(target)) RETURN p')\">\
|
|
||||||
{{unrolled}}\
|
|
||||||
</a>\
|
|
||||||
</dd>\
|
|
||||||
<dt>First Degree Local Admin</dt>\
|
|
||||||
<dd>\
|
|
||||||
<a href=\"#\" onclick=\"doQuery('MATCH (n:User {name:"{{label}}"}), (target:Computer), p=allShortestPaths((n)-[:AdminTo*1]->(target)) RETURN p')\">\
|
|
||||||
{{first_degree_admin}}\
|
|
||||||
</a>\
|
|
||||||
</dd>\
|
|
||||||
<dt>Group Delegated Local Admin Rights</dt>\
|
|
||||||
<dd>\
|
|
||||||
<a href=\"#\" onclick=\"doQuery('MATCH (n:User {name:"{{label}}"})-[r:MemberOf]->(m:Group)-[s:AdminTo]->(l:Computer) RETURN n,r,m,s,l')\">\
|
|
||||||
{{dlar}}\
|
|
||||||
</a>\
|
|
||||||
</dd>\
|
|
||||||
<dt>Derivative Local Admin Rights</dt>\
|
|
||||||
<dd>\
|
|
||||||
<a href=\"#\" onclick=\"doQuery('MATCH (n:User {name:"{{label}}"}), (target:Computer), p=allShortestPaths((n)-[*]->(target)) RETURN p')\">\
|
|
||||||
{{derivative}}\
|
|
||||||
</a>\
|
|
||||||
</dd>\
|
|
||||||
</dd>\
|
|
||||||
<dt>Sessions</dt>\
|
|
||||||
<dd>\
|
|
||||||
<a href=\"#\" onclick=\"doQuery('MATCH (n:Computer)-[r:HasSession]->(m:User {name:"{{label}}"}) RETURN n,r,m')\">\
|
|
||||||
{{sessions}}\
|
|
||||||
</a>\
|
|
||||||
</dd>\
|
|
||||||
</dl> \
|
|
||||||
{{/type_user}}\
|
|
||||||
{{#type_computer}} \
|
|
||||||
<h3 align='center'>{{label}} Properties</h2> \
|
|
||||||
<dl class='lister'>\
|
|
||||||
<dt>OS</dt> \
|
|
||||||
<dd> None </dd> \
|
|
||||||
<dt>Allows Unconstrained Delegation</dt> \
|
|
||||||
<dd> None </dd> \
|
|
||||||
<dt>Explicit Admins</dt>\
|
|
||||||
<dd>\
|
|
||||||
<a href=\"#\" onclick=\"doQuery('MATCH (a)-[b:AdminTo]->(c:Computer {name:"{{label}}"}) RETURN a,b,c')\">\
|
|
||||||
{{explicit_admins}}\
|
|
||||||
</a>\
|
|
||||||
</dd>\
|
|
||||||
<dt>Sessions</dt>\
|
|
||||||
<dd>\
|
|
||||||
<a href=\"#\" onclick=\"doQuery('MATCH (m:Computer {name:"{{label}}"})-[r:HasSession]->(n:User) WITH n,r,m WHERE NOT n.name ENDS WITH "$" RETURN n,r,m')\">\
|
|
||||||
{{sessioncount}}\
|
|
||||||
</a>\
|
|
||||||
</dd>\
|
|
||||||
<dt>First Degree Local Admin</dt>\
|
|
||||||
<dd>\
|
|
||||||
<a href=\"#\" onclick=\"doQuery('MATCH (n:User {name:"{{label}}"}), (target:Computer), p=allShortestPaths((n)-[:AdminTo*1]->(target)) RETURN p')\">\
|
|
||||||
{{first_degree_admin}}\
|
|
||||||
</a>\
|
|
||||||
</dd>\
|
|
||||||
<dt>Group Delegated Local Admin Rights</dt>\
|
|
||||||
<dd>\
|
|
||||||
<a href=\"#\" onclick=\"doQuery('MATCH (n:User {name:"{{label}}"})-[r:MemberOf]->(m:Group)-[s:AdminTo]->(l:Computer) RETURN n,r,m,s,l')\">\
|
|
||||||
{{dlar}}\
|
|
||||||
</a>\
|
|
||||||
</dd>\
|
|
||||||
<dt>Derivative Local Admin Rights</dt>\
|
|
||||||
<dd>\
|
|
||||||
<a href=\"#\" onclick=\"doQuery('MATCH (n:User {name:"{{label}}"}), (target:Computer), p=allShortestPaths((n)-[*]->(target)) RETURN p')\">\
|
|
||||||
{{derivative}}\
|
|
||||||
</a>\
|
|
||||||
</dd>\
|
|
||||||
</dl> \
|
|
||||||
{{/type_computer}}\
|
|
||||||
"
|
|
||||||
function redoLast(){
|
|
||||||
if (queryStack.length > 0){
|
|
||||||
if (currentTooltip != null){
|
|
||||||
currentTooltip.close();
|
|
||||||
}
|
|
||||||
sigma.layouts.stopForceLink();
|
|
||||||
currentQuery = queryStack.pop();
|
|
||||||
sigma.neo4j.cypher(
|
|
||||||
{url: 'http://localhost:7474', user:'neo4j', password:'neo4jj'},
|
|
||||||
currentQuery,
|
|
||||||
sigmaInstance,
|
|
||||||
function() {
|
|
||||||
$.each(sigmaInstance.graph.nodes(), function(index, node){
|
|
||||||
node.label = node.neo4j_data['name']
|
|
||||||
if (node.neo4j_labels[0]=='Group'){
|
|
||||||
node.type_group = true
|
|
||||||
node.color="#FF0000"
|
|
||||||
}else if (node.neo4j_labels[0] == 'User'){
|
|
||||||
node.type_user = true
|
|
||||||
node.color="#80FF00"
|
|
||||||
}else{
|
|
||||||
node.type_computer = true
|
|
||||||
node.color="#0000CC"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
sigmaInstance.refresh();
|
|
||||||
sigma.layouts.startForceLink();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function doQuery(query){
|
|
||||||
queryStack.push(currentQuery);
|
|
||||||
currentQuery = query;
|
|
||||||
if (currentTooltip != null){
|
|
||||||
currentTooltip.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
sigma.layouts.stopForceLink();
|
|
||||||
sigma.neo4j.cypher(
|
|
||||||
{url: 'http://localhost:7474', user:'neo4j', password:'neo4jj'},
|
|
||||||
query,
|
|
||||||
sigmaInstance,
|
|
||||||
function() {
|
|
||||||
$.each(sigmaInstance.graph.nodes(), function(index, node){
|
|
||||||
node.label = node.neo4j_data['name']
|
|
||||||
if (node.neo4j_labels[0]=='Group'){
|
|
||||||
node.type_group = true
|
|
||||||
node.color="#FF0000"
|
|
||||||
}else if (node.neo4j_labels[0] == 'User'){
|
|
||||||
node.type_user = true
|
|
||||||
node.color="#80FF00"
|
|
||||||
}else{
|
|
||||||
node.type_computer = true
|
|
||||||
node.color="#0000CC"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
sigmaInstance.refresh();
|
|
||||||
sigma.layouts.startForceLink();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadDbData(){
|
|
||||||
$.ajax({
|
|
||||||
url: "http://localhost:7474/db/data/transaction/commit",
|
|
||||||
type: 'POST',
|
|
||||||
accepts: {json: "application/json"},
|
|
||||||
dataType: "json",
|
|
||||||
contentType: "application/json",
|
|
||||||
headers: {
|
|
||||||
"Authorization": "Basic bmVvNGo6bmVvNGpq"
|
|
||||||
},
|
|
||||||
data: JSON.stringify({
|
|
||||||
"statements" : [ {
|
|
||||||
"statement" : "MATCH (n:User) WHERE NOT n.name ENDS WITH '$' RETURN count(n)"
|
|
||||||
}, {
|
|
||||||
"statement" : "MATCH (n:Group) RETURN count(n)"
|
|
||||||
}, {
|
|
||||||
"statement" : "MATCH ()-[r]->() RETURN count(r)"
|
|
||||||
}, {
|
|
||||||
"statement" : "MATCH (n:Computer) RETURN count(n)"
|
|
||||||
} ]
|
|
||||||
}),
|
|
||||||
success: function(json) {
|
|
||||||
var usercount = json.results[0].data[0].row[0];
|
|
||||||
var groupcount = json.results[1].data[0].row[0];
|
|
||||||
var relcount = json.results[2].data[0].row[0];
|
|
||||||
var compcount = json.results[3].data[0].row[0];
|
|
||||||
|
|
||||||
var template = $('#dbdata').html();
|
|
||||||
Mustache.parse(template);
|
|
||||||
var rendered = Mustache.render(template, {url:"http://localhost:7474/db/data/cypher", user: "neo4j", num_users: usercount, num_groups: groupcount, num_relationships: relcount, num_computers: compcount});
|
|
||||||
$('#dbdata').html(rendered);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateNodeData(node){
|
|
||||||
var rendered = Mustache.render(descriptionTemplate, {type_loading: true});
|
|
||||||
$('#nodeproperties').html(rendered);
|
|
||||||
if (node.data.node.type_user == true){
|
|
||||||
$.ajax({
|
|
||||||
url: "http://localhost:7474/db/data/transaction/commit",
|
|
||||||
type: 'POST',
|
|
||||||
accepts: {json: "application/json"},
|
|
||||||
dataType: "json",
|
|
||||||
contentType: "application/json",
|
|
||||||
headers: {
|
|
||||||
"Authorization": "Basic bmVvNGo6bmVvNGpq"
|
|
||||||
},
|
|
||||||
data: JSON.stringify({
|
|
||||||
"statements" : [ {
|
|
||||||
"statement" : "MATCH (n:User {name:'" + node.data.node.label + "'}), (target:Group), p=allShortestPaths((n)-[:MemberOf*1]->(target)) RETURN count(target)"
|
|
||||||
}, {
|
|
||||||
"statement" : "MATCH (n:User {name:'" + node.data.node.label + "'}), (target:Group), p=allShortestPaths((n)-[:MemberOf*1..]->(target)) RETURN count(target)"
|
|
||||||
}, {
|
|
||||||
"statement" : "MATCH (n:User {name:'" + node.data.node.label + "'}), (target:Computer), p=allShortestPaths((n)-[:AdminTo*1]->(target)) RETURN count(target)"
|
|
||||||
}, {
|
|
||||||
"statement" : "MATCH (n:User {name:'" + node.data.node.label + "'})-[:MemberOf]->(m:Group)-[:AdminTo]->(l:Computer) RETURN count(l)"
|
|
||||||
}, {
|
|
||||||
"statement" : "MATCH (n:User {name:'" + node.data.node.label + "'}), (target:Computer), p=allShortestPaths((n)-[*]->(target)) RETURN count(target)"
|
|
||||||
}, {
|
|
||||||
"statement" : "MATCH (n:Computer)-[r:HasSession]->(m:User {name:'" + node.data.node.label + "'}) RETURN count(n)"
|
|
||||||
} ]
|
|
||||||
}),
|
|
||||||
success: function(json) {
|
|
||||||
var fdg = json.results[0].data[0].row[0];
|
|
||||||
var unr = json.results[1].data[0].row[0];
|
|
||||||
var diradmin = json.results[2].data[0].row[0];
|
|
||||||
var dla = json.results[3].data[0].row[0];
|
|
||||||
var dir = json.results[4].data[0].row[0];
|
|
||||||
var sessions = json.results[5].data[0].row[0];
|
|
||||||
|
|
||||||
var rendered = Mustache.render(descriptionTemplate, {label: node.data.node.label, first_degree_group:fdg, unrolled: unr, first_degree_admin: diradmin, type_user: true, dlar: dla, derivative:dir, sessions:sessions});
|
|
||||||
$('#nodeproperties').html(rendered);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}else if (node.data.node.type_computer == true){
|
|
||||||
$.ajax({
|
|
||||||
url: "http://localhost:7474/db/data/transaction/commit",
|
|
||||||
type: 'POST',
|
|
||||||
accepts: {json: "application/json"},
|
|
||||||
dataType: "json",
|
|
||||||
contentType: "application/json",
|
|
||||||
headers: {
|
|
||||||
"Authorization": "Basic bmVvNGo6bmVvNGpq"
|
|
||||||
},
|
|
||||||
data: JSON.stringify({
|
|
||||||
"statements" : [ {
|
|
||||||
"statement" : "MATCH (a)-[b:AdminTo]->(c:Computer {name:'" + node.data.node.label + "'}) RETURN count(a)"
|
|
||||||
}, {
|
|
||||||
"statement" : "MATCH (m:Computer {name:'" + node.data.node.label + "'})-[r:HasSession]->(n:User) WITH n WHERE NOT n.name ENDS WITH '$' RETURN count(n)"
|
|
||||||
} ]
|
|
||||||
}),
|
|
||||||
success: function(json) {
|
|
||||||
var c1 = json.results[0].data[0].row[0];
|
|
||||||
var c2 = json.results[1].data[0].row[0];
|
|
||||||
|
|
||||||
|
|
||||||
var rendered = Mustache.render(descriptionTemplate, {label: node.data.node.label, explicit_admins: c1, type_computer: true, sessioncount: c2});
|
|
||||||
$('#nodeproperties').html(rendered);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function initNodeData(){
|
|
||||||
Mustache.parse(descriptionTemplate);
|
|
||||||
var rendered = Mustache.render(descriptionTemplate, {type_none: true});
|
|
||||||
$('#nodeproperties').html(rendered);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setLabelAsStart(label){
|
|
||||||
$('#startpoint').val(label);
|
|
||||||
};
|
|
||||||
|
|
||||||
function setLabelAsEnd(label){
|
|
||||||
$('#endpoint').val(label);
|
|
||||||
};
|
|
||||||
|
|
||||||
jQuery(document).ready(function(){
|
|
||||||
sigma.renderers.def = sigma.renderers.canvas
|
|
||||||
sigmaInstance = new sigma({
|
|
||||||
container: 'graph'
|
|
||||||
});
|
|
||||||
sigmaInstance.settings({
|
|
||||||
edgeColor: 'default',
|
|
||||||
defaultEdgeColor: '#356',
|
|
||||||
nodeColor: 'default',
|
|
||||||
edgeHoverColor: 'default',
|
|
||||||
defaultEdgeHoverColor: '#090',
|
|
||||||
enableEdgeHovering: false,
|
|
||||||
defaultEdgeType: 'arrow',
|
|
||||||
minEdgeSize: 1,
|
|
||||||
maxEdgeSize: 2.5,
|
|
||||||
autoRescale: true
|
|
||||||
})
|
|
||||||
|
|
||||||
sigmaInstance.bind('clickNode', function(e){
|
|
||||||
updateNodeData(e)
|
|
||||||
})
|
|
||||||
|
|
||||||
loadDbData()
|
|
||||||
initNodeData()
|
|
||||||
|
|
||||||
var tooltips = sigma.plugins.tooltips(
|
|
||||||
sigmaInstance,
|
|
||||||
sigmaInstance.renderers[0],
|
|
||||||
{
|
|
||||||
node: [{
|
|
||||||
show: 'rightClickNode',
|
|
||||||
cssClass: 'sigma-tooltip',
|
|
||||||
template:
|
|
||||||
'<div class="arrow"></div>' +
|
|
||||||
' <div class="sigma-tooltip-header">{{label}}</div>' +
|
|
||||||
' <div class="sigma-tooltip-body">' +
|
|
||||||
' <div class="btn-group btn-group-justified" role="group" aria-label="...">' +
|
|
||||||
' {{#type_user}}'+
|
|
||||||
' <div class="btn-group" role="group">'+
|
|
||||||
' <button type="button" class="btn btn-default" onclick="doQuery(\'MATCH (n {name:"{{label}}"})-[r:MemberOf]->m RETURN n,r,m\')">Members Of</button>' +
|
|
||||||
' </div>'+
|
|
||||||
' <div class="btn-group" role="group">'+
|
|
||||||
' <button type="button" class="btn btn-default" onclick="doQuery(\'MATCH (n:User {name:"{{label}}"}), (target:Computer),p=allShortestPaths((n)-[*]->(target)) RETURN p\')">Admin To</button>' +
|
|
||||||
' </div>'+
|
|
||||||
' {{/type_user}}' +
|
|
||||||
' {{#type_computer}}' +
|
|
||||||
' <div class="btn-group" role="group">'+
|
|
||||||
' <button type="button" class="btn btn-default" onclick="doQuery(\'MATCH (n {name:"{{label}}"})-[r:HasSession]->m WITH n,r,m WHERE NOT m.name ENDS WITH "$" RETURN n,r,m\')">Get Sessions</button>' +
|
|
||||||
' </div>'+
|
|
||||||
' <div class="btn-group" role="group">'+
|
|
||||||
' <button type="button" class="btn btn-default" onclick="doQuery(\'MATCH (n {name:"{{label}}"})<-[r:AdminTo]-m RETURN n,r,m\')">Get Admins</button>' +
|
|
||||||
' </div>'+
|
|
||||||
' {{/type_computer}}' +
|
|
||||||
' {{#type_group}}' +
|
|
||||||
' <div class="btn-group" role="group">'+
|
|
||||||
' <button type="button" class="btn btn-default" onclick="doQuery(\'MATCH (n {name:"{{label}}"})<-[r:MemberOf]-m RETURN n,r,m\')">Get Members</button>' +
|
|
||||||
' </div>'+
|
|
||||||
' <div class="btn-group" role="group">'+
|
|
||||||
' <button type="button" class="btn btn-default" onclick="doQuery(\'MATCH (n {name:"{{label}}"})-[r:AdminTo]->m RETURN n,r,m\')">Admin To</button>' +
|
|
||||||
' </div>'+
|
|
||||||
' {{/type_group}}' +
|
|
||||||
' </div>' +
|
|
||||||
' <div class="btn-group btn-group-justified" role="group" aria-label="...">' +
|
|
||||||
' {{#type_user}}'+
|
|
||||||
' <div class="btn-group" role="group">'+
|
|
||||||
' <button type="button" class="btn btn-default" onclick="setLabelAsStart(\'{{label}}\')">Set As Start Point</button>' +
|
|
||||||
' </div>'+
|
|
||||||
' <div class="btn-group" role="group">'+
|
|
||||||
' <button type="button" class="btn btn-default" onclick="setLabelAsEnd(\'{{label}}\')">Set As End Point</button>' +
|
|
||||||
' </div>'+
|
|
||||||
' {{/type_user}}' +
|
|
||||||
' {{#type_computer}}' +
|
|
||||||
' <div class="btn-group" role="group">'+
|
|
||||||
' <button type="button" class="btn btn-default" onclick="setLabelAsStart(\'{{label}}\')">Set As Start Point</button>' +
|
|
||||||
' </div>'+
|
|
||||||
' <div class="btn-group" role="group">'+
|
|
||||||
' <button type="button" class="btn btn-default" onclick="setLabelAsEnd(\'{{label}}\')">Set As End Point</button>' +
|
|
||||||
' </div>'+
|
|
||||||
' {{/type_computer}}' +
|
|
||||||
' {{#type_group}}' +
|
|
||||||
' <div class="btn-group" role="group">'+
|
|
||||||
' <button type="button" class="btn btn-default" onclick="setLabelAsStart(\'{{label}}\')">Set As Start Point</button>' +
|
|
||||||
' </div>'+
|
|
||||||
' <div class="btn-group" role="group">'+
|
|
||||||
' <button type="button" class="btn btn-default" onclick="setLabelAsEnd(\'{{label}}\')">Set As End Point</button>' +
|
|
||||||
' </div>'+
|
|
||||||
' {{/type_group}}' +
|
|
||||||
' </div>' +
|
|
||||||
' </div>' +
|
|
||||||
' <div class="sigma-tooltip-footer">Number of connections: {{degree}}</div>',
|
|
||||||
autoadjust: true,
|
|
||||||
renderer: function(node, template){
|
|
||||||
node.degree = this.degree(node.id)
|
|
||||||
return Mustache.render(template, node)
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
tooltips.bind('shown', function(event){
|
|
||||||
currentTooltip = event.target;
|
|
||||||
})
|
|
||||||
|
|
||||||
tooltips.bind('hidden', function(event){
|
|
||||||
currentTooltip = null;
|
|
||||||
})
|
|
||||||
|
|
||||||
var fa = sigma.layouts.configForceLink(sigmaInstance, {
|
|
||||||
worker: true,
|
|
||||||
background: true,
|
|
||||||
scalingRatio: 5,
|
|
||||||
gravity: 1,
|
|
||||||
easing: 'cubicInOut',
|
|
||||||
autoStop: true,
|
|
||||||
alignNodeSiblings:true
|
|
||||||
});
|
|
||||||
|
|
||||||
fa.bind('start stop', function(event){
|
|
||||||
console.log(event.type);
|
|
||||||
})
|
|
||||||
|
|
||||||
$('#searchbar').bind('keypress', function(e){
|
|
||||||
if (e.which == 13){
|
|
||||||
doQuery('MATCH (n) WHERE n.name STARTS WITH "' + $('#searchbar').val() + '" AND NOT n.name ENDS WITH "$" RETURN n');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var dragListener = sigma.plugins.dragNodes(sigmaInstance, sigmaInstance.renderers[0])
|
|
||||||
|
|
||||||
$('#findpath').on('click', function(event){
|
|
||||||
doQuery("MATCH (source {name:'" + $('#startpoint').val() + "'}), (target {name:'" + $('#endpoint').val() + "'}), p=allShortestPaths((source)-[*]->(target)) RETURN p");
|
|
||||||
})
|
|
||||||
|
|
||||||
$('#backButton').on('click', function () {
|
|
||||||
redoLast()
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#endpoint').typeahead({
|
|
||||||
source: function (query, process) {
|
|
||||||
return $.ajax({
|
|
||||||
url: "http://localhost:7474/db/data/cypher",
|
|
||||||
type: 'POST',
|
|
||||||
accepts: {json: "application/json"},
|
|
||||||
dataType: "json",
|
|
||||||
contentType: "application/json",
|
|
||||||
headers: {
|
|
||||||
"Authorization": "Basic bmVvNGo6bmVvNGpq"
|
|
||||||
},
|
|
||||||
data: JSON.stringify( { "query" : "MATCH (n) WHERE n.name STARTS WITH '" + query + "' AND NOT n.name ENDS WITH '$' RETURN n.name LIMIT 10"}),
|
|
||||||
success: function(json) {
|
|
||||||
var d = json.data
|
|
||||||
var l = d.length;
|
|
||||||
for (var i = 0; i < l; i++) {
|
|
||||||
d[i] = d[i].toString();
|
|
||||||
}
|
|
||||||
return process(json.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#startpoint').typeahead({
|
|
||||||
source: function (query, process) {
|
|
||||||
return $.ajax({
|
|
||||||
url: "http://localhost:7474/db/data/cypher",
|
|
||||||
type: 'POST',
|
|
||||||
accepts: {json: "application/json"},
|
|
||||||
dataType: "json",
|
|
||||||
contentType: "application/json",
|
|
||||||
headers: {
|
|
||||||
"Authorization": "Basic bmVvNGo6bmVvNGpq"
|
|
||||||
},
|
|
||||||
data: JSON.stringify( { "query" : "MATCH (n) WHERE n.name STARTS WITH '" + query + "' AND NOT n.name ENDS WITH '$' RETURN n.name LIMIT 25"}),
|
|
||||||
success: function(json) {
|
|
||||||
var d = json.data
|
|
||||||
var l = d.length;
|
|
||||||
for (var i = 0; i < l; i++) {
|
|
||||||
d[i] = d[i].toString();
|
|
||||||
}
|
|
||||||
return process(json.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#searchbar').typeahead({
|
|
||||||
source: function (query, process) {
|
|
||||||
return $.ajax({
|
|
||||||
url: "http://localhost:7474/db/data/cypher",
|
|
||||||
type: 'POST',
|
|
||||||
accepts: {json: "application/json"},
|
|
||||||
dataType: "json",
|
|
||||||
contentType: "application/json",
|
|
||||||
headers: {
|
|
||||||
"Authorization": "Basic bmVvNGo6bmVvNGpq"
|
|
||||||
},
|
|
||||||
data: JSON.stringify( { "query" : "MATCH (n) WHERE n.name STARTS WITH '" + query + "' AND NOT n.name ENDS WITH '$' RETURN n.name LIMIT 10"}),
|
|
||||||
success: function(json) {
|
|
||||||
var d = json.data
|
|
||||||
var l = d.length;
|
|
||||||
for (var i = 0; i < l; i++) {
|
|
||||||
d[i] = d[i].toString();
|
|
||||||
}
|
|
||||||
return process(json.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, afterSelect: function(selected){
|
|
||||||
doQuery('MATCH (n) WHERE n.name STARTS WITH "' + selected + '" AND NOT n.name ENDS WITH "$" RETURN n');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
doQuery("MATCH (n:Group {name:\'DOMAIN ADMINS\'})-[r]->(m) RETURN n,r,m");
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body role="document">
|
|
||||||
<nav class="navbar navbar-inverse navbar-fixed-top">
|
|
||||||
<div class="container">
|
|
||||||
<div class="navbar-header">
|
|
||||||
<a class="navbar-brand" href="#">Offensive Dashboard</a>
|
|
||||||
</div>
|
|
||||||
<div style="width:40%" class="navbar-form navbar-left">
|
|
||||||
<input id="searchbar" style="width:100%" autocomplete="off" type="text" class="form-control" placeholder="Start typing to search for a node...">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
<div class="jumbotron" style="padding-top:10px; height:80%" id="graph-container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-7" id="graph" height="100%">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4" type="x-tmpl-mustache">
|
|
||||||
<div id="dbdata" class="boxsize" style="overflow:auto; width: 100%; padding-bottom: 10px">
|
|
||||||
<h4>
|
|
||||||
Connected to DB: {{url}} as {{user}}
|
|
||||||
</h4>
|
|
||||||
Users: {{num_users}}<br>
|
|
||||||
Computers: {{num_computers}}<br>
|
|
||||||
Groups: {{num_groups}}<br>
|
|
||||||
Relationships: {{num_relationships}}
|
|
||||||
</div>
|
|
||||||
<div id="nodeproperties" class="boxsize" style="overflow: auto;width:100%">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="boxsize" style="height: 20%; width:100%;">
|
|
||||||
<h4 align="center">
|
|
||||||
Pre-Built Analytics Queries
|
|
||||||
</h4>
|
|
||||||
<a href="#" onclick="doQuery('MATCH (n), (target:Group {name:"DOMAIN ADMINS"}),p=allShortestPaths((n)-[*]->(target)) RETURN p')">Find Shortest Paths to DA</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-3">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon" id="basic-addon1">Starting Node</span>
|
|
||||||
<input type="text" id="startpoint" class="form-control" data-provide="typeahead" autocomplete="off" aria-describedby="basic-addon1">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-addon" id="basic-addon2">Ending Node</span>
|
|
||||||
<input type="text" id="endpoint" class="form-control" data-provide="typeahead" autocomplete="off" aria-describedby="basic-addon2">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3">
|
|
||||||
<button id="findpath" type="button" class="btn btn-block">
|
|
||||||
<span class="glyphicon glyphicon-arrow-right" aria-hidden="true"></span>Find Path
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3">
|
|
||||||
<button id="backButton" type="button" class="btn btn-block">
|
|
||||||
<span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span>Go Back
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
var webpack = require('webpack');
|
||||||
|
var webpackTargetElectronRenderer = require('webpack-target-electron-renderer');
|
||||||
|
|
||||||
|
var config = {
|
||||||
|
entry: [
|
||||||
|
'webpack-hot-middleware/client?reload=true&path=http://localhost:9000/__webpack_hmr',
|
||||||
|
'./src/index',
|
||||||
|
],
|
||||||
|
module: {
|
||||||
|
loaders: [{
|
||||||
|
test: /\.jsx?$/,
|
||||||
|
loaders: ['babel-loader'],
|
||||||
|
exclude: /node_modules/
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: __dirname + '/dist',
|
||||||
|
publicPath: 'http://localhost:9000/dist/',
|
||||||
|
filename: 'bundle.js'
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: ['', '.js', '.jsx'],
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new webpack.HotModuleReplacementPlugin(),
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
config.target = webpackTargetElectronRenderer(config);
|
||||||
|
|
||||||
|
module.exports = config;
|