chg: refactoring of the tree

pull/81/head
Raphaël Vinot 2020-08-11 19:17:39 +02:00
parent 339d0dd7e0
commit b5d56a5a2b
3 changed files with 238 additions and 321 deletions

View File

@ -1,17 +1,20 @@
"use strict";
// From : https://bl.ocks.org/d3noob/43a860bc0024792f8803bba8ca0d5ecd // From : https://bl.ocks.org/d3noob/43a860bc0024792f8803bba8ca0d5ecd
// Set the dimensions and margins of the diagram // Set the dimensions and margins of the diagram
var margin = {top: 20, right: 200, bottom: 30, left: 90}, let margin = {
width = 960 - margin.left - margin.right, top: 20,
height = 1000 - margin.top - margin.bottom; right: 200,
bottom: 30,
left: 90
};
let width = 960 - margin.left - margin.right;
let height = 1000 - margin.top - margin.bottom;
var node_width = 0; let node_width = 0;
var max_overlay_width = 1500; let node_height = 55;
var default_max_overlay_height = 500;
var node_height = 55;
var t = d3.transition().duration(750);
var main_svg = d3.select("body").append("svg") let main_svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left) .attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom) .attr("height", height + margin.top + margin.bottom)
@ -22,23 +25,11 @@ d3.select('body')
.attr('class', 'tooltip') .attr('class', 'tooltip')
.attr('style', 'position: absolute; opacity: 0;'); .attr('style', 'position: absolute; opacity: 0;');
main_svg.append("clipPath") // Define SVGs
.attr("id", "textOverlay") let defs = main_svg.append("defs");
.append("rect")
.attr('width', max_overlay_width - 25)
.attr('height', node_height);
main_svg.append("clipPath")
.attr("id", "overlayHeight")
.append("rect")
.attr('width', max_overlay_width)
.attr('height', default_max_overlay_height + 100);
// Define stuff
var defs = main_svg.append("defs");
// Add background pattern // Add background pattern
var pattern = defs.append('pattern') let pattern = defs.append('pattern')
.attr('id', 'backstripes') .attr('id', 'backstripes')
.attr('x', margin.left) .attr('x', margin.left)
.attr("width", node_width * 2) .attr("width", node_width * 2)
@ -53,103 +44,87 @@ pattern.append('rect')
// 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'
// moves the 'group' element to the top left margin // moves the 'group' element to the top left margin
var node_container = main_svg let node_container = main_svg.append("g")
.append("g") .attr("transform", `translate(${margin.left},${margin.top})`);
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var i = 0,
duration = 750;
// Assigns parent, children, height, depth // Assigns parent, children, height, depth
var root = d3.hierarchy(treeData); let root = d3.hierarchy(treeData);
root.x0 = height / 2; // middle of the page root.x0 = height / 2; // middle of the page
root.y0 = 0; root.y0 = 0;
// declares a tree layout // declares a tree layout
var tree = d3.tree(); let tree = d3.tree();
update(root); update(root);
// Collapse the node and all it's children
function collapse(d) {
if(d.children) {
d._children = d.children
d._children.forEach(collapse)
d.children = null
}
};
function openTreeInNewTab(capture_uuid, hostnode_uuid=null) { function openTreeInNewTab(capture_uuid, hostnode_uuid=null) {
var url = '/tree/' + capture_uuid; let url = `/tree/${capture_uuid}`;
if (hostnode_uuid) { if (hostnode_uuid != null) {
url += '/' + hostnode_uuid; url += `/${hostnode_uuid}`;
}; }
var win = window.open(url, '_blank'); let win = window.open(url, '_blank');
// FIXME: If win is None, the browser didn't allow opening a new tab, we need to inform the user. if (win == null) {
alert("The browser didn't allow Lookyloo to open a new tab. There should be an icon on the right of your URL bar to allow it.");
}
win.focus(); win.focus();
} }
function urlnode_click(d) { function open_hostnode_popup(d) {
var url = "/tree/url/" + d.data.uuid; let win = window.open(`/tree/${treeUUID}/hostname_popup/${d.data.uuid}`, '_blank', 'width=1024,height=768,left=200,top=100');
d3.blob(url, {credentials: 'same-origin'}).then(data => { if (win == null) {
var file = new File([data], "file.zip", {type: "application/zip"}); alert("The browser didn't allow Lookyloo to open a pop-up. There should be an icon on the right of your URL bar to allow it.");
saveAs(file); }
}); win.focus();
}; }
function hostnode_click_popup(d) { function LocateNode(hostnode_uuid) {
window.open('/tree/' + treeUUID + '/hostname_popup/' + d.data.uuid, '_blank', 'width=1024,height=768,left=200,top=100'); let element = document.getElementById(`node_${hostnode_uuid}`);
};
function LocateNode(urlnode_uuid) {
var element = document.getElementById("node_" + urlnode_uuid);
element.scrollIntoView({behavior: "smooth", block: "center", inline: "center"}); element.scrollIntoView({behavior: "smooth", block: "center", inline: "center"});
var line_arrow = d3.select("#node_" + urlnode_uuid) let line_arrow = d3.select(`#node_${hostnode_uuid}`)
.append('g') .append('g')
.attr('cursor', 'pointer') .attr('cursor', 'pointer')
.on('click', function() { .on('click', function() {
this.remove(); this.remove();
}); });
function lineData(d){ let line = d3.line()
var points = [
{lx: d.source.x, ly: d.source.y},
{lx: d.target.x, ly: d.source.y},
{lx: d.target.x, ly: d.target.y}
];
return line(points);
};
var line = d3.line()
// Other options: http://bl.ocks.org/d3indepth/raw/b6d4845973089bc1012dec1674d3aff8/ // Other options: http://bl.ocks.org/d3indepth/raw/b6d4845973089bc1012dec1674d3aff8/
//.curve(d3.curveCardinal) //.curve(d3.curveCardinal)
.curve(d3.curveBundle) .curve(d3.curveBundle)
.x( function(point) { return point.lx; }) .x(point => point.lx)
.y( function(point) { return point.ly; }); .y(point => point.ly);
var line_tip = d3.symbol() let line_tip = d3.symbol()
.type(d3.symbolTriangle) .type(d3.symbolTriangle)
.size(200); .size(200);
var path = line_arrow let path = line_arrow
.append("path") .append("path")
.attr("stroke-width", "3") .attr("stroke-width", "3")
.attr("stroke", "black") .attr("stroke", "black")
.attr("fill", "none") .attr("fill", "none")
.data([{source: {x : node_width/2, y : -100}, target: {x : node_width/4, y : -node_height/2}}]) .data([{
source: {x: node_width/2, y: -100},
target: {x: node_width/4, y: -node_height/2}
}])
.attr("class", "line") .attr("class", "line")
.attr("d", lineData); .attr("d", d => line(
[{lx: d.source.x, ly: d.source.y},
{lx: d.target.x, ly: d.source.y},
{lx: d.target.x, ly: d.target.y}
])
);
var arrow = line_arrow let arrow = line_arrow
.append("path") .append("path")
.attr("d", line_tip) .attr("d", line_tip)
.attr("stroke", 'black') .attr("stroke", 'black')
.style('stroke-width', '3') .style('stroke-width', '3')
.attr("fill", 'white') .attr("fill", 'white')
.attr("transform", function(d) { return "translate(" + node_width/4 + "," + -node_height/1.5 + ") rotate(60)"; });; .attr("transform", `translate(${node_width / 4}, ${-node_height / 1.5}) rotate(60)`);
function glow() { let glow = () => {
line_arrow.selectAll('path') line_arrow.selectAll('path')
.transition().duration(1000) //Set transition .transition().duration(1000) //Set transition
.style('stroke-width', '7') .style('stroke-width', '7')
@ -157,13 +132,16 @@ function LocateNode(urlnode_uuid) {
.transition().duration(1000) //Set transition .transition().duration(1000) //Set transition
.style('stroke-width', '3') .style('stroke-width', '3')
.style('stroke', 'black') .style('stroke', 'black')
.on("end", function() { .on("end", () => {
if (++i > 15) line_arrow.remove(); if (++i > 15) {
glow(); line_arrow.remove();
} else {
glow();
}
}); });
}; };
var i = 0; let i = 0;
glow(); glow();
}; };
@ -172,127 +150,55 @@ function UnflagAllNodes() {
d3.selectAll('.node_data').select('text').style('fill', 'black'); d3.selectAll('.node_data').select('text').style('fill', 'black');
d3.selectAll('.node_data').select("#flag") d3.selectAll('.node_data').select("#flag")
.text("🏁") .text("🏁")
.on('click', function(d) { .on('click', d => NodeHighlight(d.data.uuid))
PermanentNodeHighlight(d.data.uuid); .on('mouseover',() => {
})
.on('mouseover', d => {
d3.select('#tooltip') d3.select('#tooltip')
.style('opacity', 1) .style('opacity', 1)
.style('left', (d3.event.pageX+10) + 'px') .style('left', `${d3.event.pageX + 10}px`)
.style('top', (d3.event.pageY+10) + 'px') .style('top', `${d3.event.pageY + 10}px`)
.text('Flag this node for further analysis'); .text('Flag this node');
}) })
.on('mouseout', function() { .on('mouseout', () => d3.select('#tooltip').style('opacity', 0));
d3.select('#tooltip')
.style('opacity', 0);
});
}; };
function UnflagHostNode(hostnode_uuid) { function UnflagHostNode(hostnode_uuid) {
d3.select("#node_" + hostnode_uuid).select('rect').style('fill', 'white'); d3.select(`#node_${hostnode_uuid}`).select('rect').style('fill', 'white');
d3.select("#node_" + hostnode_uuid).select('text').style('fill', 'black'); d3.select(`#node_${hostnode_uuid}`).select('text').style('fill', 'black');
d3.select("#node_" + hostnode_uuid).select("#flag") d3.select(`#node_${hostnode_uuid}`).select("#flag")
.text("🏁") .text("🏁")
.on('click', function(d) { .on('click', d => NodeHighlight(d.data.uuid))
PermanentNodeHighlight(d.data.uuid); .on('mouseover', () => {
})
.on('mouseover', d => {
d3.select('#tooltip') d3.select('#tooltip')
.style('opacity', 1) .style('opacity', 1)
.style('left', (d3.event.pageX+10) + 'px') .style('left', `${d3.event.pageX + 10}px`)
.style('top', (d3.event.pageY+10) + 'px') .style('top', `${d3.event.pageY + 10}px`)
.text('Flag this node for further analysis'); .text('Flag this node');
}) })
.on('mouseout', function() { .on('mouseout', () => d3.select('#tooltip').style('opacity', 0));
d3.select('#tooltip')
.style('opacity', 0);
});
}; };
function PermanentNodeHighlight(hostnode_uuid) { function NodeHighlight(hostnode_uuid) {
var element = document.getElementById("node_" + hostnode_uuid); let element = document.getElementById(`node_${hostnode_uuid}`);
element.scrollIntoView({behavior: "smooth", block: "center", inline: "nearest"}); element.scrollIntoView({behavior: "smooth", block: "center", inline: "nearest"});
d3.select("#node_" + hostnode_uuid).select('rect').style('fill', 'black'); d3.select(`#node_${hostnode_uuid}`).select('rect').style('fill', 'black');
d3.select("#node_" + hostnode_uuid).select('text').style('fill', 'white'); d3.select(`#node_${hostnode_uuid}`).select('text').style('fill', 'white');
d3.select("#node_" + hostnode_uuid).select("#flag") d3.select(`#node_${hostnode_uuid}`).select("#flag")
.text('❌') .text('❌')
.on('click', function(d) { .on('click', d => UnflagHostNode(d.data.uuid))
UnflagHostNode(d.data.uuid); .on('mouseover', () => {
})
.on('mouseover', d => {
d3.select('#tooltip') d3.select('#tooltip')
.style('opacity', 1) .style('opacity', 1)
.style('left', (d3.event.pageX+10) + 'px') .style('left', `${d3.event.pageX + 10}px`)
.style('top', (d3.event.pageY+10) + 'px') .style('top', `${d3.event.pageY + 10}px`)
.text('Remove flag on this node'); .text('Remove flag on this node');
}) })
.on('mouseout', function() { .on('mouseout', () => d3.select('#tooltip').style('opacity', 0));
d3.select('#tooltip')
.style('opacity', 0);
});
}; };
function icon(key, icon_path, d, icon_size){ function icon_list(relative_x_pos, relative_y_pos, d) {
var iconContent = d3.create("svg") // WARNING: svg is required there, "g" doesn't have getBBox const icon_size = 16;
.attr('class', 'icon'); let icon_options = new Map([
var has_icon = false;
iconContent.datum(d);
iconContent.filter(d => {
if (['cookies_sent', 'cookies_received'].includes(key)) {
return false;
}
if (typeof d.data[key] === 'boolean') {
has_icon = d.data[key];
} else if (typeof d.data[key] === 'number') {
has_icon = d.data[key] > 0;
} else if (d.data[key] instanceof Array) {
has_icon = d.data[key].length > 0;
}
return has_icon;
}).append('image')
.attr("width", icon_size)
.attr("height", icon_size)
.attr("xlink:href", icon_path);
iconContent.filter(d => {
if (['cookies_sent', 'cookies_received'].includes(key)) {
return false;
}
if (typeof d.data[key] === 'boolean') {
return false;
// return d.data[key];
} else if (typeof d.data[key] === 'number') {
d.to_print = d.data[key]
return d.data[key] > 0;
} else if (d.data[key] instanceof Array) {
d.to_print = d.data[key].length
return d.data[key].length > 0;
}
return false;
}).append('text')
.attr("dy", 8)
.style("font-size", "10px")
.attr('x', icon_size + 1)
.text(d => d.to_print);
if (has_icon) {
return iconContent.node();
}
return false;
};
function icon_list(relative_x_pos, relative_y_pos, d, url_view=false) {
var icon_size = 16;
// Put all the icone in one sub svg document
var icons = d3.create("svg") // WARNING: svg is required there, "g" doesn't have getBBox
.attr('x', relative_x_pos)
.attr('y', relative_y_pos)
.datum(d);
icon_options = [
['js', "/static/javascript.png"], ['js', "/static/javascript.png"],
['exe', "/static/exe.png"], ['exe', "/static/exe.png"],
['css', "/static/css.png"], ['css', "/static/css.png"],
@ -304,33 +210,71 @@ function icon_list(relative_x_pos, relative_y_pos, d, url_view=false) {
['unknown_mimetype', "/static/wtf.png"], ['unknown_mimetype', "/static/wtf.png"],
['video', "/static/video.png"], ['video', "/static/video.png"],
['request_cookie', "/static/cookie_read.png"], ['request_cookie', "/static/cookie_read.png"],
['cookies_sent', "/static/cookie_read.png"],
['response_cookie', "/static/cookie_received.png"], ['response_cookie', "/static/cookie_received.png"],
['cookies_received', "/static/cookie_received.png"],
['redirect', "/static/redirect.png"], ['redirect', "/static/redirect.png"],
['redirect_to_nothing', "/static/cookie_in_url.png"] ['redirect_to_nothing', "/static/cookie_in_url.png"]
]; ]);
icon_options.forEach(entry => { // Put all the icone in one sub svg document
bloc = icon(entry[0], entry[1], d, icon_size, url_view); let icons = d3.create("svg")
if (bloc){ .attr('x', relative_x_pos)
icons.append(() => bloc); .attr('y', relative_y_pos);
};
}) icon_options.forEach(function(value, key) {
icons
.datum(d)
.filter(d => {
let has_icon = false;
if (typeof d.data[key] === 'boolean') {
has_icon = d.data[key];
} else if (typeof d.data[key] === 'number') {
has_icon = d.data[key] > 0;
} else if (d.data[key] instanceof Array) {
has_icon = d.data[key].length > 0;
}
return has_icon;
})
.append("svg")
.attr('class', 'icon')
.attr('id', key)
.append('image')
.attr("width", icon_size)
.attr("height", icon_size)
.attr("xlink:href", value);
});
icons.selectAll('.icon')
.datum(d)
.filter(function(d) {
if (typeof d.data[this.id] === 'boolean') {
return false;
} else if (typeof d.data[this.id] === 'number') {
d.to_print = d.data[this.id]
return d.data[this.id] > 0;
} else if (d.data[this.id] instanceof Array) {
d.to_print = d.data[this.id].length
return d.data[this.id].length > 0;
}
return false;
}).append('text')
.attr("dy", 8)
.style("font-size", "10px")
.attr('x', icon_size + 1)
.text(d => d.to_print);
return icons.node(); return icons.node();
} }
function text_entry(relative_x_pos, relative_y_pos, onclick_callback, d) { function text_entry(relative_x_pos, relative_y_pos, onclick_callback, d) {
// Avoid hiding the content after the circle // Avoid hiding the content after the circle
var nodeContent = d3.create("svg") // WARNING: svg is required there, "g" doesn't have getBBox let nodeContent = d3.create("svg") // WARNING: svg is required there, "g" doesn't have getBBox
.attr('height', node_height) .attr('height', node_height)
.attr('x', relative_x_pos) .attr('x', relative_x_pos)
.attr('y', relative_y_pos) .attr('y', relative_y_pos)
.datum(d); .datum(d);
// Add labels for the nodes // Add labels for the nodes
var text_nodes = nodeContent.append("text") let text_nodes = nodeContent.append("text")
.attr('dy', '.9em') .attr('dy', '.9em')
.attr("stroke", "white") .attr("stroke", "white")
.style("font-size", "16px") .style("font-size", "16px")
@ -338,10 +282,17 @@ function text_entry(relative_x_pos, relative_y_pos, onclick_callback, d) {
.style("opacity", .9) .style("opacity", .9)
.attr("clip-path", "url(#textOverlay)") .attr("clip-path", "url(#textOverlay)")
.text(d => { .text(d => {
if (d.data.urls_count) { let to_print;
return d.data.name + ' (' + d.data.urls_count + ')' if (d.data.name.length > 50) {
to_print = `[...] ${d.data.name.substring(d.data.name.length - 50, d.data.name.length)}`;
} else {
to_print = d.data.name
};
if (d.data.urls_count > 1) {
return `${to_print} (${d.data.urls_count})`;
} }
return d.data.name return to_print;
}); });
text_nodes text_nodes
@ -350,14 +301,11 @@ function text_entry(relative_x_pos, relative_y_pos, onclick_callback, d) {
.on('mouseover', d => { .on('mouseover', d => {
d3.select('#tooltip') d3.select('#tooltip')
.style('opacity', 1) .style('opacity', 1)
.style('left', (d3.event.pageX+10) + 'px') .style('left', `${d3.event.pageX + 10}px`)
.style('top', (d3.event.pageY+10) + 'px') .style('top', `${d3.event.pageY + 10}px`)
.text('Open investigation pop-up.'); .text('Open investigation pop-up.');
}) })
.on('mouseout', function() { .on('mouseout', () => d3.select('#tooltip').style('opacity', 0));
d3.select('#tooltip')
.style('opacity', 0);
});
return nodeContent.node(); return nodeContent.node();
} }
@ -365,7 +313,7 @@ function text_entry(relative_x_pos, relative_y_pos, onclick_callback, d) {
function update(root, computed_node_width=0) { function update(root, computed_node_width=0) {
// Current height of the tree (cannot use height because it isn't recomputed when we rename children -> _children) // Current height of the tree (cannot use height because it isn't recomputed when we rename children -> _children)
var max_depth = 1 let max_depth = 1
root.each(d => { root.each(d => {
if (d.children){ if (d.children){
max_depth = d.depth > max_depth ? d.depth : max_depth; max_depth = d.depth > max_depth ? d.depth : max_depth;
@ -375,14 +323,14 @@ function update(root, computed_node_width=0) {
if (computed_node_width != 0) { if (computed_node_width != 0) {
computed_node_width += 30; computed_node_width += 30;
// Re-compute SVG size depending on the generated tree // Re-compute SVG size depending on the generated tree
var newWidth = Math.max((max_depth + 1) * computed_node_width, node_width); let newWidth = Math.max((max_depth + 1) * computed_node_width, node_width);
// Update height // Update height
// node_height is the height of a node, node_height * 25 is the minimum so the root node isn't behind the menu // node_height is the height of a node, node_height * 25 is the minimum so the root node isn't behind the menu
var newHeight = Math.max(root.descendants().reverse().length * node_height, 25 * node_height); let newHeight = Math.max(root.descendants().reverse().length * node_height, 25 * node_height);
tree.size([newHeight, newWidth]) tree.size([newHeight, newWidth])
// Set background based on the computed width and height // Set background based on the computed width and height
var background = main_svg.insert('rect', ':first-child') let background = main_svg.insert('rect', ':first-child')
.attr('y', 0) .attr('y', 0)
// FIXME: + 200 doesn't make much sense... // FIXME: + 200 doesn't make much sense...
.attr('width', newWidth + margin.right + margin.left + 200) .attr('width', newWidth + margin.right + margin.left + 200)
@ -404,14 +352,28 @@ function update(root, computed_node_width=0) {
} }
// Assigns the x and y position for the nodes // Assigns the x and y position for the nodes
var treemap = tree(root); let treemap = tree(root);
// Compute the new tree layout. => Note: Need d.x & d.y // Compute the new tree layout. => Note: Need d.x & d.y
var nodes = treemap.descendants(), let nodes = treemap.descendants(),
links = treemap.descendants().slice(1); links = treemap.descendants().slice(1);
// ****************** Nodes section *************************** // ****************** Nodes section ***************************
// Toggle children on click.
let toggle_children_collapse = (d) => {
if (d.children) {
d._children = d.children;
d.children = null;
}
else {
d.children = d._children;
d._children = null;
}
// Call update on the whole Tree
update(d.ancestors().reverse()[0]);
};
// Update the nodes... // Update the nodes...
const tree_nodes = node_container.selectAll('g.node') const tree_nodes = node_container.selectAll('g.node')
.data(nodes, node => node.data.uuid); .data(nodes, node => node.data.uuid);
@ -419,10 +381,10 @@ function update(root, computed_node_width=0) {
tree_nodes.join( tree_nodes.join(
// Enter any new modes at the parent's previous position. // Enter any new modes at the parent's previous position.
enter => { enter => {
var node_group = enter.append('g') let node_group = enter.append('g')
.attr('class', 'node') .attr('class', 'node')
.attr("id", d => 'node_' + d.data.uuid) .attr("id", d => `node_${d.data.uuid}`)
.attr("transform", "translate(" + root.y0 + "," + root.x0 + ")") .attr("transform", `translate(${root.y0}, ${root.x0})`);
node_group node_group
// Add Circle for the nodes // Add Circle for the nodes
@ -430,9 +392,9 @@ function update(root, computed_node_width=0) {
.attr('class', 'node') .attr('class', 'node')
.attr('r', 1e-6) .attr('r', 1e-6)
.style("fill", d => d._children ? "lightsteelblue" : "#fff") .style("fill", d => d._children ? "lightsteelblue" : "#fff")
.on('click', click); .on('click', toggle_children_collapse);
var node_data = node_group let node_data = node_group
.append('svg') .append('svg')
.attr('class', 'node_data') .attr('class', 'node_data')
.attr('x', 0) .attr('x', 0)
@ -452,29 +414,29 @@ function update(root, computed_node_width=0) {
// Set Hostname text // Set Hostname text
node_data node_data
.append(d => text_entry(15, 5, hostnode_click_popup, d)); // Popup .append(d => text_entry(15, 5, open_hostnode_popup, d)); // Popup
// Set list of icons // Set list of icons
node_data node_data
.append(d => icon_list(17, 35, d)); .append(d => icon_list(17, 35, d));
node_group.select('.node_data').each(function(p, j){ node_group.select('.node_data').each(function(d){
// set position of icons based of their length // set position of icons based of their length
var cur_icon_list_len = 0; let cur_icon_list_len = 0;
d3.select(this).selectAll('.icon').each(function(p, j){ d3.select(this).selectAll('.icon').each(function(){
d3.select(this).attr('x', cur_icon_list_len); d3.select(this).attr('x', cur_icon_list_len);
cur_icon_list_len += d3.select(this).node().getBBox().width; cur_icon_list_len += d3.select(this).node().getBBox().width;
}); });
// Rectangle around the domain name & icons // Rectangle around the domain name & icons
var selected_node_bbox = d3.select(this).node().getBBox(); let selected_node_bbox_init = d3.select(this).node().getBBox();
d3.select(this).select('rect') d3.select(this).select('rect')
.attr('height', selected_node_bbox.height + 15) .attr('height', selected_node_bbox_init.height + 15)
.attr('width', selected_node_bbox.width + 50); .attr('width', selected_node_bbox_init.width + 50);
// Set the width for all the nodes // Set the width for all the nodes
var selected_node_bbox = d3.select(this).node().getBBox(); // Required, as the node width need to include the rectangle let selected_node_bbox = d3.select(this).node().getBBox(); // Required, as the node width need to include the rectangle
node_width = node_width > selected_node_bbox.width ? node_width : selected_node_bbox.width; node_width = node_width > selected_node_bbox.width ? node_width : selected_node_bbox.width;
// Set Flag // Set Flag
@ -485,23 +447,18 @@ function update(root, computed_node_width=0) {
.attr("id", "flag") .attr("id", "flag")
.text("🏁") .text("🏁")
.attr('cursor', 'pointer') .attr('cursor', 'pointer')
.on('click', function(d) { .on('click', d => NodeHighlight(d.data.uuid))
PermanentNodeHighlight(d.data.uuid);
})
.on('mouseover', d => { .on('mouseover', d => {
d3.select('#tooltip') d3.select('#tooltip')
.style('opacity', 1) .style('opacity', 1)
.style('left', (d3.event.pageX+10) + 'px') .style('left', `${d3.event.pageX + 10}px`)
.style('top', (d3.event.pageY+10) + 'px') .style('top', `${d3.event.pageY + 10}px`)
.text('Flag this node for further analysis'); .text('Flag this node');
}) })
.on('mouseout', function() { .on('mouseout', () => d3.select('#tooltip').style('opacity', 0));
d3.select('#tooltip')
.style('opacity', 0);
});
var icon_size = 24; if (d.data.http_content) {
if (p.data.http_content) { const icon_size = 24;
// set lock insecure connection // set lock insecure connection
d3.select(this).append("svg").append('rect') d3.select(this).append("svg").append('rect')
.attr('x', selected_node_bbox.width - 22) .attr('x', selected_node_bbox.width - 22)
@ -510,26 +467,22 @@ function update(root, computed_node_width=0) {
.attr('height', icon_size) .attr('height', icon_size)
.attr('fill', 'white') .attr('fill', 'white')
.attr('stroke', 'black'); .attr('stroke', 'black');
// Source: https://icons.getbootstrap.com/icons/lock/
d3.select(this).append('image') d3.select(this).append('image')
.attr('x', selected_node_bbox.width - 22) .attr('x', selected_node_bbox.width - 22)
.attr('y', selected_node_bbox.height - 13) .attr('y', selected_node_bbox.height - 13)
.attr('id', 'insecure_image') .attr('id', 'insecure_image')
.attr("width", icon_size) .attr("width", icon_size)
.attr("height", icon_size) .attr("height", icon_size)
.attr("title", 'This node contents insecure requests')
.attr("xlink:href", '/static/insecure.svg') .attr("xlink:href", '/static/insecure.svg')
.on('mouseover', d => { .on('mouseover', d => {
d3.select('#tooltip') d3.select('#tooltip')
.style('opacity', 1) .style('opacity', 1)
.style('left', (d3.event.pageX+10) + 'px') .style('left', `${d3.event.pageX + 10}px`)
.style('top', (d3.event.pageY+10) + 'px') .style('top', `${d3.event.pageY + 10}px`)
.text('This node containts insecure requests'); .text('This node containts insecure requests');
}) })
.on('mouseout', function() { .on('mouseout', () => d3.select('#tooltip').style('opacity', 0));
d3.select('#tooltip')
.style('opacity', 0);
});
}; };
}); });
@ -537,9 +490,9 @@ function update(root, computed_node_width=0) {
}, },
update => update, update => update,
exit => exit exit => exit
.transition(t) .transition()
// Remove any exiting nodes // Remove any exiting nodes
.attr("transform", node => "translate(" + node.y0 + "," + node.x0 + ")") .attr("transform", node => `translate(${node.y0}, ${node.x0})`)
// On exit reduce the node circles size to 0 // On exit reduce the node circles size to 0
.attr('r', 1e-6) .attr('r', 1e-6)
// On exit reduce the opacity of text labels // On exit reduce the opacity of text labels
@ -548,7 +501,7 @@ function update(root, computed_node_width=0) {
).call(node => { ).call(node => {
node node
// Transition to the proper position for the node // Transition to the proper position for the node
.attr("transform", node => "translate(" + node.y + "," + node.x + ")") .attr("transform", node => `translate(${node.y}, ${node.x})`)
// Update the node attributes and style // Update the node attributes and style
.select('circle.node') .select('circle.node')
.attr('r', 10) .attr('r', 10)
@ -568,8 +521,15 @@ function update(root, computed_node_width=0) {
// ****************** links section *************************** // ****************** links section ***************************
// Update the links... // Update the links...
const link = node_container.selectAll('path.link') const link = node_container.selectAll('path.link').data(links, d => d.id);
.data(links, d => d.id);
// Creates a curved (diagonal) path from parent to the child nodes
let diagonal = (s, d) => {
return `M ${s.y} ${s.x}
C ${(s.y + d.y) / 2} ${s.x},
${(s.y + d.y) / 2} ${d.x},
${d.y} ${d.x}`
};
link.join( link.join(
enter => enter enter => enter
@ -577,14 +537,20 @@ function update(root, computed_node_width=0) {
.insert('path', "g") .insert('path', "g")
.attr("class", "link") .attr("class", "link")
.attr('d', d => { .attr('d', d => {
var o = {x: d.x0, y: d.y0} let o = {
x: d.x0,
y: d.y0
};
return diagonal(o, o) return diagonal(o, o)
}), }),
update => update, update => update,
exit => exit exit => exit
.call(exit => exit .call(exit => exit
.attr('d', d => { .attr('d', d => {
var o = {x: d.x0, y: d.y0} let o = {
x: d.x0,
y: d.y0
};
return diagonal(o, o) return diagonal(o, o)
}) })
.remove() .remove()
@ -593,31 +559,6 @@ function update(root, computed_node_width=0) {
.attr('d', d => diagonal(d, d.parent)) .attr('d', d => diagonal(d, d.parent))
); );
// Creates a curved (diagonal) path from parent to the child nodes
function diagonal(s, d) {
path = `M ${s.y} ${s.x}
C ${(s.y + d.y) / 2} ${s.x},
${(s.y + d.y) / 2} ${d.x},
${d.y} ${d.x}`
return path
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
}
else {
d.children = d._children;
d._children = null;
}
// Call update on the whole Tree
update(d.ancestors().reverse()[0]);
}
if (computed_node_width === 0) { if (computed_node_width === 0) {
update(root, node_width) update(root, node_width)
} }

View File

@ -16,9 +16,10 @@
{% block scripts %} {% block scripts %}
{{ super() }} {{ super() }}
<script src='{{ url_for('static', filename='datatables.min.js') }}'></script> <script src='{{ url_for('static', filename='datatables.min.js') }}'></script>
<script src='{{ url_for('static', filename='generic.js') }}'></script>
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function () { $(document).ready(() => {
$('#table_other_captures').DataTable( { $('table.table').DataTable( {
"order": [[ 1, "desc" ]], "order": [[ 1, "desc" ]],
"paging": false, "paging": false,
"info": false, "info": false,
@ -29,8 +30,8 @@
], ],
"columnDefs": [{ "columnDefs": [{
"targets": 1, "targets": 1,
"render": function ( data, type, row, meta ) { "render": (data) => {
let date = new Date(data); const date = new Date(data);
return date.getFullYear() + '-' + (date.getMonth() + 1).toString().padStart(2, "0") + '-' + date.getDate().toString().padStart(2, "0") + ' ' + date.toTimeString(); return date.getFullYear() + '-' + (date.getMonth() + 1).toString().padStart(2, "0") + '-' + date.getDate().toString().padStart(2, "0") + ' ' + date.toTimeString();
} }
}] }]
@ -38,44 +39,20 @@
}); });
</script> </script>
<script> <script>
function whereAmI(hostname_uuid) { let whereAmI = (hostnode_uuid) => window.opener.LocateNode(hostnode_uuid);
window.opener.LocateNode(hostname_uuid); let flag = (hostnode_uuid) => window.opener.NodeHighlight(hostnode_uuid);
}; let openTreeInNewTab = (capture_uuid, hostnode_uuid=Null) => window.opener.openTreeInNewTab(capture_uuid, hostnode_uuid);
function flag(hostname_uuid) {
window.opener.PermanentNodeHighlight(hostname_uuid);
};
function openTreeInNewTab(capture_uuid, hostnode_uuid=Null) {
window.opener.openTreeInNewTab(capture_uuid, hostnode_uuid);
}
</script> </script>
<script> <script>
// Source: https://codepen.io/nathanlong/pen/ZpAmjv $(document).ready(() => {
function copyToClipboard(text, el) { // Grab any text in the attribute 'data-copy' and pass it to the copy function
var elOriginalText = el.attr('data-original-title'); $('.js-copy').tooltip();
$('.js-copy').click(function() {
var copyTextArea = document.createElement("textarea"); const text = $(this).attr('data-copy');
copyTextArea.value = text; const el = $(this);
document.body.appendChild(copyTextArea); copyToClipboard(text, el);
copyTextArea.select();
var successful = document.execCommand('copy');
var msg = successful ? 'Copied!' : 'Whoops, not copied!';
el.attr('data-original-title', msg).tooltip('show');
document.body.removeChild(copyTextArea);
el.attr('data-original-title', elOriginalText);
}
$(document).ready(function() {
// Copy to clipboard
// Grab any text in the attribute 'data-copy' and pass it to the copy function
$('.js-copy').tooltip();
$('.js-copy').click(function() {
var text = $(this).attr('data-copy');
var el = $(this);
copyToClipboard(text, el);
});
}); });
});
</script> </script>
{% endblock %} {% endblock %}
@ -92,7 +69,7 @@
{% for url in urls %} {% for url in urls %}
{# URL Display #} {# URL Display #}
<li class="list-group-item"> <li class="list-group-item">
<div class="h3" title={{ url['url_path'] }}> <div class="h3" title={{ url['url_object'].name }}>
{# HTTPs or not #} {# HTTPs or not #}
{% if url['encrypted'] %} {% if url['encrypted'] %}
<img src="/static/secure.svg" title="Encrypted request" width="21" height="21"/> <img src="/static/secure.svg" title="Encrypted request" width="21" height="21"/>

View File

@ -92,7 +92,6 @@
</br> </br>
{{ button_text }} {{ button_text }}
<button type="button" class="btn btn-info" onclick="whereAmI('{{ detail[1] }}')">Locate</button> <button type="button" class="btn btn-info" onclick="whereAmI('{{ detail[1] }}')">Locate</button>
<button type="button" class="btn btn-info" onclick="flag('{{ detail[1] }}')">Flag</button>
</li> </li>
{% endif %} {% endif %}
{% endfor %} {% endfor %}