зеркало из https://github.com/mozilla/MozDef.git
419 строки
14 KiB
HTML
419 строки
14 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<link rel="stylesheet" href="/stylesheets/style.css">
|
|
<style>
|
|
.iso.face.bottom {
|
|
fill: brown;
|
|
}
|
|
|
|
.iso.outline {
|
|
stroke: #333;
|
|
fill: none;
|
|
vector-effect: non-scaling-stroke;
|
|
}
|
|
|
|
.vis:hover .pipedon:not(:hover) * {
|
|
opacity: 0.6;
|
|
}
|
|
|
|
.label {
|
|
pointer-events: fill;
|
|
text-anchor: middle;
|
|
font-family: Impact;
|
|
}
|
|
|
|
</style>
|
|
<!--
|
|
load up the base libraries.
|
|
--require.js
|
|
-->
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.22/require.min.js"></script>
|
|
|
|
</head>
|
|
<body>
|
|
<h1>Risk Heatmap</h1>
|
|
<svg width="1200px" height="1000px"></svg>
|
|
|
|
</body>
|
|
<script>
|
|
//load up the remaining libraries for d3kit and underscore to help munge the data
|
|
requirejs.config({
|
|
appDir: ".",
|
|
baseUrl: "js",
|
|
paths: {
|
|
'underscore': '//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min',
|
|
'd3': '//cdnjs.cloudflare.com/ajax/libs/d3/3.5.12/d3.min',
|
|
'elasticsearch': '//cdnjs.cloudflare.com/ajax/libs/elasticsearch/10.0.1/elasticsearch.min'
|
|
},
|
|
shim: {
|
|
/* Set dependencies */
|
|
'd3' : ['underscore','elasticsearch']
|
|
}
|
|
});
|
|
|
|
require(['d3'], function(d3) {
|
|
var color, correct_x, correct_y, data, enter_labels, enter_labels_g, enter_pipedons, height, iso_layout, isometric, parallelepipedon, path_generator, pipedons, svg, treemap, vis, width, zoom, zoomable_layer;
|
|
var esresults=[];
|
|
var riskLevels = [
|
|
{name: 'maximum', color: '#0078BD'},
|
|
{name: 'high', color: '#0078BD'},
|
|
{name: 'medium', color: '#00C2E0'},
|
|
{name: 'low', color: '#51E898'},
|
|
{name: 'none', color: '#4D4D4D'},
|
|
];
|
|
|
|
|
|
// elastic search setup and query
|
|
var client = new elasticsearch.Client({
|
|
host: 'mozdefqa2.private.scl3.mozilla.com:9200',
|
|
log: 'error'
|
|
});
|
|
|
|
var searchParams = {
|
|
index: 'rra',
|
|
size: 200,
|
|
body: {
|
|
query: {
|
|
filtered: {
|
|
filter: {
|
|
// only return documents that are
|
|
// rra data
|
|
or: [
|
|
{
|
|
term: { category: "rra_data" }
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
var searchParams = {
|
|
index: 'rra',
|
|
size: 200,
|
|
body: {
|
|
query: {
|
|
bool: {
|
|
must: [
|
|
{ match: { category: "rra_data" }},
|
|
{ range: { utctimestamp: { gte: "2016-01-01" }}}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
client.search(searchParams, function (err, res) {
|
|
if (err) {
|
|
// handle error
|
|
throw err;
|
|
}
|
|
|
|
esresults= res.hits.hits
|
|
window.esresults=esresults;
|
|
console.log('results received');
|
|
|
|
//utility function
|
|
// given an object, recurse it and
|
|
// return a list of its key/value pairs
|
|
listFlatten = function(x, result, prefix) {
|
|
if(_.isObject(x)) {
|
|
_.each(x, function(v, k) {
|
|
listFlatten(v, result, k)
|
|
})
|
|
} else {
|
|
result.push({key:prefix,value: x})
|
|
}
|
|
return result
|
|
}
|
|
|
|
svg = d3.select('svg');
|
|
width = svg.node().getBoundingClientRect().width;
|
|
height = svg.node().getBoundingClientRect().height;
|
|
zoomable_layer = svg.append('g');
|
|
zoom = d3.behavior.zoom().scaleExtent([1, 10]).on('zoom', function() {
|
|
return zoomable_layer.attr({
|
|
transform: "translate(" + (zoom.translate()) + ")scale(" + (zoom.scale()) + ")"
|
|
});
|
|
});
|
|
|
|
svg.call(zoom);
|
|
vis = zoomable_layer.append('g').attr({
|
|
"class": 'vis',
|
|
transform: "translate(" + (width / 2) + "," + (height / 3) + ")"
|
|
});
|
|
|
|
isometric = function(_3d_p) {
|
|
return [-Math.sqrt(3) / 2 * _3d_p[0] + Math.sqrt(3) / 2 * _3d_p[1], +0.5 * _3d_p[0] + 0.5 * _3d_p[1] - _3d_p[2]];
|
|
};
|
|
|
|
parallelepipedon = function(d) {
|
|
var fb, ft, mlb, mlt, mrb, mrt, nb, nt;
|
|
if (!(d.x != null)) {
|
|
d.x = 0;
|
|
}
|
|
if (!(d.y != null)) {
|
|
d.y = 0;
|
|
}
|
|
if (!(d.h != null)) {
|
|
d.h = 0;
|
|
}
|
|
if (!(d.dx != null)) {
|
|
d.dx = 10;
|
|
}
|
|
if (!(d.dy != null)) {
|
|
d.dy = 10;
|
|
}
|
|
if (!(d.dh != null)) {
|
|
d.dh = 10;
|
|
}
|
|
fb = isometric([d.x, d.y, d.h], mlb = isometric([d.x + d.dx, d.y, d.h], nb = isometric([d.x + d.dx, d.y + d.dy, d.h], mrb = isometric([d.x, d.y + d.dy, d.h], ft = isometric([d.x, d.y, d.h + d.dh], mlt = isometric([d.x + d.dx, d.y, d.h + d.dh], nt = isometric([d.x + d.dx, d.y + d.dy, d.h + d.dh], mrt = isometric([d.x, d.y + d.dy, d.h + d.dh]))))))));
|
|
d.iso = {
|
|
face_bottom: [fb, mrb, nb, mlb],
|
|
face_left: [mlb, mlt, nt, nb],
|
|
face_right: [nt, mrt, mrb, nb],
|
|
face_top: [ft, mrt, nt, mlt],
|
|
outline: [ft, mrt, mrb, nb, mlb, mlt],
|
|
fb: fb,
|
|
mlb: mlb,
|
|
nb: nb,
|
|
mrb: mrb,
|
|
ft: ft,
|
|
mlt: mlt,
|
|
nt: nt,
|
|
mrt: mrt
|
|
};
|
|
return d;
|
|
};
|
|
|
|
iso_layout = function(data, shape, scale) {
|
|
if (!(scale != null)) {
|
|
scale = 1;
|
|
}
|
|
data.forEach(function(d) {
|
|
return shape(d, scale);
|
|
});
|
|
return data.sort(function(a, b) {
|
|
return b.dh - a.dh;
|
|
});
|
|
};
|
|
|
|
path_generator = function(d) {
|
|
return 'M' + d.map(function(p) {
|
|
return p.join(' ');
|
|
}).join('L') + 'z';
|
|
};
|
|
|
|
treemap = d3.layout.treemap().size([400, 400]).value(function(d) {
|
|
return d.area;
|
|
}).sort(function(a, b) {
|
|
return a.dh - b.dh;
|
|
}).ratio(2).round(false);
|
|
|
|
color = d3.scale.category20c();
|
|
|
|
correct_x = d3.scale.linear().domain([0, width]).range([0, width * 1.05]);
|
|
|
|
correct_y = d3.scale.linear().domain([0, height]).range([0, height * 3 / 4]);
|
|
|
|
|
|
//data = d3.range(30).map(function() {
|
|
// return {
|
|
// word: 'something',
|
|
// area: Math.random(),
|
|
// dh: Math.random() * 150
|
|
// };
|
|
//});
|
|
|
|
data = _.map(esresults,function(esrecord) {
|
|
//calc the risk value for this record
|
|
//by summarizing the risks into counts of LOW/MEDIUM/HIGH/MAXIMUM
|
|
//and scoring the results.
|
|
var risks=listFlatten(esrecord._source.details.risk,[])
|
|
var riskvalues=_.filter(risks,function(risk){
|
|
var keys= ['impact','probability']
|
|
if ( keys.indexOf(risk.key) >-1){
|
|
return risk
|
|
}
|
|
})
|
|
var counts=_.countBy(riskvalues,function(r){
|
|
return r.value
|
|
})
|
|
//calc the total risk score
|
|
var riskscore=0;
|
|
if ( _.has(counts,"MAXIMUM")){
|
|
riskscore += counts.MAXIMUM * 20
|
|
}
|
|
if (_.has(counts,"HIGH")){
|
|
riskscore += counts.HIGH * 15
|
|
}
|
|
if (_.has(counts,"MEDIUM")){
|
|
riskscore += counts.MEDIUM * 10
|
|
}
|
|
if (_.has(counts,"LOW")){
|
|
riskscore += counts.LOW * 5
|
|
}
|
|
return {
|
|
word: esrecord._source.details.metadata.service,
|
|
area: _.size(_.flatten(_.values(esrecord._source.details.data))), //flatten the array of data
|
|
//dh: Math.random() * 150
|
|
dh: riskscore,
|
|
record: esrecord,
|
|
score: riskscore,
|
|
counts:counts
|
|
|
|
};
|
|
});
|
|
|
|
data = treemap.nodes({
|
|
children: data
|
|
}).filter(function(n) {
|
|
return n.depth === 1;
|
|
});
|
|
|
|
iso_layout(data, parallelepipedon);
|
|
|
|
data.forEach(function(d, i) {
|
|
return d.template_color = d3.hcl(color(i));
|
|
});
|
|
|
|
pipedons = vis.selectAll('.pipedon').data(data);
|
|
|
|
enter_pipedons = pipedons.enter().append('g').attr({
|
|
"class": 'pipedon'
|
|
});
|
|
|
|
enter_pipedons.append('path').attr({
|
|
"class": 'iso face bottom',
|
|
d: function(d) {
|
|
return path_generator(d.iso.face_bottom);
|
|
}
|
|
});
|
|
|
|
enter_pipedons.append('path').attr({
|
|
"class": 'iso face left',
|
|
d: function(d) {
|
|
return path_generator(d.iso.face_left);
|
|
},
|
|
fill: function(d) {
|
|
return d.template_color;
|
|
}
|
|
});
|
|
|
|
enter_pipedons.append('path').attr({
|
|
"class": 'iso face right',
|
|
d: function(d) {
|
|
return path_generator(d.iso.face_right);
|
|
},
|
|
fill: function(d) {
|
|
return d3.hcl(d.template_color.h, d.template_color.c, d.template_color.l - 12);
|
|
}
|
|
});
|
|
|
|
enter_pipedons.append('path').attr({
|
|
"class": 'iso face top',
|
|
d: function(d) {
|
|
return path_generator(d.iso.face_top);
|
|
},
|
|
fill: function(d) {
|
|
return d3.hcl(d.template_color.h, d.template_color.c, d.template_color.l + 12);
|
|
}
|
|
});
|
|
|
|
enter_labels_g = enter_pipedons.append('g');
|
|
|
|
enter_labels = enter_labels_g.append('svg').attr({
|
|
"class": 'label'
|
|
});
|
|
|
|
enter_labels.append('text').text(function(d) {
|
|
return d.word.toUpperCase();
|
|
}).attr({
|
|
dy: '.35em'
|
|
}).each(function(node) {
|
|
var bbox, bbox_aspect, node_bbox, node_bbox_aspect, rotate;
|
|
bbox = this.getBBox();
|
|
bbox_aspect = bbox.width / bbox.height;
|
|
node_bbox = {
|
|
width: node.dx,
|
|
height: node.dy
|
|
};
|
|
node_bbox_aspect = node_bbox.width / node_bbox.height;
|
|
rotate = bbox_aspect >= 1 && node_bbox_aspect < 1 || bbox_aspect < 1 && node_bbox_aspect >= 1;
|
|
node.label_bbox = {
|
|
x: bbox.x + (bbox.width - correct_x(bbox.width)) / 2,
|
|
y: bbox.y + (bbox.height - correct_y(bbox.height)) / 2,
|
|
width: correct_x(bbox.width),
|
|
height: correct_y(bbox.height)
|
|
};
|
|
if (rotate) {
|
|
node.label_bbox = {
|
|
x: node.label_bbox.y,
|
|
y: node.label_bbox.x,
|
|
width: node.label_bbox.height,
|
|
height: node.label_bbox.width
|
|
};
|
|
return d3.select(this).attr('transform', 'rotate(90) translate(0,1)');
|
|
}
|
|
});
|
|
|
|
//add a tooltip
|
|
enter_labels.append("svg:title")
|
|
.text(function(d){
|
|
return d.word + ' ' + _.pairs(d.counts).toString() + ' total score: ' + d.score});
|
|
|
|
enter_labels.each(function(d) {
|
|
d.iso_x = isometric([d.x + d.dx / 2, d.y + d.dy / 2, d.h + d.dh])[0] - d.dx / 2;
|
|
return d.iso_y = isometric([d.x + d.dx / 2, d.y + d.dy / 2, d.h + d.dh])[1] - d.dy / 2;
|
|
});
|
|
|
|
enter_labels.attr({
|
|
x: function(d) {
|
|
return d.iso_x;
|
|
},
|
|
y: function(d) {
|
|
return d.iso_y;
|
|
},
|
|
width: function(node) {
|
|
return node.dx;
|
|
},
|
|
height: function(node) {
|
|
return node.dy;
|
|
},
|
|
viewBox: function(node) {
|
|
return "" + node.label_bbox.x + " " + node.label_bbox.y + " " + node.label_bbox.width + " " + node.label_bbox.height;
|
|
},
|
|
preserveAspectRatio: 'none',
|
|
fill: function(d) {
|
|
return d3.hcl(d.template_color.h, d.template_color.c, d.template_color.l - 12);
|
|
}
|
|
});
|
|
|
|
enter_labels_g.attr({
|
|
transform: function(d) {
|
|
return "translate(" + (d.iso_x + d.dx / 2) + "," + (d.iso_y + d.dy / 2) + ") scale(1, " + (1 / Math.sqrt(3)) + ") rotate(-45) translate(" + (-(d.iso_x + d.dx / 2)) + "," + (-(d.iso_y + d.dy / 2)) + ")";
|
|
}
|
|
});
|
|
|
|
enter_pipedons.append('path').attr({
|
|
"class": 'iso outline',
|
|
d: function(d) {
|
|
return path_generator(d.iso.outline);
|
|
}
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
//publish objects to the console for debugging
|
|
//window.roadmapData = roadmapData;
|
|
window.data = data;
|
|
//window.chart=chart;
|
|
});
|
|
|
|
</script>
|
|
</html>
|