diff --git a/.gitignore b/.gitignore
index 53ef0cd..a537823 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@
.*.swp
.*.swo
db.sqlite3
+dnmapper/settings.py
diff --git a/bgpdata/models.py b/bgpdata/models.py
index a9e75a1..2804a56 100644
--- a/bgpdata/models.py
+++ b/bgpdata/models.py
@@ -84,6 +84,10 @@ class AS(models.Model):
def getPeerings(self):
return Peering.objects.filter(Q(as1=self)|Q(as2=self))
+ def formatLastSeen(self):
+ if self.lastSeen:
+ return self.lastSeen.startTime.strftime("%d.%m.%Y %H:%I")
+
class BorderRouter(models.Model):
# as id, ip, check method, pingable, reachable
# unique: (crawl_id, asno, as id)
diff --git a/bgpdata/templates/bgpdata/map.html b/bgpdata/templates/bgpdata/map.html
index 2e15e5d..374f250 100644
--- a/bgpdata/templates/bgpdata/map.html
+++ b/bgpdata/templates/bgpdata/map.html
@@ -25,7 +25,7 @@
var asdata = [
{% for AS in ASses %}
- {id: {{AS.pk}}, nodetype: "AS", asnumber: {{AS.number}}, label: "{{AS.number}}", neighbors: {{AS.getPeerings.count}}, crawled: {%if AS.directlyCrawled%}true{%else%}false{%endif%}, online: {%if AS.online%}true{%else%}false{%endif%}, lastSeenDate: "{{AS.lastSeen}}", lastSeen: {%if AS.lastSeen%}{{AS.lastSeen.pk}}{%else%}null{%endif%}}{%if not forloop.last%},{%endif%}
+ {id: {{AS.pk}}, nodetype: "AS", asnumber: {{AS.number}}, label: "{{AS.number}}", neighbors: {{AS.getPeerings.count}}, crawled: {%if AS.directlyCrawled%}true{%else%}false{%endif%}, online: {%if AS.online%}true{%else%}false{%endif%}, lastSeenDate: "{{AS.formatLastSeen}}", lastSeen: {%if AS.lastSeen%}{{AS.lastSeen.pk}}{%else%}null{%endif%}, dismiss: true}{%if not forloop.last%},{%endif%}
{%endfor%}
];
@@ -63,7 +63,6 @@ var svg = d3.select('#plotwin')
.attr('width', width)
.attr('height', height);
-console.log(asdata);
var force = d3.layout.force()
.nodes(asdata)
.links(peerings)
@@ -72,9 +71,7 @@ var force = d3.layout.force()
// .linkDistance(70)
.linkStrength(0.65)
.linkDistance(function(l) {
- console.log(l);
neighs = Math.min(l.source.neighbors, l.target.neighbors);
- console.log(neighs, "neighbors");
switch(neighs) {
case 0: return 40;
case 1:
@@ -103,6 +100,7 @@ var node = svg.selectAll('.node')
.data(asdata)
.enter()
.append("g")
+ .attr("id", function(d) { return "node-" + d.id; })
.call(force.drag);
node.append("ellipse")
@@ -120,6 +118,7 @@ node.append("ellipse")
.attr("stroke", "black")
.attr("stroke-width", "1px");
+
node.append('text')
.attr("font-family", "sans-serif")
.attr("font-size", "13px")
@@ -128,6 +127,57 @@ node.append('text')
.attr("text-anchor", "middle")
.text(function(d) { return d.label; });
+var lastMouseNode = null;
+node.on("mouseover", function(d) {
+ if(lastMouseNode) {
+ if(!d.dismiss && lastMouseNode.id == d.id)
+ return;
+ else
+ $("#node-"+lastMouseNode.id).tipsy("hide");
+ }
+
+ d.dismiss = true;
+ $("#node-"+d.id).tipsy("show");
+
+ lastMouseNode = d;
+ })
+ .on("mouseout", function(d) {
+ if(d.dismiss)
+ $("#node-"+d.id).tipsy("hide");
+ });
+
+$('svg g').tipsy({
+ gravity: 'e',
+ offset: 20,
+ html: true,
+ trigger: 'manual',
+ title: function() {
+ var d = this.__data__;
+ var content = '';
+ if(d.nodetype == 'AS') {
+ var state = null;
+ if(d.online)
+ state = 'Online';
+ else if(d.lastSeen)
+ state = 'Offline';
+ else
+ state = 'Never seen';
+
+ content = 'AS '+d.asnumber+'
';
+ content += '
';
+ content += 'State | '+state+' | ';
+ if(d.lastSeen) {
+ content += '
---|
Last seen | Crawl '+d.lastSeen+' '+d.lastSeenDate+' |
';
+ }
+ if(d.crawled) {
+ content += 'Note | Directly crawled |
';
+ }
+ content += '
';
+ }
+ return content;
+ }
+});
+
force.on("tick", function() {
//node.attr('cx', function(d) { return d.x; })
// .attr('cy', function(d) { return d.y; });
@@ -139,51 +189,62 @@ force.on("tick", function() {
.attr("y2", function(d) { return d.target.y; });
});
+
var lastNode = null;
function click(d) {
if(d3.event.defaultPrevented) return;
- if(lastNode)
- console.log("last time you clicked on another node", d);
+
+ //if(lastNode)
+ // $("#node-" + lastNode.id).tipsy("hide");
+ d.dismiss = false;
+ //$("#node-" + d.id).tipsy("show");
+
+
$("#infowin").fadeOut('fast', function() {
// set progress bar
$("#infowin").html('');
- console.log("fading done");
var content = '';
- if(d.nodetype == 'AS' && d.crawled) {
- console.log("doing javascript...");
- $.ajax({url: "/map/api/borderrouter/?AS__crawl={{crawl.pk}}&AS__number=" + d.asnumber, success: function(result) {
- console.log("SUCCESS", result);
- $("#infowin").html('');
- $("#infowin").fadeIn('fast', function() {});
- for(var i=0; i';
- astable += 'Network | Next Hop | AS Path |
';
- for(var j=0; j'+ann.ip+'/'+ann.prefix+' | '+ann.nextHop+' | '+ann.ASPath+' | ';
- }
- astable += '';
- console.log("Sending stuff for", currRouter.id, "aka", currRouter.routerID);
- $('#infowin').append(astable);
- }});
- })(result.objects[i]);
- }
+ if(d.nodetype == 'AS') {
+ //$("#node-"+d.id).popover({content: "Hallo Katze! :)"});
+ //$("#node-"+d.id).popover('show');
+ //console.log("My element", $("#node-"+d.id));
+ //$("#node-"+d.id).tipsy({
+ // gravity: 'e',
+ // html: true,
+ // //trigger: 'focus',
+ // trigger: 'hover',
+ // title: function() {
+ // var d = this.__data__;
+ // astable = '';
+ // return astable;
+ // }
+ //});
- }});
- astable = '';
- astable += 'AS | '+ d.asnumber +' | |
';
- astable += 'Directly crawled | | |
';
- astable += 'Online | | |
';
- astable += ' | | |
';
-
- astable += '
';
+ if(d.crawled) {
+ $.ajax({url: "/map/api/borderrouter/?AS__crawl={{crawl.pk}}&AS__number=" + d.asnumber, success: function(result) {
+ $("#infowin").html('');
+ $("#infowin").fadeIn('fast', function() {});
+ for(var i=0; i';
+ astable += 'Network | Next Hop | AS Path |
';
+ for(var j=0; j'+ann.ip+'/'+ann.prefix+' | '+ann.nextHop+' | '+ann.ASPath+' | ';
+ }
+ astable += '';
+ $('#infowin').append(astable);
+ }});
+ })(result.objects[i]);
+ }
+
+ }});
+ }
} else {
@@ -191,10 +252,10 @@ function click(d) {
$("#infowin").fadeIn('fast', function() {});
}
});
- console.log("Single click on", d);
lastNode = d;
}
-node.on("click", click)
+node.on("click", click);
+
force.start();
diff --git a/dnmapper/settings.default.py b/dnmapper/settings.default.py
new file mode 100644
index 0000000..fa8ef5c
--- /dev/null
+++ b/dnmapper/settings.default.py
@@ -0,0 +1,93 @@
+"""
+Django settings for dnmapper project.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.7/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/1.7/ref/settings/
+"""
+
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+import os
+BASE_DIR = os.path.dirname(os.path.dirname(__file__))
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = 'crv*hfx2pkxvq1s!)dbz*hdu+r7u2$y4djf6_#6mm)shk9e!58'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+TEMPLATE_DEBUG = True
+
+ALLOWED_HOSTS = []
+
+STATICFILES_DIRS = (
+ 'static/',
+)
+
+# Application definition
+
+INSTALLED_APPS = (
+ 'django.contrib.admin',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ 'bgpdata',
+ 'tastypie',
+)
+
+API_LIMIT_PER_PAGE = 100
+
+MIDDLEWARE_CLASSES = (
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+)
+
+ROOT_URLCONF = 'dnmapper.urls'
+
+WSGI_APPLICATION = 'dnmapper.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
+ }
+}
+
+# Internationalization
+# https://docs.djangoproject.com/en/1.7/topics/i18n/
+
+LANGUAGE_CODE = 'de-de'
+
+TIME_ZONE = 'CET'
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = True
+TEMPLATE_DIRS = (
+ 'templates/',
+)
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/1.7/howto/static-files/
+
+STATIC_URL = '/static/'
diff --git a/static/css/tipsy.css b/static/css/tipsy.css
new file mode 100644
index 0000000..a50b701
--- /dev/null
+++ b/static/css/tipsy.css
@@ -0,0 +1,25 @@
+.tipsy { font-size: 14px; position: absolute; padding: 5px; z-index: 100000; }
+ .tipsy-inner { background-color: #000; color: #FFF; max-width: 200px; padding: 5px 8px 4px 8px; text-align: center; }
+
+ /* Rounded corners */
+ .tipsy-inner { border-radius: 3px; -moz-border-radius: 3px; -webkit-border-radius: 3px; }
+
+ /* Uncomment for shadow */
+ /*.tipsy-inner { box-shadow: 0 0 5px #000000; -webkit-box-shadow: 0 0 5px #000000; -moz-box-shadow: 0 0 5px #000000; }*/
+
+ .tipsy-arrow { position: absolute; width: 0; height: 0; line-height: 0; border: 5px dashed #000; }
+
+ /* Rules to colour arrows */
+ .tipsy-arrow-n { border-bottom-color: #000; }
+ .tipsy-arrow-s { border-top-color: #000; }
+ .tipsy-arrow-e { border-left-color: #000; }
+ .tipsy-arrow-w { border-right-color: #000; }
+
+ .tipsy-n .tipsy-arrow { top: 0px; left: 50%; margin-left: -5px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent; }
+ .tipsy-nw .tipsy-arrow { top: 0; left: 10px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent;}
+ .tipsy-ne .tipsy-arrow { top: 0; right: 10px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent;}
+ .tipsy-s .tipsy-arrow { bottom: 0; left: 50%; margin-left: -5px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; }
+ .tipsy-sw .tipsy-arrow { bottom: 0; left: 10px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; }
+ .tipsy-se .tipsy-arrow { bottom: 0; right: 10px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; }
+ .tipsy-e .tipsy-arrow { right: 0; top: 50%; margin-top: -5px; border-left-style: solid; border-right: none; border-top-color: transparent; border-bottom-color: transparent; }
+ .tipsy-w .tipsy-arrow { left: 0; top: 50%; margin-top: -5px; border-right-style: solid; border-left: none; border-top-color: transparent; border-bottom-color: transparent; }
diff --git a/static/js/jquery.tipsy.js b/static/js/jquery.tipsy.js
new file mode 100644
index 0000000..b0a807e
--- /dev/null
+++ b/static/js/jquery.tipsy.js
@@ -0,0 +1,260 @@
+// tipsy, facebook style tooltips for jquery
+// version 1.0.0a
+// (c) 2008-2010 jason frame [jason@onehackoranother.com]
+// released under the MIT license
+
+(function($) {
+
+ function maybeCall(thing, ctx) {
+ return (typeof thing == 'function') ? (thing.call(ctx)) : thing;
+ };
+
+ function isElementInDOM(ele) {
+ while (ele = ele.parentNode) {
+ if (ele == document) return true;
+ }
+ return false;
+ };
+
+ function Tipsy(element, options) {
+ this.$element = $(element);
+ this.options = options;
+ this.enabled = true;
+ this.fixTitle();
+ };
+
+ Tipsy.prototype = {
+ show: function() {
+ var title = this.getTitle();
+ if (title && this.enabled) {
+ var $tip = this.tip();
+
+ $tip.find('.tipsy-inner')[this.options.html ? 'html' : 'text'](title);
+ $tip[0].className = 'tipsy'; // reset classname in case of dynamic gravity
+ $tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).prependTo(document.body);
+
+ var pos = $.extend({}, this.$element.offset(), {
+ width: this.$element[0].offsetWidth,
+ height: this.$element[0].offsetHeight
+ });
+
+ var actualWidth = $tip[0].offsetWidth,
+ actualHeight = $tip[0].offsetHeight,
+ gravity = maybeCall(this.options.gravity, this.$element[0]);
+
+ var tp;
+ switch (gravity.charAt(0)) {
+ case 'n':
+ tp = {top: pos.top + pos.height + this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
+ break;
+ case 's':
+ tp = {top: pos.top - actualHeight - this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
+ break;
+ case 'e':
+ tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth - this.options.offset};
+ // XXX HACK: i want to set another offset so apparently I have to hardcode this into the code. obviously.
+ tp = {top: pos.top + pos.height / 2 - actualHeight / 2 + this.options.offset, left: pos.left - actualWidth};
+ break;
+ case 'w':
+ tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width + this.options.offset};
+ break;
+ }
+
+ if (gravity.length == 2) {
+ if (gravity.charAt(1) == 'w') {
+ tp.left = pos.left + pos.width / 2 - 15;
+ } else {
+ tp.left = pos.left + pos.width / 2 - actualWidth + 15;
+ }
+ }
+
+ $tip.css(tp).addClass('tipsy-' + gravity);
+ $tip.find('.tipsy-arrow')[0].className = 'tipsy-arrow tipsy-arrow-' + gravity.charAt(0);
+ if (this.options.className) {
+ $tip.addClass(maybeCall(this.options.className, this.$element[0]));
+ }
+
+ if (this.options.fade) {
+ $tip.stop().css({opacity: 0, display: 'block', visibility: 'visible'}).animate({opacity: this.options.opacity});
+ } else {
+ $tip.css({visibility: 'visible', opacity: this.options.opacity});
+ }
+ }
+ },
+
+ hide: function() {
+ if (this.options.fade) {
+ this.tip().stop().fadeOut(function() { $(this).remove(); });
+ } else {
+ this.tip().remove();
+ }
+ },
+
+ fixTitle: function() {
+ var $e = this.$element;
+ if ($e.attr('title') || typeof($e.attr('original-title')) != 'string') {
+ $e.attr('original-title', $e.attr('title') || '').removeAttr('title');
+ }
+ },
+
+ getTitle: function() {
+ var title, $e = this.$element, o = this.options;
+ this.fixTitle();
+ var title, o = this.options;
+ if (typeof o.title == 'string') {
+ title = $e.attr(o.title == 'title' ? 'original-title' : o.title);
+ } else if (typeof o.title == 'function') {
+ title = o.title.call($e[0]);
+ }
+ title = ('' + title).replace(/(^\s*|\s*$)/, "");
+ return title || o.fallback;
+ },
+
+ tip: function() {
+ if (!this.$tip) {
+ this.$tip = $('').html('');
+ this.$tip.data('tipsy-pointee', this.$element[0]);
+ }
+ return this.$tip;
+ },
+
+ validate: function() {
+ if (!this.$element[0].parentNode) {
+ this.hide();
+ this.$element = null;
+ this.options = null;
+ }
+ },
+
+ enable: function() { this.enabled = true; },
+ disable: function() { this.enabled = false; },
+ toggleEnabled: function() { this.enabled = !this.enabled; }
+ };
+
+ $.fn.tipsy = function(options) {
+
+ if (options === true) {
+ return this.data('tipsy');
+ } else if (typeof options == 'string') {
+ var tipsy = this.data('tipsy');
+ if (tipsy) tipsy[options]();
+ return this;
+ }
+
+ options = $.extend({}, $.fn.tipsy.defaults, options);
+
+ function get(ele) {
+ var tipsy = $.data(ele, 'tipsy');
+ if (!tipsy) {
+ tipsy = new Tipsy(ele, $.fn.tipsy.elementOptions(ele, options));
+ $.data(ele, 'tipsy', tipsy);
+ }
+ return tipsy;
+ }
+
+ function enter() {
+ var tipsy = get(this);
+ tipsy.hoverState = 'in';
+ if (options.delayIn == 0) {
+ tipsy.show();
+ } else {
+ tipsy.fixTitle();
+ setTimeout(function() { if (tipsy.hoverState == 'in') tipsy.show(); }, options.delayIn);
+ }
+ };
+
+ function leave() {
+ var tipsy = get(this);
+ tipsy.hoverState = 'out';
+ if (options.delayOut == 0) {
+ tipsy.hide();
+ } else {
+ setTimeout(function() { if (tipsy.hoverState == 'out') tipsy.hide(); }, options.delayOut);
+ }
+ };
+
+ if (!options.live) this.each(function() { get(this); });
+
+ if (options.trigger != 'manual') {
+ var binder = options.live ? 'live' : 'bind',
+ eventIn = options.trigger == 'hover' ? 'mouseenter' : 'focus',
+ eventOut = options.trigger == 'hover' ? 'mouseleave' : 'blur';
+ this[binder](eventIn, enter)[binder](eventOut, leave);
+ }
+
+ return this;
+
+ };
+
+ $.fn.tipsy.defaults = {
+ className: null,
+ delayIn: 0,
+ delayOut: 0,
+ fade: false,
+ fallback: '',
+ gravity: 'n',
+ html: false,
+ live: false,
+ offset: 0,
+ opacity: 0.8,
+ title: 'title',
+ trigger: 'hover'
+ };
+
+ $.fn.tipsy.revalidate = function() {
+ $('.tipsy').each(function() {
+ var pointee = $.data(this, 'tipsy-pointee');
+ if (!pointee || !isElementInDOM(pointee)) {
+ $(this).remove();
+ }
+ });
+ };
+
+ // Overwrite this method to provide options on a per-element basis.
+ // For example, you could store the gravity in a 'tipsy-gravity' attribute:
+ // return $.extend({}, options, {gravity: $(ele).attr('tipsy-gravity') || 'n' });
+ // (remember - do not modify 'options' in place!)
+ $.fn.tipsy.elementOptions = function(ele, options) {
+ return $.metadata ? $.extend({}, options, $(ele).metadata()) : options;
+ };
+
+ $.fn.tipsy.autoNS = function() {
+ return $(this).offset().top > ($(document).scrollTop() + $(window).height() / 2) ? 's' : 'n';
+ };
+
+ $.fn.tipsy.autoWE = function() {
+ return $(this).offset().left > ($(document).scrollLeft() + $(window).width() / 2) ? 'e' : 'w';
+ };
+
+ /**
+ * yields a closure of the supplied parameters, producing a function that takes
+ * no arguments and is suitable for use as an autogravity function like so:
+ *
+ * @param margin (int) - distance from the viewable region edge that an
+ * element should be before setting its tooltip's gravity to be away
+ * from that edge.
+ * @param prefer (string, e.g. 'n', 'sw', 'w') - the direction to prefer
+ * if there are no viewable region edges effecting the tooltip's
+ * gravity. It will try to vary from this minimally, for example,
+ * if 'sw' is preferred and an element is near the right viewable
+ * region edge, but not the top edge, it will set the gravity for
+ * that element's tooltip to be 'se', preserving the southern
+ * component.
+ */
+ $.fn.tipsy.autoBounds = function(margin, prefer) {
+ return function() {
+ var dir = {ns: prefer[0], ew: (prefer.length > 1 ? prefer[1] : false)},
+ boundTop = $(document).scrollTop() + margin,
+ boundLeft = $(document).scrollLeft() + margin,
+ $this = $(this);
+
+ if ($this.offset().top < boundTop) dir.ns = 'n';
+ if ($this.offset().left < boundLeft) dir.ew = 'w';
+ if ($(window).width() + $(document).scrollLeft() - $this.offset().left < margin) dir.ew = 'e';
+ if ($(window).height() + $(document).scrollTop() - $this.offset().top < margin) dir.ns = 's';
+
+ return dir.ns + (dir.ew ? dir.ew : '');
+ }
+ };
+
+})(jQuery);
diff --git a/templates/base.html b/templates/base.html
index d1b3345..e54b6f9 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -4,10 +4,12 @@
+
{% block head %}{% endblock %}
+