lookyloo/website/web/static/tree.js

840 lines
33 KiB
JavaScript
Raw Normal View History

2020-08-11 19:17:39 +02:00
"use strict";
2017-09-22 00:26:38 +02:00
// From : https://bl.ocks.org/d3noob/43a860bc0024792f8803bba8ca0d5ecd
// Set the dimensions and margins of the diagram
2020-08-11 19:17:39 +02:00
let margin = {
top: 170,
2020-08-11 19:17:39 +02:00
right: 200,
2020-12-10 19:23:17 +01:00
bottom: 10,
2020-08-11 19:17:39 +02:00
left: 90
};
2017-09-22 00:26:38 +02:00
2021-04-06 23:34:57 +02:00
let menuHeight = document.getElementById('menu_vertical').clientHeight;
2020-12-03 14:15:12 +01:00
let width = 960 - margin.left - margin.right;
let height = menuHeight * 2;
let node_width = 10;
2020-08-11 19:17:39 +02:00
let node_height = 55;
2021-01-20 01:28:54 +01:00
let center_node = null;
2017-10-04 15:13:42 +02:00
2020-08-11 19:17:39 +02:00
let main_svg = d3.select("body").append("svg")
2017-09-25 11:23:32 +02:00
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
// dummy container for tooltip
d3.select('body')
.append('div')
.attr('id', 'tooltip')
.attr('class', 'tooltip')
.attr('style', 'position: absolute; opacity: 0;');
2020-08-11 19:17:39 +02:00
// Define SVGs
let defs = main_svg.append("defs");
2018-06-29 08:03:52 +02:00
2017-09-25 11:23:32 +02:00
// Add background pattern
2020-08-11 19:17:39 +02:00
let pattern = defs.append('pattern')
2017-09-25 11:23:32 +02:00
.attr('id', 'backstripes')
.attr('x', margin.left)
2017-09-29 14:43:40 +02:00
.attr("width", node_width * 2)
.attr("height", height)
2017-09-25 11:23:32 +02:00
.attr('patternUnits', "userSpaceOnUse" )
pattern.append('rect')
2017-09-29 14:43:40 +02:00
.attr('width', node_width)
2017-09-25 11:23:32 +02:00
.attr('height', height)
.attr("fill", "#EEEEEE");
2017-09-22 00:26:38 +02:00
// append the svg object to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
2020-08-11 19:17:39 +02:00
let node_container = main_svg.append("g")
2020-12-03 14:15:12 +01:00
.attr("transform", `translate(${margin.left}, ${margin.top})`);
2017-09-22 00:26:38 +02:00
// Assigns parent, children, height, depth
2020-08-11 19:17:39 +02:00
let root = d3.hierarchy(treeData);
root.x0 = height / 2;
2017-09-22 00:26:38 +02:00
root.y0 = 0;
// declares a tree layout
2020-08-11 19:17:39 +02:00
let tree = d3.tree();
2017-09-22 00:26:38 +02:00
update(root);
if (parent_uuid != null) {
2021-05-28 01:33:17 +02:00
let parent_box_y = root.y - 70;
let parent_box_x = root.x - 150;
let parent_rect = node_container.append('rect')
.attr("rx", 6)
.attr("ry", 6)
2021-05-28 01:33:17 +02:00
.attr("transform", `translate(${parent_box_y}, ${parent_box_x})`)
.style("opacity", "0.5")
.attr("stroke", 'black')
.attr('stroke-opacity', "0.8")
.attr("stroke-width", "2")
.attr("stroke-linecap", "round")
.attr("fill", "white")
let text = node_container
.data([
{
"line1": 'This capture was triggered',
"line2": 'from a previous capture.',
"line3": 'See the parent',
"parent_uuid": parent_uuid
}
])
.append('text')
2021-05-28 01:33:17 +02:00
.attr("dy", 0)
.style("font-size", "12px")
2021-05-28 01:33:17 +02:00
.style('text-align', 'center')
.attr("transform", `translate(${parent_box_y + 3}, ${parent_box_x + 15})`);
text
.append('tspan')
.text(d => d.line1);
text
.append('tspan')
.attr("x", 8)
.attr("dy", 18)
.text(d => d.line2);
text
.append('tspan')
.attr("x", 30)
.attr("dy", 20)
.text(d => d.line3)
.style('fill', '#0000EE')
.attr('cursor', 'pointer')
.on('click', (event, d) => { openTreeInNewTab(d.parent_uuid) } );
2021-05-28 01:33:17 +02:00
parent_rect
.attr('width', text.node().getBBox().width + 6)
.attr('height', text.node().getBBox().height + 10)
let line_arrow = node_container
2021-05-28 01:33:17 +02:00
.append('g');
//.attr("transform", `translate(${root.y}, ${root.x})`);
let line = d3.line()
// Other options: http://bl.ocks.org/d3indepth/raw/b6d4845973089bc1012dec1674d3aff8/
//.curve(d3.curveCardinal)
.curve(d3.curveBundle)
.x(point => point.lx)
.y(point => point.ly);
let line_tip = d3.symbol()
.type(d3.symbolTriangle)
.size(200);
line_arrow
.append("path")
.attr('stroke-opacity', "0.7")
.attr("stroke-width", "2")
.attr("stroke", "black")
.attr("fill", "none")
.data([{
2021-05-28 01:33:17 +02:00
source: {x: 0, y: parent_box_x + parent_rect.node().getBBox().height},
target: {x: 50, y: parent_box_x + parent_rect.node().getBBox().height + 42}
}])
.attr("class", "line")
.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}
])
);
line_arrow
.append("path")
.attr("d", line_tip)
.attr("stroke", 'black')
.attr('stroke-opacity', "0.8")
.style('stroke-width', '1.5')
.attr("fill-opacity", '0')
2021-05-28 01:33:17 +02:00
.attr("transform", `translate(50, ${parent_box_x + parent_rect.node().getBBox().height + 48}) rotate(60)`);
};
function openURLInNewTab(url) {
2020-08-11 19:17:39 +02:00
let win = window.open(url, '_blank');
if (win == null) {
return false;
2020-08-11 19:17:39 +02:00
}
2020-08-07 18:01:06 +02:00
win.focus();
return true;
2020-08-07 18:01:06 +02:00
}
function openTreeInNewTab(capture_uuid, hostnode_uuid=null) {
let url = `/tree/${capture_uuid}`;
if (hostnode_uuid != null) {
url += `/${hostnode_uuid}`;
}
openURLInNewTab(url);
}
2020-09-28 15:28:47 +02:00
function open_hostnode_popup(hostnode_uuid) {
2020-10-09 18:05:04 +02:00
let win = window.open(`/tree/${treeUUID}/host/${hostnode_uuid}`, '_blank', 'width=1024,height=768,left=200,top=100');
2020-08-11 19:17:39 +02:00
if (win == null) {
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.");
}
win.focus();
}
2020-05-18 18:35:20 +02:00
2020-08-11 19:17:39 +02:00
function LocateNode(hostnode_uuid) {
let element = document.getElementById(`node_${hostnode_uuid}`);
2020-08-04 17:27:33 +02:00
element.scrollIntoView({behavior: "smooth", block: "center", inline: "center"});
2020-05-18 18:35:20 +02:00
2020-08-11 19:17:39 +02:00
let line_arrow = d3.select(`#node_${hostnode_uuid}`)
.append('g')
.attr('cursor', 'pointer')
2020-09-28 15:28:47 +02:00
.on('click', (event, d) => { event.currentTarget.remove(); });
2020-08-11 19:17:39 +02:00
let line = d3.line()
// Other options: http://bl.ocks.org/d3indepth/raw/b6d4845973089bc1012dec1674d3aff8/
//.curve(d3.curveCardinal)
.curve(d3.curveBundle)
2020-08-11 19:17:39 +02:00
.x(point => point.lx)
.y(point => point.ly);
2020-08-11 19:17:39 +02:00
let line_tip = d3.symbol()
.type(d3.symbolTriangle)
.size(200);
2020-08-11 19:17:39 +02:00
let path = line_arrow
.append("path")
.attr("stroke-width", "3")
.attr("stroke", "black")
.attr("fill", "none")
2020-08-11 19:17:39 +02:00
.data([{
source: {x: node_width/2, y: -100},
target: {x: node_width/4, y: -node_height/2}
}])
.attr("class", "line")
2020-08-11 19:17:39 +02:00
.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}
])
);
let arrow = line_arrow
.append("path")
.attr("d", line_tip)
.attr("stroke", 'black')
.style('stroke-width', '3')
.attr("fill", 'white')
2020-08-11 19:17:39 +02:00
.attr("transform", `translate(${node_width / 4}, ${-node_height / 1.5}) rotate(60)`);
2020-08-11 19:17:39 +02:00
let glow = () => {
line_arrow.selectAll('path')
.transition().duration(1000) //Set transition
.style('stroke-width', '7')
.style('stroke', 'red')
.transition().duration(1000) //Set transition
.style('stroke-width', '3')
.style('stroke', 'black')
2020-08-11 19:17:39 +02:00
.on("end", () => {
if (++i > 15) {
line_arrow.remove();
} else {
glow();
}
});
};
2020-08-11 19:17:39 +02:00
let i = 0;
glow();
2020-05-18 18:35:20 +02:00
};
function UnbookmarkAllNodes() {
2020-07-28 18:26:07 +02:00
d3.selectAll('.node_data').select('rect').style('fill', 'white');
d3.selectAll('.node_data').select('text').style('fill', 'black');
d3.selectAll('.node_data').select("#bookmark")
2020-07-28 18:26:07 +02:00
.text("🏁")
2020-09-28 15:28:47 +02:00
.on('click', (event, d) => NodeHighlight(d.data.uuid))
.on('mouseover', (event, d) => {
2020-08-06 17:47:39 +02:00
d3.select('#tooltip')
.style('opacity', 1)
2020-09-28 15:28:47 +02:00
.style('left', `${event.pageX + 10}px`)
.style('top', `${event.pageY + 10}px`)
.text('Bookmark this node');
2020-08-06 17:47:39 +02:00
})
2020-09-28 15:28:47 +02:00
.on('mouseout', (event, d) => d3.select('#tooltip').style('opacity', 0));
2020-07-28 18:26:07 +02:00
};
function MarkAsKnown(capture_uuid, hostnode_uuid=null, urlnode_uuid=null) {
let data = {};
if (hostnode_uuid != null) { data['hostnode_uuid'] = hostnode_uuid; };
if (urlnode_uuid != null) { data['urlnode_uuid'] = urlnode_uuid; };
$.post(`/tree/${capture_uuid}/mark_as_legitimate`, data);
};
function UnbookmarkHostNode(hostnode_uuid) {
2020-08-11 19:17:39 +02:00
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("#bookmark")
.text("🏁")
2020-09-28 15:28:47 +02:00
.on('click', (event, d) => NodeHighlight(d.data.uuid))
.on('mouseover', (event, d) => {
2020-08-06 17:47:39 +02:00
d3.select('#tooltip')
.style('opacity', 1)
2020-09-28 15:28:47 +02:00
.style('left', `${event.pageX + 10}px`)
.style('top', `${event.pageY + 10}px`)
.text('Bookmark this node');
2020-08-06 17:47:39 +02:00
})
2020-09-28 15:28:47 +02:00
.on('mouseout', (event, d) => d3.select('#tooltip').style('opacity', 0));
};
2020-08-11 19:17:39 +02:00
function NodeHighlight(hostnode_uuid) {
let element = document.getElementById(`node_${hostnode_uuid}`);
element.scrollIntoView({behavior: "smooth", block: "center", inline: "nearest"});
2020-08-11 19:17:39 +02:00
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("#bookmark")
.text('❌')
.on('click', (event, d) => UnbookmarkHostNode(d.data.uuid))
2020-09-28 15:28:47 +02:00
.on('mouseover', (event, d) => {
2020-08-06 17:47:39 +02:00
d3.select('#tooltip')
.style('opacity', 1)
2020-09-28 15:28:47 +02:00
.style('left', `${event.pageX + 10}px`)
.style('top', `${event.pageY + 10}px`)
.text('Remove bookmark on this node');
2020-08-06 17:47:39 +02:00
})
2020-09-28 15:28:47 +02:00
.on('mouseout', (event, d) => d3.select('#tooltip').style('opacity', 0));
2018-02-01 23:58:26 +01:00
};
2020-08-11 19:17:39 +02:00
function icon_list(relative_x_pos, relative_y_pos, d) {
const icon_size = 16;
2020-09-28 15:28:47 +02:00
const icon_options = new Map([
2021-01-01 21:27:08 +01:00
['js', {path: "/static/javascript.png", tooltip: "URL(s) loading Javascript"}],
['exe', {path: "/static/exe.png", tooltip: "URL(s) loading executables"}],
['css', {path: "/static/css.png", tooltip: "URL(s) loading CSS"}],
['font', {path: "/static/font.png", tooltip: "URL(s) loading fonts"}],
['html', {path: "/static/html.png", tooltip: "URL(s) loading HTML"}],
['json', {path: "/static/json.png", tooltip: "URL(s) loading Json"}],
['iframe', {path: "/static/ifr.png", tooltip: "URL(s) loaded from an Iframe"}],
['image', {path: "/static/img.png", tooltip: "URL(s) loading images"}],
['unknown_mimetype', {path: "/static/wtf.png", tooltip: "URL(s) loading contents of an unknown type"}],
['video', {path: "/static/video.png", tooltip: "URL(s) loading videos"}],
['request_cookie', {path: "/static/cookie_read.png", tooltip: "cookie(s) sent to the server in the request"}],
['response_cookie', {path: "/static/cookie_received.png", tooltip: "cookie(s) received in the response"}],
['redirect', {path: "/static/redirect.png", tooltip: "redirect(s)"}],
['redirect_to_nothing', {path: "/static/cookie_in_url.png", tooltip: "redirect(s) to URL(s) missing in the capture"}]
2020-08-11 19:17:39 +02:00
]);
2019-05-15 18:10:07 +02:00
2020-08-11 19:17:39 +02:00
// Put all the icone in one sub svg document
let icons = d3.create("svg")
.attr('x', relative_x_pos)
2020-08-12 20:09:40 +02:00
.attr('y', relative_y_pos)
.attr('class', 'icons_list');
2020-08-11 19:17:39 +02:00
2021-01-01 21:27:08 +01:00
icon_options.forEach(function(icon_details, key) {
2020-08-12 20:09:40 +02:00
let has_icon = false;
let counter = 0;
if (typeof d.data[key] === 'boolean') {
has_icon = d.data[key];
} else if (typeof d.data[key] === 'number') {
has_icon = d.data[key] > 0;
2021-01-01 21:27:08 +01:00
counter = d.data[key];
2020-08-12 20:09:40 +02:00
} else if (d.data[key] instanceof Array) {
has_icon = d.data[key].length > 0;
2021-01-01 21:27:08 +01:00
counter = d.data[key].length;
2020-08-12 20:09:40 +02:00
};
if (has_icon) {
let icon_group = icons
.append("svg")
.attr('class', 'icon')
.attr("id", `icons_${key}`);
icon_group
.append('image')
.attr("width", icon_size)
.attr("height", icon_size)
2021-01-01 21:27:08 +01:00
.attr("xlink:href", icon_details.path)
.on('mouseover', (event, d) => {
d3.select('#tooltip')
.style('opacity', 1)
.style('left', `${event.pageX + 10}px`)
.style('top', `${event.pageY + 10}px`)
.text(`${counter} ${icon_details.tooltip}`);
})
.on('mouseout', (event, d) => d3.select('#tooltip').style('opacity', 0));
2020-08-12 20:09:40 +02:00
if (counter > 0) {
icon_group
.append('text')
.attr("dy", 8)
.style("font-size", "10px")
.attr('x', icon_size + 1)
.text(counter);
};
};
})
return icons.node();
2018-02-01 01:16:20 +01:00
}
2020-09-28 15:28:47 +02:00
function text_entry(relative_x_pos, relative_y_pos, d) {
2018-02-01 01:16:20 +01:00
// Avoid hiding the content after the circle
2020-08-11 19:17:39 +02:00
let nodeContent = d3.create("svg") // WARNING: svg is required there, "g" doesn't have getBBox
2018-04-05 22:59:45 +02:00
.attr('height', node_height)
2018-02-01 01:16:20 +01:00
.attr('x', relative_x_pos)
.attr('y', relative_y_pos)
2019-05-15 18:10:07 +02:00
.datum(d);
2018-02-01 01:16:20 +01:00
// Add labels for the nodes
nodeContent.append("text")
2018-02-01 01:16:20 +01:00
.attr('dy', '.9em')
.attr("stroke", "white")
.style("font-size", "16px")
.attr("stroke-width", ".2px")
.style("opacity", .9)
2020-08-12 13:59:40 +02:00
.attr('cursor', 'pointer')
2020-09-28 15:28:47 +02:00
.on('click', (event, d) => open_hostnode_popup(d.data.uuid))
.on('mouseover', (event, d) => {
2020-08-12 13:59:40 +02:00
d3.select('#tooltip')
.style('opacity', 1)
2020-09-28 15:28:47 +02:00
.style('left', `${event.pageX + 10}px`)
.style('top', `${event.pageY + 10}px`)
2020-08-12 13:59:40 +02:00
.text('Open investigation pop-up.');
})
2020-09-28 15:28:47 +02:00
.on('mouseout', (event, d) => d3.select('#tooltip').style('opacity', 0))
2019-05-17 16:59:56 +02:00
.text(d => {
2020-08-11 19:17:39 +02:00
let to_print;
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
};
return to_print;
});
return nodeContent.node();
2018-02-01 01:16:20 +01:00
}
// Recursively generate the tree
function update(root, computed_node_width=0) {
2017-09-22 00:26:38 +02:00
// Current height of the tree (cannot use height because it isn't recomputed when we rename children -> _children)
let max_depth = 0
root.each(d => {
if (d.children){
max_depth = d.depth > max_depth ? d.depth : max_depth;
}
});
2017-09-29 14:43:40 +02:00
if (computed_node_width != 0) {
computed_node_width += 30;
// Re-compute SVG size depending on the generated tree
2020-08-11 19:17:39 +02:00
let newWidth = Math.max((max_depth + 1) * computed_node_width, node_width);
// Update height
// node_height is the height of a node, menuHeight * 3 is the minimum so the root node isn't behind the menu
2020-12-10 19:23:17 +01:00
let newHeight = Math.max(root.descendants().reverse().length * node_height, menuHeight * 2.5);
tree.size([newHeight, newWidth])
// Set background based on the computed width and height
2020-08-11 19:17:39 +02:00
let background = main_svg.insert('rect', ':first-child')
.attr('y', 0)
// Note: We want the background width with an extra computed_node_width
// in order to make sure the last node is completely covered
.attr('width', newWidth + (margin.right + margin.left + computed_node_width))
.attr('height', newHeight + margin.top + margin.bottom)
.style('fill', "url(#backstripes)");
2019-05-15 18:10:07 +02:00
// Update size
main_svg
.attr("width", newWidth + (margin.right + margin.left)*2)
.attr("height", newHeight + margin.top + margin.bottom)
// Update pattern
main_svg.selectAll('pattern')
2020-08-12 13:59:40 +02:00
.attr('width', `${computed_node_width * 2}px`)
pattern.selectAll('rect')
2020-08-12 13:59:40 +02:00
.attr('width', `${computed_node_width}px`)
2017-09-29 14:43:40 +02:00
}
2017-09-22 00:26:38 +02:00
// Assigns the x and y position for the nodes
2020-08-11 19:17:39 +02:00
let treemap = tree(root);
2017-09-22 00:26:38 +02:00
// Compute the new tree layout. => Note: Need d.x & d.y
2020-08-11 19:17:39 +02:00
let nodes = treemap.descendants(),
links = treemap.descendants().slice(1);
2017-09-22 00:26:38 +02:00
// ****************** Nodes section ***************************
2020-08-11 19:17:39 +02:00
// Toggle children on click.
2020-09-28 15:28:47 +02:00
let toggle_children_collapse = (event, d) => {
2020-08-11 19:17:39 +02:00
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]);
};
2017-09-22 00:26:38 +02:00
// Update the nodes...
const tree_nodes = node_container.selectAll('g.node')
.data(nodes, node => node.data.uuid);
tree_nodes.join(
// Enter any new modes at the parent's previous position.
enter => {
2020-08-11 19:17:39 +02:00
let node_group = enter.append('g')
.attr('class', 'node')
2020-08-11 19:17:39 +02:00
.attr("id", d => `node_${d.data.uuid}`)
.attr("transform", `translate(${root.y0}, ${root.x0})`);
2019-05-15 18:10:07 +02:00
2020-08-11 19:17:39 +02:00
let node_data = node_group
2019-05-15 18:10:07 +02:00
.append('svg')
.attr('class', 'node_data')
.attr('x', 0)
2019-05-15 18:10:07 +02:00
.attr('y', -30);
node_data.append('rect')
.attr("rx", 6)
.attr("ry", 6)
.attr('x', 0)
2019-05-15 18:10:07 +02:00
.attr('y', 0)
2020-12-10 19:23:17 +01:00
.attr('width', 10)
2019-05-15 18:10:07 +02:00
.style("opacity", "0.5")
.attr("stroke", 'black')
2019-05-15 18:10:07 +02:00
.attr('stroke-opacity', "0.8")
.attr("stroke-width", "2")
2019-05-15 18:10:07 +02:00
.attr("stroke-linecap", "round")
.attr("fill", "white")
2019-05-15 18:10:07 +02:00
// Set Hostname text
2019-05-15 18:10:07 +02:00
node_data
.append(d => text_entry(10, 5, d)); // Popup
// Set list of icons
2019-05-15 18:10:07 +02:00
node_data
.append(d => icon_list(12, 35, d));
2019-05-15 18:10:07 +02:00
2020-08-11 19:17:39 +02:00
node_group.select('.node_data').each(function(d){
2019-05-22 12:27:20 +02:00
// set position of icons based of their length
2020-08-11 19:17:39 +02:00
let cur_icon_list_len = 0;
d3.select(this).selectAll('.icon').each(function(){
2019-05-22 12:27:20 +02:00
d3.select(this).attr('x', cur_icon_list_len);
cur_icon_list_len += d3.select(this).node().getBBox().width;
});
2019-05-15 18:10:07 +02:00
2019-05-22 12:27:20 +02:00
// Rectangle around the domain name & icons
d3.select(this).select('rect')
2020-08-12 13:59:40 +02:00
.attr('height', node_height + 5)
2020-12-31 19:32:08 +01:00
.attr('width', d3.select(this).node().getBBox().width + 60);
// Set the width for all the nodes
2021-01-04 10:39:13 +01:00
// Required, as the node width need to include the rectangle
// Note: removing .select('rect') breaks rendering on firefox but not on chrome.
let selected_node_bbox = d3.select(this).select('rect').node().getBBox();
d.node_width = selected_node_bbox.width;
node_width = node_width > selected_node_bbox.width ? node_width : selected_node_bbox.width;
// Set number of URLs after the hostname
if (d.data.urls_count > 1) {
d3.select(this).append("text")
.attr('x', d => d3.select(this).select('text').node().getBBox().width + 13)
.attr('y', 5)
.attr('dy', '.9em')
.attr("stroke", "white")
.style("font-size", "16px")
.attr("stroke-width", ".2px")
.style("opacity", .9)
.on('mouseover', (event, d) => {
d3.select('#tooltip')
.style('opacity', 1)
.style('left', `${event.pageX + 10}px`)
.style('top', `${event.pageY + 10}px`)
.text(`This node contains ${d.data.urls_count} URLs.`);
})
.on('mouseout', (event, d) => d3.select('#tooltip').style('opacity', 0))
.text(d => {
return `(${d.data.urls_count})`;
});
};
// Set Bookmark
2020-11-29 23:56:42 +01:00
if (enable_bookmark) {
d3.select(this).append("text")
.attr('x', `${selected_node_bbox.width - 12}px`)
.attr('y', '20px')
.style("font-size", "16px")
.attr("id", "bookmark")
.text("🏁")
.attr('cursor', 'pointer')
.on('click', (event, d) => NodeHighlight(d.data.uuid))
.on('mouseover', (event, d) => {
d3.select('#tooltip')
.style('opacity', 1)
.style('left', `${event.pageX + 10}px`)
.style('top', `${event.pageY + 10}px`)
.text('Bookmark this node');
})
.on('mouseout', (event, d) => d3.select('#tooltip').style('opacity', 0));
};
2020-12-10 19:23:17 +01:00
const thumbnail_size = 64;
if (d.data.contains_rendered_urlnode) {
2021-01-20 01:28:54 +01:00
center_node = d.data.uuid;
if (d.data.downloaded_filename) {
d3.select(this).append("svg").append('rect')
.attr('x', selected_node_bbox.width/3)
.attr('y', node_height - 3)
.attr('width', thumbnail_size)
.attr('height', thumbnail_size)
.attr('fill', 'white')
.attr('stroke', 'black');
d3.select(this).append('image')
.attr('x', selected_node_bbox.width/3)
.attr('y', node_height - 3)
.attr('id', 'screenshot_thumbnail')
.attr("width", thumbnail_size)
.attr("height", thumbnail_size)
.attr("xlink:href", '/static/download.svg')
.on('mouseover', (event, d) => {
d3.select('#tooltip')
.style('opacity', 1)
.style('left', `${event.pageX + 10}px`)
.style('top', `${event.pageY + 10}px`)
.text(`Contains the downloaded file (${d.data.downloaded_filename}).`);
})
.on('mouseout', (event, d) => {
d3.select('#tooltip').style('opacity', 0)
});
} else {
d3.select(this).append("svg").append('rect')
.attr('x', selected_node_bbox.width/3)
.attr('y', node_height - 3)
.attr('width', thumbnail_size)
.attr('height', thumbnail_size)
.attr('fill', 'white')
.attr('stroke', 'black');
d3.select(this).append('image')
.attr('x', selected_node_bbox.width/3)
.attr('y', node_height - 3)
.attr('id', 'screenshot_thumbnail')
.attr("width", thumbnail_size)
.attr("height", thumbnail_size)
.attr("xlink:href", `data:image/png;base64,${screenshot_thumbnail}`)
.attr('cursor', 'pointer')
.on('mouseover', (event, d) => {
d3.select('#tooltip')
.style('opacity', 1)
.style('left', `${event.pageX + 10}px`)
.style('top', `${event.pageY + 10}px`)
.text('Contains the URL rendered in the browser.');
})
.on('click', (event, d) => {
$("#screenshotModal").modal('toggle');
})
.on('mouseout', (event, d) => {
d3.select('#tooltip').style('opacity', 0)
});
}
};
2020-12-10 19:23:17 +01:00
const http_icon_size = 24;
if (d.data.http_content) {
// set lock insecure connection
d3.select(this).append("svg").append('rect')
.attr('x', selected_node_bbox.width - 22)
.attr('y', selected_node_bbox.height - 13)
.attr('width', http_icon_size)
.attr('height', http_icon_size)
.attr('fill', 'white')
.attr('stroke', 'black');
d3.select(this).append('image')
.attr('x', selected_node_bbox.width - 22)
.attr('y', selected_node_bbox.height - 13)
.attr('id', 'insecure_image')
.attr("width", http_icon_size)
.attr("height", http_icon_size)
.attr("xlink:href", '/static/insecure.svg')
.on('mouseover', (event, d) => {
d3.select('#tooltip')
.style('opacity', 1)
.style('left', `${event.pageX + 10}px`)
.style('top', `${event.pageY + 10}px`)
.text('This node containts insecure requests');
})
.on('mouseout', (event, d) => d3.select('#tooltip').style('opacity', 0));
};
const context_icon_size = 24;
if (d.data.malicious) {
// set bomb
d3.select(this).append("svg").append('rect')
.attr('x', selected_node_bbox.width - 22 - http_icon_size)
.attr('y', selected_node_bbox.height - 13)
.attr('width', context_icon_size)
.attr('height', context_icon_size)
.attr('fill', 'white')
.attr('stroke', 'black');
d3.select(this).append('image')
.attr('x', selected_node_bbox.width - 22 - http_icon_size)
.attr('y', selected_node_bbox.height - 13)
.attr('id', 'malicious_image')
.attr("width", context_icon_size)
.attr("height", context_icon_size)
.attr("xlink:href", '/static/bomb.svg')
2020-09-28 15:28:47 +02:00
.on('mouseover', (event, d) => {
d3.select('#tooltip')
.style('opacity', 1)
2020-09-28 15:28:47 +02:00
.style('left', `${event.pageX + 10}px`)
.style('top', `${event.pageY + 10}px`)
.text('This node containts known malicious content');
})
2020-09-28 15:28:47 +02:00
.on('mouseout', (event, d) => d3.select('#tooltip').style('opacity', 0));
} else if (d.data.legitimate) {
// set checkmark
d3.select(this).append("svg").append('rect')
.attr('x', selected_node_bbox.width - 22 - http_icon_size)
.attr('y', selected_node_bbox.height - 13)
.attr('width', context_icon_size)
.attr('height', context_icon_size)
.attr('fill', 'white')
.attr('stroke', 'black');
d3.select(this).append('image')
.attr('x', selected_node_bbox.width - 22 - http_icon_size)
.attr('y', selected_node_bbox.height - 13)
.attr('id', 'known_image')
.attr("width", context_icon_size)
.attr("height", context_icon_size)
.attr("xlink:href", '/static/check.svg')
2020-09-28 15:28:47 +02:00
.on('mouseover', (event, d) => {
d3.select('#tooltip')
.style('opacity', 1)
2020-09-28 15:28:47 +02:00
.style('left', `${event.pageX + 10}px`)
.style('top', `${event.pageY + 10}px`)
.text('This node has only known content');
})
2020-09-28 15:28:47 +02:00
.on('mouseout', (event, d) => d3.select('#tooltip').style('opacity', 0));
} else if (d.data.all_empty && !d.data.contains_rendered_urlnode) {
// set empty
d3.select(this).append("svg").append('rect')
.attr('x', selected_node_bbox.width - 22 - http_icon_size)
.attr('y', selected_node_bbox.height - 13)
.attr('width', context_icon_size)
.attr('height', context_icon_size)
.attr('fill', 'white')
.attr('stroke', 'black');
d3.select(this).append('image')
.attr('x', selected_node_bbox.width - 22 - http_icon_size)
.attr('y', selected_node_bbox.height - 13)
.attr('id', 'empty_image')
.attr("width", context_icon_size)
.attr("height", context_icon_size)
.attr("xlink:href", '/static/empty.svg')
2020-09-28 15:28:47 +02:00
.on('mouseover', (event, d) => {
d3.select('#tooltip')
.style('opacity', 1)
2020-09-28 15:28:47 +02:00
.style('left', `${event.pageX + 10}px`)
.style('top', `${event.pageY + 10}px`)
.text('This node has only empty content');
})
2020-09-28 15:28:47 +02:00
.on('mouseout', (event, d) => d3.select('#tooltip').style('opacity', 0));
};
if (d.children || d._children) {
d3.select(this)
// Add Circle for the nodes
.append('circle')
.attr('class', 'node')
.attr('r', 1e-6)
.attr('cx', d => d.node_width)
.attr('cy', d => node_height/2)
.style("fill", d => d._children ? "lightsteelblue" : "#fff")
.on('mouseover', (event, d) => {
if (d.children || d._children) {
d3.select('#tooltip')
.style('opacity', 1)
.style('left', `${event.pageX + 10}px`)
.style('top', `${event.pageY + 10}px`)
.text(d.children ? 'Collapse the URLs loaded by this node.' : 'Expand the URLs loaded by this node.');
};
}
)
.on('mouseout', (event, d) => {
if (d.children || d._children) {
d3.select('#tooltip').style('opacity', 0)
};
}
)
.on('click', (event, d) => {
if (d.children || d._children) {
toggle_children_collapse(event, d)
};
}
);
};
});
return node_group;
},
2020-08-12 20:09:40 +02:00
update => update,
exit => exit
2020-08-11 19:17:39 +02:00
.transition()
2019-05-17 16:59:56 +02:00
// Remove any exiting nodes
2020-08-11 19:17:39 +02:00
.attr("transform", node => `translate(${node.y0}, ${node.x0})`)
2019-05-17 16:59:56 +02:00
// On exit reduce the node circles size to 0
.attr('r', 1e-6)
// On exit reduce the opacity of text labels
.style('fill-opacity', 1e-6)
.remove()
).call(node => {
2019-05-17 16:59:56 +02:00
node
// Transition to the proper position for the node
2020-08-11 19:17:39 +02:00
.attr("transform", node => `translate(${node.y}, ${node.x})`)
2019-05-17 16:59:56 +02:00
// Update the node attributes and style
.select('circle.node')
.attr('r', 10)
.style("fill", node => node._children ? "lightsteelblue" : "#fff")
.attr('cursor', (d) => {
if (d.children || d._children) {
return 'pointer';
}
});
2019-05-15 18:10:07 +02:00
2017-10-04 15:13:42 +02:00
});
2017-09-22 00:26:38 +02:00
nodes.forEach(d => {
// Store the old positions for transition.
d.x0 = d.x;
d.y0 = d.y;
2017-09-29 14:43:40 +02:00
});
2018-02-01 01:16:20 +01:00
2017-09-22 00:26:38 +02:00
// ****************** links section ***************************
// Update the links...
2020-08-11 19:17:39 +02:00
const link = node_container.selectAll('path.link').data(links, d => d.id);
// Creates a curved (diagonal) path from parent to the child nodes
let diagonal = d3.linkHorizontal()
.source(d => {return [d.y, d.x]})
.target(d => {return [d.parent.y + d.parent.node_width, d.parent.x]});
link.join(
enter => enter
// Enter any new links at the parent's previous position.
.insert('path', "g")
.attr("class", "link")
.attr('d', diagonal),
update => update,
exit => exit.call(exit => exit.attr('d', diagonal).remove())
).call(link => link.attr('d', diagonal));
2017-09-22 00:26:38 +02:00
if (computed_node_width === 0) {
update(root, node_width)
2017-09-22 00:26:38 +02:00
}
}