mirror of https://github.com/CIRCL/lookyloo
Add tooltip for URLs
parent
678985c94a
commit
0ac38a0f2c
|
@ -6,15 +6,23 @@ import json
|
||||||
from har2tree import CrawledTree
|
from har2tree import CrawledTree
|
||||||
from scrapysplashwrapper import crawl
|
from scrapysplashwrapper import crawl
|
||||||
|
|
||||||
from flask import Flask, render_template, request
|
from flask import Flask, render_template, request, session
|
||||||
from flask_bootstrap import Bootstrap
|
from flask_bootstrap import Bootstrap
|
||||||
|
|
||||||
from glob import glob
|
from glob import glob
|
||||||
import os
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
import pickle
|
||||||
|
import tempfile
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
app.secret_key = 'changeme'
|
||||||
|
|
||||||
|
if app.secret_key == 'changeme':
|
||||||
|
raise Exception('FFS, please set a proper secret key...')
|
||||||
|
|
||||||
Bootstrap(app)
|
Bootstrap(app)
|
||||||
app.config['BOOTSTRAP_SERVE_LOCAL'] = True
|
app.config['BOOTSTRAP_SERVE_LOCAL'] = True
|
||||||
app.debug = True
|
app.debug = True
|
||||||
|
@ -23,11 +31,25 @@ HAR_DIR = 'scraped'
|
||||||
SPLASH = 'http://127.0.0.1:8050'
|
SPLASH = 'http://127.0.0.1:8050'
|
||||||
|
|
||||||
|
|
||||||
|
@app.before_request
|
||||||
|
def session_management():
|
||||||
|
# make the session last indefinitely until it is cleared
|
||||||
|
session.permanent = True
|
||||||
|
|
||||||
|
|
||||||
def load_tree(report_dir):
|
def load_tree(report_dir):
|
||||||
|
if session.get('tree'):
|
||||||
|
# TODO delete file
|
||||||
|
pass
|
||||||
|
session.clear()
|
||||||
har_files = sorted(glob(os.path.join(HAR_DIR, report_dir, '*.har')))
|
har_files = sorted(glob(os.path.join(HAR_DIR, report_dir, '*.har')))
|
||||||
ct = CrawledTree(har_files)
|
ct = CrawledTree(har_files)
|
||||||
ct.find_parents()
|
ct.find_parents()
|
||||||
ct.join_trees()
|
ct.join_trees()
|
||||||
|
temp = tempfile.NamedTemporaryFile(delete=False)
|
||||||
|
pickle.dump(ct, temp)
|
||||||
|
temp.close()
|
||||||
|
session["tree"] = temp.name
|
||||||
return ct.jsonify(), ct.start_time.isoformat(), ct.user_agent, ct.root_url
|
return ct.jsonify(), ct.start_time.isoformat(), ct.user_agent, ct.root_url
|
||||||
|
|
||||||
|
|
||||||
|
@ -63,6 +85,25 @@ def get_report_dirs():
|
||||||
return sorted(os.listdir(HAR_DIR), reverse=True)
|
return sorted(os.listdir(HAR_DIR), reverse=True)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/tree/hostname/<node_uuid>', methods=['GET'])
|
||||||
|
def hostnode_details(node_uuid):
|
||||||
|
with open(session["tree"], 'rb') as f:
|
||||||
|
ct = pickle.load(f)
|
||||||
|
hostnode = ct.root_hartree.get_host_node_by_uuid(node_uuid)
|
||||||
|
urls = []
|
||||||
|
for url in hostnode.urls:
|
||||||
|
urls.append(url.jsonify())
|
||||||
|
return json.dumps(urls)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/tree/url/<node_uuid>', methods=['GET'])
|
||||||
|
def urlnode_details(node_uuid):
|
||||||
|
with open(session["tree"], 'rb') as f:
|
||||||
|
ct = pickle.load(f)
|
||||||
|
urlnode = ct.root_hartree.get_url_node_by_uuid(node_uuid)
|
||||||
|
return urlnode.jsonify()
|
||||||
|
|
||||||
|
|
||||||
@app.route('/tree/<int:tree_id>', methods=['GET'])
|
@app.route('/tree/<int:tree_id>', methods=['GET'])
|
||||||
def tree(tree_id):
|
def tree(tree_id):
|
||||||
report_dir = get_report_dirs()[tree_id]
|
report_dir = get_report_dirs()[tree_id]
|
||||||
|
|
|
@ -49,3 +49,24 @@
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tooltip {
|
||||||
|
position: absolute;
|
||||||
|
text-align: left;
|
||||||
|
padding: 2px;
|
||||||
|
font: 12px sans-serif;
|
||||||
|
background: lightsteelblue;
|
||||||
|
border: 0px;
|
||||||
|
border-radius: 8px;
|
||||||
|
z-index: 1;
|
||||||
|
border: 2px solid;
|
||||||
|
padding-top: 5px;
|
||||||
|
padding-right: 5px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip text {
|
||||||
|
font: 15px sans-serif;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,11 @@ var margin = {top: 20, right: 200, bottom: 30, left: 90},
|
||||||
height = 10000 - margin.top - margin.bottom;
|
height = 10000 - margin.top - margin.bottom;
|
||||||
|
|
||||||
var node_width = 0;
|
var node_width = 0;
|
||||||
var node_height = 35;
|
var node_height = 45;
|
||||||
|
|
||||||
|
var hostnode_tooltip = d3.select("body").append("div")
|
||||||
|
.attr("class", "tooltip")
|
||||||
|
.style("opacity", 0);
|
||||||
|
|
||||||
var init = d3.select("body").append("svg")
|
var init = d3.select("body").append("svg")
|
||||||
.attr("width", width + margin.right + margin.left)
|
.attr("width", width + margin.right + margin.left)
|
||||||
|
@ -29,7 +33,12 @@ var background = init.append('rect')
|
||||||
.attr('y', 0)
|
.attr('y', 0)
|
||||||
.attr('width', width)
|
.attr('width', width)
|
||||||
.attr('height', height)
|
.attr('height', height)
|
||||||
.style('fill', "url(#backstripes)");
|
.style('fill', "url(#backstripes)")
|
||||||
|
.on('click', function(d) {
|
||||||
|
hostnode_tooltip.transition()
|
||||||
|
.duration(500)
|
||||||
|
.style("opacity", 0);
|
||||||
|
});
|
||||||
|
|
||||||
// append the svg object to the body of the page
|
// append the svg object to the body of the page
|
||||||
// appends a 'group' element to 'svg'
|
// appends a 'group' element to 'svg'
|
||||||
|
@ -71,6 +80,41 @@ function getBB(selection) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function urlnode_click(uuid) {
|
||||||
|
var url = "url/" + uuid;
|
||||||
|
d3.json(url, function(error, u) {
|
||||||
|
if (error) throw error;
|
||||||
|
console.log(u)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function hostnode_click(d) {
|
||||||
|
// Modal display
|
||||||
|
var url = "hostname/" + d.data.uuid;
|
||||||
|
var pageX=d3.event.pageX;
|
||||||
|
var pageY=d3.event.pageY;
|
||||||
|
hostnode_tooltip.selectAll("ul").remove();
|
||||||
|
d3.json(url, function(error, urls) {
|
||||||
|
if (error) throw error;
|
||||||
|
hostnode_tooltip.transition()
|
||||||
|
.duration(200)
|
||||||
|
.style("opacity", .9)
|
||||||
|
.style("left", (pageX) + "px")
|
||||||
|
.style("top", (pageY - 28) + "px");
|
||||||
|
var list = hostnode_tooltip.append('ul')
|
||||||
|
.attr("class", "list-group");
|
||||||
|
urls.forEach(function(url){
|
||||||
|
jdata = JSON.parse(url)
|
||||||
|
var entry = list.append('li')
|
||||||
|
.attr("class", "list-group-item")
|
||||||
|
.attr("url_uuid", jdata['uuid'])
|
||||||
|
.text(jdata['name'])
|
||||||
|
.on('click', urlnode_click(jdata['uuid']));
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function update(source) {
|
function update(source) {
|
||||||
|
|
||||||
// reinitialize max_depth
|
// reinitialize max_depth
|
||||||
|
@ -101,8 +145,7 @@ function update(source) {
|
||||||
.attr('class', 'node')
|
.attr('class', 'node')
|
||||||
.attr("transform", function(d) {
|
.attr("transform", function(d) {
|
||||||
return "translate(" + source.y0 + "," + source.x0 + ")";
|
return "translate(" + source.y0 + "," + source.x0 + ")";
|
||||||
})
|
});
|
||||||
.on('click', click);
|
|
||||||
|
|
||||||
// Add Circle for the nodes
|
// Add Circle for the nodes
|
||||||
nodeEnter.append('circle')
|
nodeEnter.append('circle')
|
||||||
|
@ -110,11 +153,13 @@ function update(source) {
|
||||||
.attr('r', 1e-6)
|
.attr('r', 1e-6)
|
||||||
.style("fill", function(d) {
|
.style("fill", function(d) {
|
||||||
return d._children ? "lightsteelblue" : "#fff";
|
return d._children ? "lightsteelblue" : "#fff";
|
||||||
});
|
})
|
||||||
|
.on('click', click);
|
||||||
|
|
||||||
// Avoid hiding the content after the circle
|
// Avoid hiding the content after the circle
|
||||||
var nodeContent = nodeEnter
|
var nodeContent = nodeEnter
|
||||||
.append('svg')
|
.append('svg')
|
||||||
|
.attr('height',node_height)
|
||||||
.attr('x', 10)
|
.attr('x', 10)
|
||||||
.attr('y', -20);
|
.attr('y', -20);
|
||||||
|
|
||||||
|
@ -128,7 +173,8 @@ function update(source) {
|
||||||
.text(function(d) {
|
.text(function(d) {
|
||||||
d.data.total_width = 0; // reset total_width
|
d.data.total_width = 0; // reset total_width
|
||||||
return d.data.name;
|
return d.data.name;
|
||||||
});
|
})
|
||||||
|
.on('click', hostnode_click);
|
||||||
|
|
||||||
// This value has to be set once for all for the whole tree and cannot be updated
|
// This value has to be set once for all for the whole tree and cannot be updated
|
||||||
// on click as clicking only updates a part of the tree
|
// on click as clicking only updates a part of the tree
|
||||||
|
@ -361,4 +407,5 @@ function update(source) {
|
||||||
}
|
}
|
||||||
update(d);
|
update(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue