1
0
mirror of https://github.com/r4sas/Niflheim-api synced 2025-02-11 14:34:14 +00:00

update files, add map and few more scripts

Signed-off-by: r4sas <r4sas@i2pmail.org>
This commit is contained in:
R4SAS 2022-12-10 14:07:20 +00:00
parent 87f5936e83
commit 8897b48fc9
21 changed files with 1120 additions and 102 deletions

View File

@ -1,6 +1,6 @@
# niflheim-api.py
# Yggdrasil-monitor
Niflheim-api provides both a web interface and an api. The web interface is used to see some basic stats on the data vserv.py has collected and the API provides raw data in JSON format.
Yggdrasil-monitor provides both a web interface and an api. The web interface is used to see some basic stats on the data has collected by crawler and the API provides raw data in JSON format.
## Install & Setup

14
api/addresses.py Normal file
View File

@ -0,0 +1,14 @@
#!/usr/bin/env python
import json
from pk2addr import keyTo128BitAddress
with open('api/result.json', 'r') as f:
data = json.load(f)
addlist = open("api/addresses.txt", "w")
for key, _ in data['yggnodes'].items():
addlist.write(keyTo128BitAddress(key) + "\n")
addlist.close()

24
api/config.py.example Normal file
View File

@ -0,0 +1,24 @@
### Configuration file ###
### Database configuration ###
DB_PASS = "password"
DB_USER = "yggindex"
DB_NAME = "yggindex"
DB_HOST = "localhost"
DB_RETRIES = 3
DB_RECONNIDLE = 2
# count peer alive if it was available not more that amount of seconds ago
# I'm using 1 hour beause of running crawler every 30 minutes
ALIVE_SECONDS = 3600 # 1 hour
### Built-in crawler configuration ###
# Configuration to use TCP connection or unix domain socket for admin connection to yggdrasil
useAdminSock = True
yggAdminTCP = ('localhost', 9001)
yggAdminSock = ('/var/run/yggdrasil.sock')
# Save in database node info fields like buildname, buildarch, etc. (True/False)?
saveDefaultNodeInfo = False
removableFileds = ['buildname', 'buildarch', 'buildplatform', 'buildversion', 'board_name', 'kernel', 'model', 'system']

View File

@ -12,22 +12,7 @@ import ipaddress
import traceback
from threading import Lock, Thread
from queue import Queue
#####
# Configuration to use TCP connection or unix domain socket for admin connection to yggdrasil
useAdminSock = True
yggAdminTCP = ('localhost', 9001)
yggAdminSock = ('/var/run/yggdrasil.sock')
DB_PASS = "password"
DB_USER = "yggindex"
DB_NAME = "yggindex"
DB_HOST = "localhost"
## Save in database node info fields like buildname, buildarch, etc. (True/False)?
saveDefaultNodeInfo = False
removableFileds = ['buildname', 'buildarch', 'buildplatform', 'buildversion', 'board_name', 'kernel', 'model', 'system']
from config import DB_PASS, DB_USER, DB_NAME, DB_HOST, useAdminSock, yggAdminTCP, yggAdminSock, saveDefaultNodeInfo, removableFileds
#####
@ -210,11 +195,11 @@ for k,v in selfInfo['response']['self'].items():
# Loop over rumored nodes and ping them, adding to visited if they respond
while len(rumored) > 0:
for k,v in rumored.items():
#print("Processing", v['coords'])
# print("Processing", v['coords'])
handleResponse(k, v, doRequest(getDHTPingRequest(v['box_pub_key'], v['coords'])))
break
del rumored[k]
#End
# End
nodeinfopool.wait()

112
api/graphPlotter.py Normal file
View File

@ -0,0 +1,112 @@
import pygraphviz as pgv
import time
import json
import networkx as nx
from networkx.algorithms import centrality
import urllib.request
def position_nodes(nodes, edges):
G = pgv.AGraph(strict=True, directed=False, size='10!')
for n in nodes.values():
G.add_node(n.ip, label=n.label, coords=n.coords)
for e in edges:
G.add_edge(e.a.ip, e.b.ip, len=1.0)
G.layout(prog='neato', args='-Gepsilon=0.0001 -Gmaxiter=100000')
return G
def compute_betweenness(G):
ng = nx.Graph()
for start in G.iternodes():
others = G.neighbors(start)
for other in others:
ng.add_edge(start, other)
c = centrality.betweenness_centrality(ng)
for k, v in c.items():
c[k] = v
return c
def canonalize_ip(ip):
return ':'.join( i.rjust(4, '0') for i in ip.split(':') )
def load_db():
url = "http://[316:c51a:62a3:8b9::2]/result.json"
f = urllib.request.urlopen(url)
return dict(
[
(canonalize_ip(v[0]), v[1]) for v in
[
l.split(None)[:2] for l in
json.loads(f.read())["yggnodes"].keys()
]
if len(v) > 1
]
)
def get_graph_json(G):
max_neighbors = 1
for n in G.iternodes():
neighbors = len(G.neighbors(n))
if neighbors > max_neighbors:
max_neighbors = neighbors
print('Max neighbors: %d' % max_neighbors)
out_data = {
'created': int(time.time()),
'nodes': [],
'edges': []
}
centralities = compute_betweenness(G)
db = load_db()
for n in G.iternodes():
neighbor_ratio = len(G.neighbors(n)) / float(max_neighbors)
pos = n.attr['pos'].split(',', 1)
centrality = centralities.get(n.name, 0)
size = 5*(1 + 1*centrality)
name = db.get(canonalize_ip(n.name))
# If label isn't the default value, set name to that instead
if n.attr['label'] != n.name.split(':')[-1]: name = n.attr['label']
out_data['nodes'].append({
'id': n.name,
'label': name if name else n.attr['label'],
'name': name,
'coords': n.attr['coords'],
'x': float(pos[0]),
'y': float(pos[1]),
'color': _gradient_color(neighbor_ratio, [(100, 100, 100), (0, 0, 0)]),
'size': size,
'centrality': '%.4f' % centrality
})
for e in G.iteredges():
out_data['edges'].append({
'sourceID': e[0],
'targetID': e[1]
})
return json.dumps(out_data)
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 = int(a[0] + (b[0] - a[0]) * ratio)
g = int(a[1] + (b[1] - a[1]) * ratio)
b = int(a[2] + (b[2] - a[2]) * ratio)
return '#%02x%02x%02x' % (r, g, b)

View File

@ -1,39 +1,37 @@
#!/usr/bin/env python
import psycopg2, json, traceback
from config import DB_PASS, DB_USER, DB_NAME, DB_HOST, saveDefaultNodeInfo, removableFileds
from pk2addr import keyTo128BitAddress
#####
# Configuration to use TCP connection or unix domain socket for admin connection to yggdrasil
DB_PASS = "password"
DB_USER = "yggindex"
DB_NAME = "yggindex"
DB_HOST = "localhost"
## Save in database node info fields like buildname, buildarch, etc. (True/False)?
saveDefaultNodeInfo = False
removableFileds = ['buildname', 'buildarch', 'buildplatform', 'buildversion', 'board_name', 'kernel', 'model', 'system']
#####
with open('api/results.json', 'r') as f:
with open('api/result.json', 'r') as f:
data = json.load(f)
timestamp = data['meta']['generated_at_utc']
# connect to database
dbconn = psycopg2.connect(host=DB_HOST, database=DB_NAME, user=DB_USER, password=DB_PASS)
cur = dbconn.cursor()
# start importing
for node in data['topology']:
for key, node in data['yggnodes'].items():
nodename = ""
nodeinfo = {}
ipv6 = data['topology'][node]['ipv6_addr']
coords = '[%s]' % (' '.join(str(e) for e in data['topology'][node]['coords']))
if node in data['nodeinfo']:
nodeinfo = data['nodeinfo'][node]
if "address" in node:
ipv6 = keyTo128BitAddress(node['address']) if len(node['address']) == 64 else node['address']
else:
ipv6 = keyTo128BitAddress(key)
if "coords" in node:
coords = node['coords']
else:
continue
timestamp = node['time']
if "nodeinfo" in node:
nodeinfo = node['nodeinfo']
if not saveDefaultNodeInfo:
# remove default Node info fields
@ -42,10 +40,8 @@ for node in data['topology']:
if "name" in nodeinfo:
nodename = nodeinfo['name']
elif data['topology'][node]['found'] == False:
nodename = '? %s' % coords
else:
nodename = ipv6
nodename = '? %s' % coords
nodeinfo = json.dumps(nodeinfo)
@ -66,4 +62,3 @@ for node in data['topology']:
dbconn.commit()
cur.close()
dbconn.close()

View File

@ -1,21 +1,12 @@
#!/usr/bin/env python
#max/min for the day with nodes
# max/min for the day with nodes
import psycopg2
import time
from config import DB_PASS, DB_USER, DB_NAME, DB_HOST, ALIVE_SECONDS
#run every hour
DB_PASS = "password"
DB_USER = "yggindex"
DB_NAME = "yggindex"
DB_HOST = "localhost"
# count peer alive if it was available not more that amount of seconds ago
# I'm using 1 hour beause of running crawler every 15 minutes
ALIVE_SECONDS = 3600 # 1 hour
# run every hour
def age_calc(ustamp):
if (time.time() - ustamp) <= ALIVE_SECONDS :

32
api/pk2addr.py Normal file
View File

@ -0,0 +1,32 @@
# Author: silentfamiliar@matrix
def keyTo128BitAddress(key):
key260bits = int("1" + key, 16) # "1" to avoid trimming leading 0s
source_cursor = 4 # skip the "1"
# loop over each bit while NOT(bit) is 1
while (1 & ~(key260bits >> (260 - source_cursor - 1))) == 1:
source_cursor = source_cursor + 1
ones_count = source_cursor - 4 # 1s to count minus 4 which was our initial offset
source_cursor = source_cursor + 1 # skipping trailing 0
dest = (0x2 << 8) | ones_count # set header
bitsToAdd = 128 - 16 # header was 2 bytes which is 16 bit
# append needed amount of NOT key starting from source_cursor
dest = (dest << bitsToAdd) | ((2**bitsToAdd - 1) & ~(key260bits >> (260 - source_cursor - bitsToAdd)))
# the long addr
dest_hex = "0" + hex(dest)[2:]
# format ipv6 128bit addr
addr = ""
for i in range(8):
piece = int(dest_hex[i*4:i*4+4], 16)
if (len(addr) != 0) and not (addr[len(addr)-2:len(addr)] == "::"):
addr += ":"
if (piece != 0):
addr += hex(piece)[2:]
return addr

6
api/static/map/jquery-2.0.3.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View 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)})}});

426
api/static/map/network.js Normal file
View File

@ -0,0 +1,426 @@
"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.setTransform(1, 0, 0, 1, 0, 0);
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 > 4 || 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', '#E86946', '#E8AC9B', '#E8C9C1'];
var txtCol = ['#000000', '#032247', '#034537', '#0E3D02', '#354703', '#403203', '#3D1F02', '#3B0E02', '#3B0E02', '#3B0E02', '#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>Coords:</strong> ' + node.coords + '<br>' +
'<strong>Peers:</strong> ' + node.peers.length + '<br>' +
'<strong>Centrality:</strong> ' + node.centrality + '<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 = node.size;
node.hover = false;
node.selected = false;
node.edges = [];
node.peers = [];
node.depth = 0xFFFF;
// node.color = '#000';
node.originalColor = node.color;
node.textColor = node.color;
}
var newEdges = []
// 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];
}
if (!edge.sourceNode || !edge.targetNode)
continue;
edge.sourceNode.edges.push(edge);
edge.targetNode.edges.push(edge);
edge.sourceNode.peers.push(edge.targetNode);
edge.targetNode.peers.push(edge.sourceNode);
newEdges.push(edge);
}
edges = newEdges;
// 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 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 = (node.size) * 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');
}

202
api/static/map/style.css Normal file
View File

@ -0,0 +1,202 @@
* {
margin: 0;
padding: 0;
}
html, body {
background: #F5F5F5;
font-family: 'sans serif';
}
#header {
background: #FFF;
height: 48px;
line-height: 48px;
position: absolute;
top: 0;
left: 0;
right: 0;
}
h1 {
font-family: 'Inconsolata', 'Consolas', 'Ubuntu Mono', monospace;
font-size: 32px;
float: left;
padding: 0 40px;
font-weight: 100;
color: #333;
}
small {
font-size: 16px;
}
.grey {
color: #999;
}
ul {
list-style-type: none;
height: 100%;
}
li {
float: left;
height: 100%;
}
#header a {
color: #777;
padding: 0 20px;
font-family: 'Open Sans', 'sans-serif';
font-size: 14px;
text-decoration: none;
height: 100%;
display: block;
}
#header a.selected {
background: #DDD;
}
#header a:hover {
background: #EEE;
}
#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;*/
max-height: calc(100vh - 40px);
min-width: 250px;
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;*/
font-size: 10px;
}
#search-box {
width: 100%;
padding: 5px;
outline: none;
border: none;
/*border: 1px solid #CCC;*/
margin: -5px;
font-size: inherit;
}
#sidebar a {
color: #333;
text-decoration: none;
}
#sidebar a:hover {
color: #AAA;
}
#sidebar 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: monospace;
font-size: 10px;
}
.autocomplete-suggestions {
font-family: monospace;
font-size: 10px;
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%;
}
#content {
width: 500px;
margin: 30px auto;
font-family: sans-serif;
font-size: 16px;
color: #333;
line-height: 28px;
letter-spacing: 0.2px;
}
#content h2 {
text-align: center;
margin-bottom: 20px;
color: #29BBFF;
}
#content h3 {
margin-top: 20px;
text-align: center;
color: #29BBFF;
}
#content a {
color: #29BBFF;
}

View File

@ -14,29 +14,31 @@
<strong>Current Nodes online<br />
<font size="18">{{ nodes }}</font></strong>
<br /><br />
You can see them in <i>Yggdrasil Interactive World map</i> <sup><a href="http://[21f:dd73:7cdb:773b:a924:7ec0:800b:221e]/">[&#8663;]</a> <a href="http://map.ygg/" target="_blank">[DNS]</a></sup>
You can see them in <a href="/map"><i>Yggdrasil Interactive World map</i></a>
<br />
Arceliar's map can be found here: <sup><a href="http://[21e:e795:8e82:a9e2:ff48:952d:55f2:f0bb]/" target="_blank">[&#8663;]</a> <a href="http://map.ygg/" target="_blank">[DNS]</a></sup>
</center>
<br /><br />
<div class="wide"></div>
<strong>Make an API request</strong><br />
<small>note: data updated every 15 minutes, all requests are returned in JSON.</small><br /><br />
<small>note: data is updated hourly (due to network growth and instabillity), all requests are returned in JSON.</small><br /><br />
Get a current list of active and online nodes:<br />
<div class="apireq">
http://[31a:fb8a:c43e:ca59::2]/current <sup><a href="http://[31a:fb8a:c43e:ca59::2]/current" target="_blank">[&#8663;]</a> <a href="http://nodelist.ygg/current" target="_blank">[DNS]</a></sup>
http://[316:c51a:62a3:8b9::2]/current <sup><a href="http://[316:c51a:62a3:8b9::2]/current" target="_blank">[&#8663;]</a> <a href="http://nodelist.ygg/current" target="_blank">[DNS]</a></sup>
</div>
Nodeinfo from all current active nodes:<br />
<div class="apireq">
http://[31a:fb8a:c43e:ca59::2]/nodeinfo <sup><a href="http://[31a:fb8a:c43e:ca59::2]/nodeinfo" target="_blank">[&#8663;]</a> <a href="http://nodelist.ygg/nodeinfo" target="_blank">[DNS]</a></sup>
http://[316:c51a:62a3:8b9::2]/nodeinfo <sup><a href="http://[316:c51a:62a3:8b9::2]/nodeinfo" target="_blank">[&#8663;]</a> <a href="http://nodelist.ygg/nodeinfo" target="_blank">[DNS]</a></sup>
</div>
Active nodes count for last 24 hours:<br />
<div class="apireq">
http://[31a:fb8a:c43e:ca59::2]/nodes24h <sup><a href="http://[31a:fb8a:c43e:ca59::2]/nodes24h" target="_blank">[&#8663;]</a> <a href="http://nodelist.ygg/nodes24h" target="_blank">[DNS]</a></sup>
http://[316:c51a:62a3:8b9::2]/nodes24h <sup><a href="http://[316:c51a:62a3:8b9::2]/nodes24h" target="_blank">[&#8663;]</a> <a href="http://nodelist.ygg/nodes24h" target="_blank">[DNS]</a></sup>
</div>
Active nodes count for last 30 days:<br />
<div class="apireq">
http://[31a:fb8a:c43e:ca59::2]/nodes30d <sup><a href="http://[31a:fb8a:c43e:ca59::2]/nodes30d" target="_blank">[&#8663;]</a> <a href="http://nodelist.ygg/nodes30d" target="_blank">[DNS]</a></sup>
http://[316:c51a:62a3:8b9::2]/nodes30d <sup><a href="http://[316:c51a:62a3:8b9::2]/nodes30d" target="_blank">[&#8663;]</a> <a href="http://nodelist.ygg/nodes30d" target="_blank">[DNS]</a></sup>
</div>
<div class="wide"></div>
@ -47,7 +49,7 @@
</div>
<div class="wide"></div>
<small>Made with <a href="https://github.com/r4sas/Niflheim-api" target="_blank">fork</a> of <a href="https://github.com/yakamok/Niflheim-api" target="_blank">Niflheim-API</a> by yakamok</small>
<small>Made with <a href="https://github.com/r4sas/yggdrasil-monitor" target="_blank">fork</a> of <a href="https://github.com/yakamok/Niflheim-api" target="_blank">Niflheim-API</a> by yakamok</small>
</div>
</body>
</html>

View File

@ -0,0 +1,17 @@
{% extends "map/base.html" %}
{% block content %}
<div id="content-wrapper">
<div id="content">
<h2>About</h2>
<p>This is a project that aims to demystify what the <a href="https://yggdrasil-network.github.io/">Yggdrasil</a> network is like. Currently the only thing we have here is a map of the spanning tree subset of the network. The full source code is at <a href="https://github.com/Arceliar/yggdrasil-map">GitHub</a>.</p>
<h3>Network map</h3>
<p>The network page has a map of Yggdrasil's spanning tree as it is now. The map is not complete since it is hard/impossible to get a full picture of the network, and it only includes the minimum subset of links needed to construct the spanning tree. The known nodes and tree coordinates are taken from <a href="http://y.yakamo.org:3000/">Yakamo's API</a>. Node names can be configured by setting a "name" field in <a href="https://yggdrasil-network.github.io/configuration.html">NodeInfo</a>, or from <a href="https://github.com/yakamok/yggdrasil-nodelist">Yakamo's node list</a> as a fallback.</p>
<h3>Contact</h3>
<p>This project was forked from <em>zielmicha</em>'s fork of <em>Randati</em>'s fc00.
The yggdrasil developers can be contacted over matrix or IRC, for more info see: <a href="https://yggdrasil-network.github.io/">yggdrasil-network.github.io</a>.</p>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>0200::/7 Mapping The Yggdrasil Network</title>
<script src="{{ url_for('static', filename='map/jquery-2.0.3.min.js')}}"></script>
<script src="{{ url_for('static', filename='map/jquery.autocomplete.min.js')}}"></script>
<link href="{{ url_for('static', filename='map/style.css')}}" rel="stylesheet" type="text/css">
</head>
<body>
<div id="header">
<h1>0200<span class="grey">::/7</span></h1>
<ul>
<li><a href="/">Status</a></li>
<li><a href="/map" {% if page == 'network' %} class="selected" {% endif %}>Map</a></li>
<li><a href="/map/about"{% if page == 'about' %} class="selected" {% endif %}>About</a></li>
<li><a href="https://github.com/r4sas/yggdrasil-monitor">Source</a></li>
<li><tt>{% if ip is not none %}{{ ip }}{% endif %}</tt></li>
</ul>
</div>
{% block content %}{% endblock %}
</body>
</html>

View File

@ -0,0 +1,22 @@
{% extends "map/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/map/network.js"></script>
{% endblock %}

78
api/updateGraph.py Executable file
View File

@ -0,0 +1,78 @@
#!/usr/bin/env python
import graphPlotter
import html
import urllib.request, json
url = "http://[316:c51a:62a3:8b9::2]/result.json"
# nodes indexed by coords
class NodeInfo:
def __init__(self, ip, coords):
self.ip = str(ip)
self.label = str(ip).split(":")[-1]
self.coords = str(coords)
self.version = "unknown"
def getCoordList(self):
return self.coords.strip("[]").split(" ")
def getParent(self):
p = self.getCoordList()
if len(p) > 0: p = p[:-1]
return "[" + " ".join(p).strip() + "]"
def getLink(self):
c = self.getCoordList()
return int(self.getCoordList()[-1].strip() or "0")
class LinkInfo:
def __init__(self, a, b):
self.a = a # NodeInfo
self.b = b # NodeInfo
def generate_graph(time_limit=60*60*3):
response = urllib.request.urlopen(url)
data = json.loads(response.read())["yggnodes"]
toAdd = []
for key in data:
if 'address' not in data[key] or 'coords' not in data[key]: continue
ip = data[key]['address']
coords = data[key]['coords']
info = NodeInfo(ip, coords)
try:
if 'nodeinfo' in data[key]:
if 'name' in data[key]['nodeinfo']:
label = str(data[key]['nodeinfo']['name'])
if len(label) <= 64:
info.label = label
except: pass
info.label = html.escape(info.label)
toAdd.append(info)
nodes = dict()
def addAncestors(info):
coords = info.getParent()
parent = NodeInfo("{} {}".format("?", coords), coords)
parent.label = parent.ip
nodes[parent.coords] = parent
if parent.coords != parent.getParent(): addAncestors(parent)
for info in toAdd: addAncestors(info)
for info in toAdd: nodes[info.coords] = info
sortedNodes = sorted(nodes.values(), key=(lambda x: x.getLink()))
#for node in sortedNodes: print node.ip, node.coords, node.getParent(), node.getLink()
edges = []
for node in sortedNodes:
if node.coords == node.getParent: continue
edges.append(LinkInfo(node, nodes[node.getParent()]))
print('%d nodes, %d edges' % (len(nodes), len(edges)))
graph = graphPlotter.position_nodes(nodes, edges)
js = graphPlotter.get_graph_json(graph)
with open('api/static/graph.json', 'w') as f:
f.write(js)
if __name__ == '__main__':
generate_graph()

7
api/web.fcgi Executable file
View File

@ -0,0 +1,7 @@
#!/usr/bin/env python
from flup.server.fcgi import WSGIServer
from web import app
if __name__ == '__main__':
WSGIServer(app).run()

View File

@ -1,47 +1,62 @@
#!/usr/bin/env python
import signal, sys, time
from flask import Flask, render_template
from functools import wraps
from flask import Flask, Response, render_template
from flask_restful import Resource, Api
import requests
import psycopg2
import json
######
DB_PASS = "password"
DB_USER = "yggindex"
DB_NAME = "yggindex"
DB_HOST = "localhost"
# count peer alive if it was available not more that amount of seconds ago
# I'm using 1 hour beause of running crawler every 15 minutes
ALIVE_SECONDS = 3600 # 1 hour
from config import DB_PASS, DB_USER, DB_NAME, DB_HOST, DB_RETRIES, DB_RECONNIDLE, ALIVE_SECONDS
######
app = Flask(__name__)
api = Api(app)
dbconn = psycopg2.connect(host=DB_HOST,\
database=DB_NAME,\
user=DB_USER,\
password=DB_PASS)
def pg_connect():
return psycopg2.connect(host=DB_HOST,\
database=DB_NAME,\
user=DB_USER,\
password=DB_PASS)
# dbconn = pg_connect() # initialize connection
def retry(fn):
@wraps(fn)
def wrapper(*args, **kw):
for x in range(DB_RETRIES):
try:
return fn(*args, **kw)
except (psycopg2.InterfaceError, psycopg2.OperationalError) as e:
print ("\nDatabase Connection [InterfaceError or OperationalError]")
print ("Idle for %s seconds" % (cls._reconnectIdle))
time.sleep(DB_RECONNIDLE)
dbconn = pg_connect()
return wrapper
def signal_handler(sig, frame):
dbconn.close()
sys.exit(0)
def age_calc(ustamp):
if (time.time() - ustamp) <= ALIVE_SECONDS:
return True
else:
return False
# active nodes
class nodesCurrent(Resource):
@retry
def get(self):
dbconn = pg_connect()
cur = dbconn.cursor()
nodes = {}
cur.execute("select * from yggindex")
for i in cur.fetchall():
@ -51,15 +66,18 @@ class nodesCurrent(Resource):
dbconn.commit()
cur.close()
nodelist = {}
nodelist['yggnodes'] = nodes
nodeinfo = {}
nodeinfo['yggnodes'] = nodes
return nodelist
dbconn.close()
return nodeinfo
# nodes info
class nodesInfo(Resource):
@retry
def get(self):
dbconn = pg_connect()
cur = dbconn.cursor()
nodes = {}
cur.execute("select * from yggnodeinfo")
@ -73,12 +91,15 @@ class nodesInfo(Resource):
nodeinfo = {}
nodeinfo['yggnodeinfo'] = nodes
dbconn.close()
return nodeinfo
# alive nodes count for latest 24 hours
class nodes24h(Resource):
@retry
def get(self):
dbconn = pg_connect()
cur = dbconn.cursor()
nodes = {}
cur.execute("SELECT * FROM timeseries ORDER BY unixtstamp DESC LIMIT 24")
@ -91,12 +112,15 @@ class nodes24h(Resource):
nodeinfo = {}
nodeinfo['nodes24h'] = nodes
dbconn.close()
return nodeinfo
# alive nodes count for latest 30 days
class nodes30d(Resource):
@retry
def get(self):
dbconn = pg_connect()
cur = dbconn.cursor()
nodes = {}
cur.execute("SELECT * FROM timeseries ORDER BY unixtstamp DESC LIMIT 24 * 30")
@ -109,20 +133,14 @@ class nodes30d(Resource):
nodeinfo = {}
nodeinfo['nodes30d'] = nodes
dbconn.close()
return nodeinfo
# alive nodes count for latest 24 hours
class crawlResult(Resource):
def get(self):
with open('api/results.json', 'r') as f:
data = json.load(f)
return data
@app.route("/")
@retry
def fpage():
dbconn = pg_connect()
cur = dbconn.cursor()
nodes = 0
cur.execute("select * from yggindex")
@ -134,19 +152,32 @@ def fpage():
dbconn.commit()
cur.close()
dbconn.close()
return render_template('index.html', nodes=nodes)
@app.route('/map')
@app.route('/map/network')
def page_network():
return render_template('map/network.html', page='network')
@app.route('/map/about')
def page_about():
return render_template('map/about.html', page='about')
@app.after_request
def add_header(response):
response.cache_control.max_age = 300
return response
# sort out the api request here for the url
api.add_resource(nodesCurrent, '/current')
api.add_resource(nodesInfo, '/nodeinfo')
api.add_resource(nodes24h, '/nodes24h')
api.add_resource(nodes30d, '/nodes30d')
api.add_resource(crawlResult, '/result.json')
# regirster signal handler
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
if __name__ == '__main__':
app.run(host='::', port=3000)
app.run(host='127.0.0.1', port=3001)

View File

@ -1,12 +1,32 @@
#!/bin/sh
#!/bin/bash
YGGCRAWL="/opt/yggcrawl/yggcrawl" # path to yggcrawl binary
YGGAPIPATH="/opt/yggdrasil-api" # path to Niflheim-API directory
ulimit -n 4096
CRAWLPEER="tcp://127.0.0.1:12345" # Yggdrasil peer address
CRAWLFILE="api/results.json"
CRAWLRETR=3
YGGCRAWL="/opt/yggdrasil-crawler/crawler"
YGGAPIPATH="/opt/yggdrasil-api"
TMPFILE="api/current.json"
CRAWLFILE="api/result.json"
# Crawler timeout in minutes. It must be lesser then crontab job period
# Increased to 50 minutes and crontab runs hourly due to network instabillity
#CRAWLTIMEOUT=50
##############################################################################
cd $YGGAPIPATH
$YGGCRAWL -peer $CRAWLPEER -retry $CRAWLRETR -file $CRAWLFILE > api/yggcrawl.log 2>&1
venv/bin/python api/importer.py >> api/yggcrawl.log 2>&1
#let "TIMEOUT = $CRAWLTIMEOUT * 60"
#timeout $TIMEOUT $YGGCRAWL > $TMPFILE 2>logs/crawler.log
$YGGCRAWL > $TMPFILE 2>logs/crawler.log
if [[ $? == 0 ]] # Crawler not triggered error or was killed
then
# add a little delay...
sleep 3
mv -f $TMPFILE $CRAWLFILE
venv/bin/python api/importer.py > logs/importer.log 2>&1
venv/bin/python api/addresses.py > logs/addresses.log 2>&1
venv/bin/python api/updateGraph.py > logs/graph.log 2>&1
fi

View File

@ -4,7 +4,7 @@ After=network.target
[Service]
WorkingDirectory=/opt/yggdrasil-api
ExecStart=/opt/yggdrasil-api/venv/bin/python /opt/yggdrasil-api/api/niflheim-api.py
ExecStart=/opt/yggdrasil-api/venv/bin/python /opt/yggdrasil-api/api/web.py
Restart=always
Type=simple