5
0
mirror of https://github.com/cwinfo/yggdrasil-map synced 2024-11-22 12:50:27 +00:00
yggdrasil-map/web/index.html
2014-03-19 11:12:47 +02:00

663 lines
14 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>fc00::/8</title>
<script src="jquery-2.0.3.min.js"></script>
<script src="jquery.autocomplete.min.js"></script>
<link href='http://fonts.googleapis.com/css?family=Source+Sans+Pro|Droid+Sans|Duru+Sans|Quattrocento+Sans|Open+Sans' rel='stylesheet' type='text/css'>
<style type="text/css">
@font-face {
font-family: 'Inconsolata';
src: url("Inconsolata.otf");
}
* {
margin: 0;
padding: 0;
}
html, body {
font-family: 'Source Sans Pro';
background: #F5F5F5;
}
#header {
background: #FFF;
height: 48px;
line-height: 48px;
/*box-shadow: 0 5px 3px rgba(0, 0, 0, 0.1);*/
z-index: 0;
position: absolute;
top: 0;
left: 0;
right: 0;
}
h1 {
font-family: 'Inconsolata';
font-size: 32px;
float: left;
padding: 0 40px;
font-weight: 100;
color: #333;
}
small {
font-size: 16px;
}
.grey {
color: #999;
/*font-size: 16px;*/
/*vertical-align: middle;*/
}
ul {
list-style-type: none;
height: 100%;
}
li {
float: left;
height: 100%;
}
#header a {
color: #777;
padding: 0 20px;
font-family: 'Source Sans Pro';
font-size: 14px;
text-decoration: none;
height: 100%;
display: block;
}
#header a:hover {
background: #EEE;
}
/*#map-wrapper {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: -1;
}*/
#general-info {
position: absolute;
bottom: 0;
left: 0;
font-size: 10px;
padding: 5px;
line-height: 150%;
z-index: 999;
}
#sidebar {
padding: 20px 20px;
background: rgba(220, 220, 220, 0.8);
position: absolute;
top: 0;
right: 0;
/*bottom: 0;*/
min-width: 200px;
z-index: 999;
/*overflow-y: scroll;*/
font-size: 12px;
}
#search-wrapper {
width: 100%;
margin-bottom: 20px;
/*position: absolute;*/
/*top: 0;*/
/*right: 10px;*/
/*z-index: 5;*/
}
#search-box {
width: 100%;
padding: 5px;
outline: none;
border: none;
/*border: 1px solid #CCC;*/
margin: -5px;
font-size: inherit;
}
a {
color: #333;
text-decoration: none;
}
a:hover {
color: #AAA;
}
h2 {
text-align: center;
margin-bottom: 5px;
color: #29BBFF;
}
#node-info table {
width: 100%;
}
#node-info td + td {
text-align: right;
}
#node-info strong {
color: #29BBFF;
letter-spacing: 1px;
}
.tt {
font-family: 'Source Code Pro', Consolas, monospace;
font-size: 10px;
}
.autocomplete-suggestions {
font-family: 'Source Code Pro', Consolas, monospace;
font-size: 12px;
border: 1px solid #FFF;
background: #FFF;
overflow: auto;
color: #555;
}
.autocomplete-suggestion {
padding: 2px 5px;
white-space: nowrap;
overflow: hidden;
}
.autocomplete-selected { background: #7FD6FF; }
.autocomplete-suggestions strong {
color: #000000;
}
#canvas-wrapper {
position: absolute;
top: 48px;
left: 0;
right: 0;
bottom: 0;
}
#map {
position: absolute;
width:100%;
height:100%;
}
</style>
</head>
<body>
<div id="header">
<h1>fc00<span class="grey">::/8</span></h1>
<ul>
<li><a href="#">Network</a></li>
<li><a href="#">World map</a></li>
<li><a href="#">Tools</a></li>
</ul>
</div>
<!-- <div id="search-wrapper">
<input id="search-box" class="tt" type="text" placeholder="Search nodes...">
</div> -->
<!-- <div id="map-wrapper"> -->
<div id="general-info">
<!-- <br> -->
Nodes: <span id="number-of-nodes">-</span><br>
Links: <span id="number-of-connections">-</span><br>
Updated <span id="update-time">-</span><br>
</div>
<div id="sidebar">
<div id="search-wrapper">
<input id="search-box" class="tt" type="text" placeholder="Search nodes...">
</div>
<div id="node-info">
<span class="tt" style="opacity: 0">0000:0000:0000:0000:0000:0000:0000:0000</span>
</div>
</div>
<div id="canvas-wrapper">
<canvas id="map"></canvas>
</div>
<!-- </div> -->
<script>
"use strict";
var nodes = [];
var edges = [];
var canvas = null;
var ctx = null;
var mapOffset = {x: 0, y: 0};
var zoom = 1.0;
function changeHash(hash) {
window.location.replace(('' + window.location).split('#')[0] + '#' + hash);
}
function updateCanvasSize() {
$(canvas).attr({height: $(canvas).height(), width: $(canvas).width()});
ctx.translate(mapOffset.x, mapOffset.y);
}
function drawCircle(ctx, x, y, radius, color) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI*2, true);
ctx.fill();
}
function drawLine(ctx, x1, y1, x2, y2, color) {
ctx.strokeStyle = color;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.closePath();
ctx.stroke();
}
function drawText(ctx, x, y, text, color, font) {
// ctx.save();
// ctx.translate(x, y);
// ctx.rotate(Math.PI/4);
ctx.fillStyle = color;
ctx.font = font;
ctx.textAlign = 'center';
ctx.fillText(text, x, y);
// ctx.restore();
}
function drawNetwork() {
ctx.save();
ctx.resetTransform();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.restore();
// Draw edges
for (var i = 0; i < edges.length; ++i) {
var edge = edges[i];
var highlight = edge.sourceNode.hover || edge.targetNode.hover;
var color = highlight ? 'rgba(0, 0, 0, 0.5)' : 'rgba(0, 0, 0, 0.15)';
drawLine(ctx,
edge.sourceNode.x, edge.sourceNode.y,
edge.targetNode.x, edge.targetNode.y,
color);
}
// Draw nodes
for (var i = 0; i < nodes.length; ++i) {
var node = nodes[i];
drawCircle(ctx, node.x, node.y, node.radius, node.color);
}
// Draw labels
for (var i = 0; i < nodes.length; ++i) {
var node = nodes[i];
if (node.radius > 2 || node.selected || node.hover) {
var fontSize = 4 + node.radius * 0.4;
drawText(ctx, node.x, node.y - node.radius - 1,
node.label, node.textColor, fontSize + 'pt "ubuntu mono"');
}
}
}
function getNodeAt(x, y) {
x -= mapOffset.x;
y -= mapOffset.y;
for (var i = nodes.length - 1; i >= 0; --i) {
var node = nodes[i];
var distPow2 = (node.x - x) * (node.x - x) + (node.y - y) * (node.y - y);
if (distPow2 <= node.radius * node.radius) {
return node;
}
}
return null;
}
function searchNode(id) {
for (var i = 0; i < nodes.length; ++i) {
if (nodes[i].id == id)
return nodes[i];
}
return null;
}
function clearNodes() {
changeHash('');
$('#node-info').html('');
for (var i = 0; i < nodes.length; ++i) {
var node = nodes[i];
node.depth = 0xFFFF;
node.color = node.originalColor;
node.textColor = node.color;
node.selected = false;
}
}
function selectNode(node, redraw) {
clearNodes();
changeHash(node.id);
node.selected = true;
showNodeInfo(node);
markPeers(node, 0);
if (redraw)
drawNetwork();
}
function markPeers(node, depth) {
node.depth = depth;
// var colors = ['#000000', '#333333', '#555555', '#777777', '#999999', '#BBBBBB', '#DDDDDD'];
// var colors = ['#000000', '#29BBFF', '#09E844', '#FFBD0F', '#FF5E14', '#FF3C14', '#FF7357', '#FF9782', '#FFC8BD', '#FFE6E0'];
var colors = ['#000000', '#096EE8', '#09E8B8', '#36E809', '#ADE809', '#E8B809', '#E87509', '#E83A09'];
var txtCol = ['#000000', '#032247', '#034537', '#0E3D02', '#354703', '#403203', '#3D1F02', '#3B0E02'];
// var colors = ['#000000', '#064F8F', '#068F81', '#068F38', '#218F06', '#6F8F06', '#8F7806', '#8F5106'];
// var colors = ['#FFFFFF', '#29BBFF', '#17FF54', '#FFBD0F', '#FF3C14', '#590409'];
node.color = (depth >= colors.length) ? '#FFFFFF' : colors[depth];
node.textColor = (depth >= txtCol.length) ? '#FFFFFF' : txtCol[depth];
for (var i = 0; i < node.peers.length; ++i) {
var n = node.peers[i];
if (n.depth > depth + 1)
markPeers(n, depth + 1);
}
}
function showNodeInfo(node) {
var ip_peers = [];
var dns_peers = [];
for (var i = 0; i < node.peers.length; ++i) {
var n = node.peers[i];
if (/^[0-9A-F]{4}$/i.test(n.label))
ip_peers.push(n);
else
dns_peers.push(n);
}
var label_compare = function(a, b) {
return a.label.localeCompare(b.label);
}
dns_peers.sort(label_compare);
ip_peers.sort(label_compare);
var peers = dns_peers.concat(ip_peers);
var html =
'<h2>' + node.label + '</h2>' +
'<span class="tt">' + node.id + '</span><br>' +
'<br>' +
'<strong>Version:</strong> ' + node.version + '<br>' +
'<strong>Location:</strong> Helsinki, Finland<br>' +
'<strong>Peers:</strong> ' + node.peers.length + '<br>' +
'<table>' +
// '<tr><td></td><td><strong>Their peers #</strong></td></tr>' +
peers.map(function (n) {
return '<tr>' +
'<td><a href="#' + n.id + '" class="tt">' + n.label + '</a></td>' +
'<td>' + n.peers.length + '</td></tr>';
}).join('') +
'</table>';
$('#node-info').html(html);
}
function mousePos(e) {
var rect = canvas.getBoundingClientRect();
return {x: e.clientX - rect.left, y: e.clientY - rect.top};
}
$(document).ready(function() {
canvas = document.getElementById('map');
ctx = canvas.getContext('2d');
updateCanvasSize();
jQuery.getJSON('graph.json', function(data) {
nodes = data.nodes;
edges = data.edges;
// Calculate node radiuses
for (var i = 0; i < nodes.length; ++i) {
var node = nodes[i];
node.x = node.x * 1.2;
node.y = node.y * 1.2;
node.radius = 4 + node.size * 10;
node.hover = false;
node.selected = false;
node.edges = [];
node.peers = [];
node.depth = 0xFFFF;
// node.color = '#000';
node.originalColor = node.color;
node.textColor = node.color;
}
// Find node references for edges
for (var i = 0; i < edges.length; ++i) {
var edge = edges[i];
for (var n = 0; n < nodes.length; ++n) {
if (nodes[n].id == edge.sourceID) {
edge.sourceNode = nodes[n];
// edge.sourceNode.edges.append(edge);
}
else if (nodes[n].id == edge.targetID)
edge.targetNode = nodes[n];
}
edge.sourceNode.edges.push(edge);
edge.targetNode.edges.push(edge);
edge.sourceNode.peers.push(edge.targetNode);
edge.targetNode.peers.push(edge.sourceNode);
}
// Set update time
var delta = Math.round(new Date().getTime() / 1000) - data.created;
var min = Math.floor(delta / 60);
var sec = delta % 60;
$('#update-time').text(min + ' min, ' + sec + ' s ago');
// Set stats
$('#number-of-nodes').text(nodes.length);
$('#number-of-connections').text(edges.length);
if (window.location.hash) {
var id = window.location.hash.substring(1);
var node = searchNode(id);
if (node) selectNode(node, false);
}
drawNetwork();
$(window).resize(function() {
updateCanvasSize();
drawNetwork();
});
// Initialize search
var searchArray = [];
for (var i = 0; i < nodes.length; ++i) {
var node = nodes[i];
searchArray.push({
value: node.label,
data: node
});
searchArray.push({
value: node.id,
data: node
});
}
$('#search-box').autocomplete({
lookup: searchArray,
autoSelectFirst: true,
lookupLimit: 7,
onSelect: function(suggestion) {
selectNode(suggestion.data, true);
}
});
$('#search-box').keypress(function(e) {
if (e.which == 13) {
selectNode(searchNode($('#search-box').val()), true);
}
});
$(document).on('click', '#node-info a', function(e) {
var id = e.target.hash.substring(1);
selectNode(searchNode(id), true);
});
});
var mouseDownPos = null;
var mouseLastPos = null;
var mouseDownNode = null;
var mouseHoverNode = null;
$(canvas).mousemove(function(e) {
var mouse = mousePos(e);
// Dragging
if (mouseDownPos != null) {
$('body').css('cursor', 'move');
var dx = mouse.x - mouseLastPos.x;
var dy = mouse.y - mouseLastPos.y;
mapOffset.x += dx;
mapOffset.y += dy;
ctx.translate(dx, dy);
mouseLastPos = {x: mouse.x, y: mouse.y};
drawNetwork();
}
// Hovering
else {
var node = getNodeAt(mouse.x, mouse.y);
if (node == mouseHoverNode)
return;
if (node == null) {
nodeMouseOut(mouseHoverNode);
}
else {
if (mouseHoverNode != null)
nodeMouseOut(mouseHoverNode);
nodeMouseIn(node);
}
mouseHoverNode = node;
drawNetwork();
}
});
$(canvas).mousedown(function(e) {
var mouse = mousePos(e);
mouseLastPos = mouseDownPos = {x: mouse.x, y: mouse.y};
mouseDownNode = getNodeAt(mouse.x, mouse.y);
return false;
});
$(canvas).mouseup(function(e) {
var mouse = mousePos(e);
var mouseMoved =
Math.abs(mouse.x - mouseDownPos.x) +
Math.abs(mouse.y - mouseDownPos.y) > 3
if (!mouseMoved) {
if (mouseDownNode)
selectNode(mouseDownNode, true);
else {
clearNodes();
drawNetwork();
}
}
else {
$('body').css('cursor', 'auto');
}
mouseDownPos = null;
mouseDownNode = null;
return false;
});
function handleScroll(e) {
var mouse = mousePos(e);
var e = window.event;
var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
var ratio = (delta < 0) ? (3 / 4) : 1 + (1 / 3);
var mx = mouse.x - mapOffset.x;
var my = mouse.y - mapOffset.y;
zoom *= ratio;
for (var i = 0; i < nodes.length; ++i) {
var node = nodes[i];
node.x = (node.x - mx) * ratio + mx;
node.y = (node.y - my) * ratio + my;
// node.x *= ratio;
// node.y *= ratio;
// node.radius *= ratio;
node.radius = (4 + node.size * 8) * zoom;
}
drawNetwork();
}
canvas.addEventListener("mousewheel", handleScroll, false);
canvas.addEventListener("DOMMouseScroll", handleScroll, false);
});
function nodeMouseIn(node) {
node.hover = true;
$('body').css('cursor', 'pointer');
}
function nodeMouseOut(node) {
node.hover = false;
$('body').css('cursor', 'auto');
}
</script>
</body>
</html>