mirror of
https://github.com/cwinfo/yggdrasil-map
synced 2024-11-25 20:31:35 +00:00
Initial commit.
This commit is contained in:
commit
380e7bb3dd
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
.cache
|
||||||
|
graph.json
|
150
makeGraph.py
Executable file
150
makeGraph.py
Executable file
@ -0,0 +1,150 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
cjdns_path = '/home/antti/prog/cjdns/'
|
||||||
|
|
||||||
|
import sys
|
||||||
|
sys.path.append(cjdns_path + 'contrib/python/cjdnsadmin/')
|
||||||
|
import adminTools as admin
|
||||||
|
from collections import deque
|
||||||
|
import pygraphviz as pgv
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import httplib2
|
||||||
|
|
||||||
|
|
||||||
|
cjdns = admin.connect()
|
||||||
|
root = admin.whoami(cjdns)
|
||||||
|
rootIP = root['IP']
|
||||||
|
|
||||||
|
G = pgv.AGraph(strict=True, directed=False, size='10!')
|
||||||
|
G.add_node(rootIP, label=rootIP[-4:])
|
||||||
|
|
||||||
|
nodes = deque()
|
||||||
|
nodes.append(rootIP)
|
||||||
|
|
||||||
|
while len(nodes) != 0:
|
||||||
|
parentIP = nodes.popleft()
|
||||||
|
resp = cjdns.NodeStore_nodeForAddr(parentIP)
|
||||||
|
numLinks = 0
|
||||||
|
|
||||||
|
if 'result' in resp:
|
||||||
|
link = resp['result']
|
||||||
|
if 'linkCount' in link:
|
||||||
|
numLinks = int(resp['result']['linkCount'])
|
||||||
|
G.get_node(parentIP).attr['version'] = resp['result']['protocolVersion']
|
||||||
|
|
||||||
|
for i in range(0, numLinks):
|
||||||
|
resp = cjdns.NodeStore_getLink(parentIP, i)
|
||||||
|
childLink = resp['result']
|
||||||
|
childIP = childLink['child']
|
||||||
|
|
||||||
|
# Check to see if its one hop away from parent node
|
||||||
|
if childLink['isOneHop'] != 1:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# If its a new node then we want to follow it
|
||||||
|
if not childIP in G.nodes():
|
||||||
|
G.add_node(childIP, label=childIP[-4:])
|
||||||
|
G.get_node(childIP).attr['version'] = 0
|
||||||
|
nodes.append(childIP)
|
||||||
|
|
||||||
|
# If there is not a link between the nodes we should put one there
|
||||||
|
if not G.has_edge(childIP, parentIP):
|
||||||
|
G.add_edge(parentIP, childIP, len=1.0)
|
||||||
|
|
||||||
|
print 'Number of nodes:', G.number_of_nodes()
|
||||||
|
print 'Number of edges:', G.number_of_edges()
|
||||||
|
|
||||||
|
G.layout(prog='neato', args='-Gepsilon=0.0001 -Gmaxiter=100000') # neato, fdp, dot
|
||||||
|
|
||||||
|
|
||||||
|
max_neighbors = 0
|
||||||
|
|
||||||
|
for n in G.iternodes():
|
||||||
|
neighbors = len(G.neighbors(n))
|
||||||
|
if neighbors > max_neighbors:
|
||||||
|
max_neighbors = neighbors
|
||||||
|
|
||||||
|
print 'Max neighbors:', max_neighbors
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def download_node_names():
|
||||||
|
print "Downloading names"
|
||||||
|
page = 'http://[fc5d:baa5:61fc:6ffd:9554:67f0:e290:7535]/nodes/list.json'
|
||||||
|
|
||||||
|
ip_dict = dict()
|
||||||
|
h = httplib2.Http(".cache", timeout=15.0)
|
||||||
|
try:
|
||||||
|
r, content = h.request(page, "GET")
|
||||||
|
nameip = json.loads(content)['nodes']
|
||||||
|
|
||||||
|
for node in nameip:
|
||||||
|
ip_dict[node['ip']] = node['name']
|
||||||
|
|
||||||
|
print "Names downloaded"
|
||||||
|
except Exception as e:
|
||||||
|
print "Connection to Mikey's nodelist failed, continuing without names", e
|
||||||
|
|
||||||
|
return ip_dict
|
||||||
|
|
||||||
|
|
||||||
|
node_names = download_node_names()
|
||||||
|
|
||||||
|
def gradient_color(ratio, colors):
|
||||||
|
jump = 1.0 / (len(colors) - 1)
|
||||||
|
gap_num = int(ratio / (jump + 0.0000001))
|
||||||
|
|
||||||
|
a = colors[gap_num]
|
||||||
|
b = colors[gap_num + 1]
|
||||||
|
|
||||||
|
ratio = (ratio - gap_num * jump) * (len(colors) - 1)
|
||||||
|
|
||||||
|
r = a[0] + (b[0] - a[0]) * ratio
|
||||||
|
g = a[1] + (b[1] - a[1]) * ratio
|
||||||
|
b = a[2] + (b[2] - a[2]) * ratio
|
||||||
|
|
||||||
|
return '#%02x%02x%02x' % (r, g, b)
|
||||||
|
|
||||||
|
|
||||||
|
out_data = {
|
||||||
|
'created': int(time.time()),
|
||||||
|
'nodes': [],
|
||||||
|
'edges': []
|
||||||
|
}
|
||||||
|
|
||||||
|
for n in G.iternodes():
|
||||||
|
neighbor_ratio = len(G.neighbors(n)) / float(max_neighbors)
|
||||||
|
|
||||||
|
pos = n.attr['pos'].split(',', 1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
name = node_names[n.name]
|
||||||
|
except:
|
||||||
|
name = n.attr['label']
|
||||||
|
|
||||||
|
out_data['nodes'].append({
|
||||||
|
'id': n.name,
|
||||||
|
'label': name,
|
||||||
|
'version': n.attr['version'],
|
||||||
|
'x': float(pos[0]),
|
||||||
|
'y': float(pos[1]),
|
||||||
|
# 'color': gradient_color(neighbor_ratio, [(255,60,20), (23,255,84), (41,187,255)]),
|
||||||
|
'color': gradient_color(neighbor_ratio, [(100, 100, 100), (0, 0, 0)]),
|
||||||
|
# 'color': gradient_color(neighbor_ratio, [(255, 255, 255), (255, 0 ,255)]),
|
||||||
|
'size': neighbor_ratio
|
||||||
|
})
|
||||||
|
|
||||||
|
# '#29BBFF', '#17FF54', '#FFBD0F', '#FF3C14', '#590409'
|
||||||
|
|
||||||
|
for e in G.iteredges():
|
||||||
|
out_data['edges'].append({
|
||||||
|
'sourceID': e[0],
|
||||||
|
'targetID': e[1]
|
||||||
|
})
|
||||||
|
|
||||||
|
json_output = json.dumps(out_data)
|
||||||
|
|
||||||
|
f = open('web/static/graph.json', 'w')
|
||||||
|
f.write(json_output)
|
||||||
|
f.close()
|
662
web/index.html
Normal file
662
web/index.html
Normal file
@ -0,0 +1,662 @@
|
|||||||
|
<!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>
|
BIN
web/static/Inconsolata.otf
Normal file
BIN
web/static/Inconsolata.otf
Normal file
Binary file not shown.
6
web/static/jquery-2.0.3.min.js
vendored
Normal file
6
web/static/jquery-2.0.3.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
29
web/static/jquery.autocomplete.min.js
vendored
Normal file
29
web/static/jquery.autocomplete.min.js
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Ajax Autocomplete for jQuery, version 1.2.9
|
||||||
|
* (c) 2013 Tomas Kirda
|
||||||
|
*
|
||||||
|
* Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license.
|
||||||
|
* For details, see the web site: https://github.com/devbridge/jQuery-Autocomplete
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
(function(d){"function"===typeof define&&define.amd?define(["jquery"],d):d(jQuery)})(function(d){function g(a,b){var c=function(){},c={autoSelectFirst:!1,appendTo:"body",serviceUrl:null,lookup:null,onSelect:null,width:"auto",minChars:1,maxHeight:300,deferRequestBy:0,params:{},formatResult:g.formatResult,delimiter:null,zIndex:9999,type:"GET",noCache:!1,onSearchStart:c,onSearchComplete:c,onSearchError:c,containerClass:"autocomplete-suggestions",tabDisabled:!1,dataType:"text",currentRequest:null,triggerSelectOnValidInput:!0,
|
||||||
|
lookupFilter:function(a,b,c){return-1!==a.value.toLowerCase().indexOf(c)},paramName:"query",transformResult:function(a){return"string"===typeof a?d.parseJSON(a):a}};this.element=a;this.el=d(a);this.suggestions=[];this.badQueries=[];this.selectedIndex=-1;this.currentValue=this.element.value;this.intervalId=0;this.cachedResponse={};this.onChange=this.onChangeInterval=null;this.isLocal=!1;this.suggestionsContainer=null;this.options=d.extend({},c,b);this.classes={selected:"autocomplete-selected",suggestion:"autocomplete-suggestion"};
|
||||||
|
this.hint=null;this.hintValue="";this.selection=null;this.initialize();this.setOptions(b)}var k=function(){return{escapeRegExChars:function(a){return a.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")},createNode:function(a){var b=document.createElement("div");b.className=a;b.style.position="absolute";b.style.display="none";return b}}}();g.utils=k;d.Autocomplete=g;g.formatResult=function(a,b){var c="("+k.escapeRegExChars(b)+")";return a.value.replace(RegExp(c,"gi"),"<strong>$1</strong>")};g.prototype=
|
||||||
|
{killerFn:null,initialize:function(){var a=this,b="."+a.classes.suggestion,c=a.classes.selected,e=a.options,f;a.element.setAttribute("autocomplete","off");a.killerFn=function(b){0===d(b.target).closest("."+a.options.containerClass).length&&(a.killSuggestions(),a.disableKillerFn())};a.suggestionsContainer=g.utils.createNode(e.containerClass);f=d(a.suggestionsContainer);f.appendTo(e.appendTo);"auto"!==e.width&&f.width(e.width);f.on("mouseover.autocomplete",b,function(){a.activate(d(this).data("index"))});
|
||||||
|
f.on("mouseout.autocomplete",function(){a.selectedIndex=-1;f.children("."+c).removeClass(c)});f.on("click.autocomplete",b,function(){a.select(d(this).data("index"))});a.fixPosition();a.fixPositionCapture=function(){a.visible&&a.fixPosition()};d(window).on("resize.autocomplete",a.fixPositionCapture);a.el.on("keydown.autocomplete",function(b){a.onKeyPress(b)});a.el.on("keyup.autocomplete",function(b){a.onKeyUp(b)});a.el.on("blur.autocomplete",function(){a.onBlur()});a.el.on("focus.autocomplete",function(){a.onFocus()});
|
||||||
|
a.el.on("change.autocomplete",function(b){a.onKeyUp(b)})},onFocus:function(){this.fixPosition();if(this.options.minChars<=this.el.val().length)this.onValueChange()},onBlur:function(){this.enableKillerFn()},setOptions:function(a){var b=this.options;d.extend(b,a);if(this.isLocal=d.isArray(b.lookup))b.lookup=this.verifySuggestionsFormat(b.lookup);d(this.suggestionsContainer).css({"max-height":b.maxHeight+"px",width:b.width+"px","z-index":b.zIndex})},clearCache:function(){this.cachedResponse={};this.badQueries=
|
||||||
|
[]},clear:function(){this.clearCache();this.currentValue="";this.suggestions=[]},disable:function(){this.disabled=!0;this.currentRequest&&this.currentRequest.abort()},enable:function(){this.disabled=!1},fixPosition:function(){var a;"body"===this.options.appendTo&&(a=this.el.offset(),a={top:a.top+this.el.outerHeight()+"px",left:a.left+"px"},"auto"===this.options.width&&(a.width=this.el.outerWidth()-2+"px"),d(this.suggestionsContainer).css(a))},enableKillerFn:function(){d(document).on("click.autocomplete",
|
||||||
|
this.killerFn)},disableKillerFn:function(){d(document).off("click.autocomplete",this.killerFn)},killSuggestions:function(){var a=this;a.stopKillSuggestions();a.intervalId=window.setInterval(function(){a.hide();a.stopKillSuggestions()},50)},stopKillSuggestions:function(){window.clearInterval(this.intervalId)},isCursorAtEnd:function(){var a=this.el.val().length,b=this.element.selectionStart;return"number"===typeof b?b===a:document.selection?(b=document.selection.createRange(),b.moveStart("character",
|
||||||
|
-a),a===b.text.length):!0},onKeyPress:function(a){if(!this.disabled&&!this.visible&&40===a.which&&this.currentValue)this.suggest();else if(!this.disabled&&this.visible){switch(a.which){case 27:this.el.val(this.currentValue);this.hide();break;case 39:if(this.hint&&this.options.onHint&&this.isCursorAtEnd()){this.selectHint();break}return;case 9:if(this.hint&&this.options.onHint){this.selectHint();return}case 13:if(-1===this.selectedIndex){this.hide();return}this.select(this.selectedIndex);if(9===a.which&&
|
||||||
|
!1===this.options.tabDisabled)return;break;case 38:this.moveUp();break;case 40:this.moveDown();break;default:return}a.stopImmediatePropagation();a.preventDefault()}},onKeyUp:function(a){var b=this;if(!b.disabled){switch(a.which){case 38:case 40:return}clearInterval(b.onChangeInterval);if(b.currentValue!==b.el.val())if(b.findBestHint(),0<b.options.deferRequestBy)b.onChangeInterval=setInterval(function(){b.onValueChange()},b.options.deferRequestBy);else b.onValueChange()}},onValueChange:function(){var a=
|
||||||
|
this.options,b=this.el.val(),c=this.getQuery(b);this.selection&&(this.selection=null,(a.onInvalidateSelection||d.noop).call(this.element));clearInterval(this.onChangeInterval);this.currentValue=b;this.selectedIndex=-1;if(a.triggerSelectOnValidInput&&(b=this.findSuggestionIndex(c),-1!==b)){this.select(b);return}c.length<a.minChars?this.hide():this.getSuggestions(c)},findSuggestionIndex:function(a){var b=-1,c=a.toLowerCase();d.each(this.suggestions,function(a,d){if(d.value.toLowerCase()===c)return b=
|
||||||
|
a,!1});return b},getQuery:function(a){var b=this.options.delimiter;if(!b)return a;a=a.split(b);return d.trim(a[a.length-1])},getSuggestionsLocal:function(a){var b=this.options,c=a.toLowerCase(),e=b.lookupFilter,f=parseInt(b.lookupLimit,10),b={suggestions:d.grep(b.lookup,function(b){return e(b,a,c)})};f&&b.suggestions.length>f&&(b.suggestions=b.suggestions.slice(0,f));return b},getSuggestions:function(a){var b,c=this,e=c.options,f=e.serviceUrl,l,g;e.params[e.paramName]=a;l=e.ignoreParams?null:e.params;
|
||||||
|
c.isLocal?b=c.getSuggestionsLocal(a):(d.isFunction(f)&&(f=f.call(c.element,a)),g=f+"?"+d.param(l||{}),b=c.cachedResponse[g]);b&&d.isArray(b.suggestions)?(c.suggestions=b.suggestions,c.suggest()):c.isBadQuery(a)||!1===e.onSearchStart.call(c.element,e.params)||(c.currentRequest&&c.currentRequest.abort(),c.currentRequest=d.ajax({url:f,data:l,type:e.type,dataType:e.dataType}).done(function(b){c.currentRequest=null;c.processResponse(b,a,g);e.onSearchComplete.call(c.element,a)}).fail(function(b,d,f){e.onSearchError.call(c.element,
|
||||||
|
a,b,d,f)}))},isBadQuery:function(a){for(var b=this.badQueries,c=b.length;c--;)if(0===a.indexOf(b[c]))return!0;return!1},hide:function(){this.visible=!1;this.selectedIndex=-1;d(this.suggestionsContainer).hide();this.signalHint(null)},suggest:function(){if(0===this.suggestions.length)this.hide();else{var a=this.options,b=a.formatResult,c=this.getQuery(this.currentValue),e=this.classes.suggestion,f=this.classes.selected,g=d(this.suggestionsContainer),k=a.beforeRender,m="",h;if(a.triggerSelectOnValidInput&&
|
||||||
|
(h=this.findSuggestionIndex(c),-1!==h)){this.select(h);return}d.each(this.suggestions,function(a,d){m+='<div class="'+e+'" data-index="'+a+'">'+b(d,c)+"</div>"});"auto"===a.width&&(h=this.el.outerWidth()-2,g.width(0<h?h:300));g.html(m);a.autoSelectFirst&&(this.selectedIndex=0,g.children().first().addClass(f));d.isFunction(k)&&k.call(this.element,g);g.show();this.visible=!0;this.findBestHint()}},findBestHint:function(){var a=this.el.val().toLowerCase(),b=null;a&&(d.each(this.suggestions,function(c,
|
||||||
|
d){var f=0===d.value.toLowerCase().indexOf(a);f&&(b=d);return!f}),this.signalHint(b))},signalHint:function(a){var b="";a&&(b=this.currentValue+a.value.substr(this.currentValue.length));this.hintValue!==b&&(this.hintValue=b,this.hint=a,(this.options.onHint||d.noop)(b))},verifySuggestionsFormat:function(a){return a.length&&"string"===typeof a[0]?d.map(a,function(a){return{value:a,data:null}}):a},processResponse:function(a,b,c){var d=this.options;a=d.transformResult(a,b);a.suggestions=this.verifySuggestionsFormat(a.suggestions);
|
||||||
|
d.noCache||(this.cachedResponse[c]=a,0===a.suggestions.length&&this.badQueries.push(c));b===this.getQuery(this.currentValue)&&(this.suggestions=a.suggestions,this.suggest())},activate:function(a){var b=this.classes.selected,c=d(this.suggestionsContainer),e=c.children();c.children("."+b).removeClass(b);this.selectedIndex=a;return-1!==this.selectedIndex&&e.length>this.selectedIndex?(a=e.get(this.selectedIndex),d(a).addClass(b),a):null},selectHint:function(){var a=d.inArray(this.hint,this.suggestions);
|
||||||
|
this.select(a)},select:function(a){this.hide();this.onSelect(a)},moveUp:function(){-1!==this.selectedIndex&&(0===this.selectedIndex?(d(this.suggestionsContainer).children().first().removeClass(this.classes.selected),this.selectedIndex=-1,this.el.val(this.currentValue),this.findBestHint()):this.adjustScroll(this.selectedIndex-1))},moveDown:function(){this.selectedIndex!==this.suggestions.length-1&&this.adjustScroll(this.selectedIndex+1)},adjustScroll:function(a){var b=this.activate(a),c,e;b&&(b=b.offsetTop,
|
||||||
|
c=d(this.suggestionsContainer).scrollTop(),e=c+this.options.maxHeight-25,b<c?d(this.suggestionsContainer).scrollTop(b):b>e&&d(this.suggestionsContainer).scrollTop(b-this.options.maxHeight+25),this.el.val(this.getValue(this.suggestions[a].value)),this.signalHint(null))},onSelect:function(a){var b=this.options.onSelect;a=this.suggestions[a];this.currentValue=this.getValue(a.value);this.el.val(this.currentValue);this.signalHint(null);this.suggestions=[];this.selection=a;d.isFunction(b)&&b.call(this.element,
|
||||||
|
a)},getValue:function(a){var b=this.options.delimiter,c;if(!b)return a;c=this.currentValue;b=c.split(b);return 1===b.length?a:c.substr(0,c.length-b[b.length-1].length)+a},dispose:function(){this.el.off(".autocomplete").removeData("autocomplete");this.disableKillerFn();d(window).off("resize.autocomplete",this.fixPositionCapture);d(this.suggestionsContainer).remove()}};d.fn.autocomplete=function(a,b){return 0===arguments.length?this.first().data("autocomplete"):this.each(function(){var c=d(this),e=
|
||||||
|
c.data("autocomplete");if("string"===typeof a){if(e&&"function"===typeof e[a])e[a](b)}else e&&e.dispose&&e.dispose(),e=new g(this,a),c.data("autocomplete",e)})}});
|
422
web/static/network.js
Normal file
422
web/static/network.js
Normal file
@ -0,0 +1,422 @@
|
|||||||
|
"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('/static/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');
|
||||||
|
}
|
186
web/static/style.css
Normal file
186
web/static/style.css
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: 'Inconsolata';
|
||||||
|
src: url("/static/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: 1;*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
top: 48px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#map {
|
||||||
|
position: absolute;
|
||||||
|
width:100%;
|
||||||
|
height:100%;
|
||||||
|
}
|
26
web/templates/base.html
Normal file
26
web/templates/base.html
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>fc00::/8</title>
|
||||||
|
<script src="static/jquery-2.0.3.min.js"></script>
|
||||||
|
<script src="static/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'>
|
||||||
|
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.css" />
|
||||||
|
<link href='static/style.css' rel='stylesheet' type='text/css'>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="header">
|
||||||
|
<h1>fc00<span class="grey">::/8</span></h1>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="/network">Network</a></li>
|
||||||
|
<li><a href="/world-map">World map</a></li>
|
||||||
|
<li><a href="/tools">Tools</a></li>
|
||||||
|
<li><tt>{{ ip }}</tt></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
22
web/templates/network.html
Normal file
22
web/templates/network.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div id="general-info">
|
||||||
|
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"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="content-wrapper">
|
||||||
|
<canvas id="map"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="static/network.js"></script>
|
||||||
|
{% endblock %}
|
9
web/templates/tools.html
Normal file
9
web/templates/tools.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div id="content-wrapper">
|
||||||
|
<h2>Ping</h2>
|
||||||
|
<input type="text" value="{{ ip }}">
|
||||||
|
<h2>Traceroute</h2>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
40
web/templates/world-map.html
Normal file
40
web/templates/world-map.html
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div id="content-wrapper">
|
||||||
|
<div id="map"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<script src="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var nodes = {{ nodes|tojson|safe }}
|
||||||
|
|
||||||
|
var map = L.map('map').setView([0, 0], 2);
|
||||||
|
|
||||||
|
L.tileLayer('http://otile{s}.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.jpg', {
|
||||||
|
subdomains: '1234',
|
||||||
|
minZoom: 2,
|
||||||
|
maxZoom: 7,
|
||||||
|
noWrap: true
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
|
||||||
|
for (var i = 0; i < nodes.length; ++i) {
|
||||||
|
var node = nodes[i];
|
||||||
|
|
||||||
|
node.circle = L.circle([node.latitude, node.longitude], 5000, {
|
||||||
|
// color: 'red',
|
||||||
|
stroke: false,
|
||||||
|
fillColor: 'red',
|
||||||
|
fillOpacity: 1
|
||||||
|
}).addTo(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
50
web/web.py
Normal file
50
web/web.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
from flask import Flask, render_template, request
|
||||||
|
from flaskext.mysql import MySQL
|
||||||
|
import MySQLdb
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.debug = True
|
||||||
|
app.config['MYSQL_DATABASE_HOST'] = 'localhost'
|
||||||
|
app.config['MYSQL_DATABASE_PORT'] = 3306
|
||||||
|
app.config['MYSQL_DATABASE_USER'] = 'fc00'
|
||||||
|
app.config['MYSQL_DATABASE_PASSWORD'] = 'A54RZ8FN9CtgIdPWmVIgp3sKgm1uSNVlVtPGipOHsalMZ9DNEUTeOijNcZRpLCn1'
|
||||||
|
app.config['MYSQL_DATABASE_DB'] = 'fc00'
|
||||||
|
app.config['MYSQL_DATABASE_CHARSET'] = 'utf8'
|
||||||
|
|
||||||
|
mysql = MySQL()
|
||||||
|
mysql.init_app(app)
|
||||||
|
conn = mysql.connect()
|
||||||
|
cursor = conn.cursor(MySQLdb.cursors.DictCursor)
|
||||||
|
|
||||||
|
|
||||||
|
@app.context_processor
|
||||||
|
def add_ip():
|
||||||
|
return dict(ip=request.environ['REMOTE_ADDR'])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
@app.route('/network')
|
||||||
|
def page_network():
|
||||||
|
return render_template('network.html')
|
||||||
|
|
||||||
|
@app.route('/world-map')
|
||||||
|
def page_world_map():
|
||||||
|
cursor.execute('SELECT ip, ipv4, ipv6, domain, CONVERT(longitude, CHAR(16)) as longitude, CONVERT(latitude, CHAR(16)) as latitude FROM nodes')
|
||||||
|
nodes = cursor.fetchall()
|
||||||
|
return render_template('world-map.html', nodes=nodes)
|
||||||
|
|
||||||
|
@app.route('/tools')
|
||||||
|
def page_tools():
|
||||||
|
return render_template('tools.html')
|
||||||
|
|
||||||
|
@app.route('/test')
|
||||||
|
def asd():
|
||||||
|
pprint(vars(request))
|
||||||
|
return 'Asd'
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(host='::')
|
Loading…
Reference in New Issue
Block a user