Changeset - 38ad4d4c0c74
[Not reviewed]
Dennis Fink - 9 years ago 2016-03-01 00:23:44
dennis.fink@c3l.lu
Added tooltips and added statistics data to own blueprint
8 files changed with 107 insertions and 38 deletions:
0 comments (0 inline, 0 general)
ennstatus/__init__.py
Show inline comments
 
# Ënnstatus
 
# Copyright (C) 2015  Dennis Fink
 
#
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
 
import os.path
 
import json
 

	
 
from flask import Flask, render_template
 
from flask.ext.bootstrap import Bootstrap
 
from flask.ext.wtf import CsrfProtect
 
from flask.ext.moment import Moment
 

	
 
from werkzeug.contrib.fixers import ProxyFix
 

	
 
import gnupg
 

	
 
bootstrap = Bootstrap()
 
csrf = CsrfProtect()
 
moment = Moment()
 

	
 
config_file = os.path.abspath('config.json')
 

	
 

	
 
def create_app():
 

	
 
    app = Flask(__name__)
 

	
 
    import ennstatus.config as config
 
    config.init_app(app)
 

	
 
    if not hasattr(app.config, 'from_json'):
 
        def from_json(file, silent=True):
 
            try:
 
                with open(file, encoding='utf-8') as json_file:
 
                    obj = json.load(json_file)
 
            except IOError:
 
                if silent:
 
                    return False
 
                raise
 

	
 
            for key in obj:
 
                if key.isupper():
 
                    app.config[key] = obj[key]
 

	
 
            return True
 

	
 
        app.config.from_json = from_json
 

	
 
    app.config.from_json(config_file)
 

	
 
    app.wsgi_app = ProxyFix(app.wsgi_app)
 

	
 
    bootstrap.init_app(app)
 
    csrf.init_app(app)
 
    moment.init_app(app)
 

	
 
    from .status.functions import mail
 
    mail.init_app(app)
 

	
 
    from .root.views import root_page
 
    app.register_blueprint(root_page)
 

	
 
    from .api.views import api_page
 
    app.register_blueprint(api_page, url_prefix='/api')
 

	
 
    from .donate.views import donate_page
 
    app.register_blueprint(donate_page, url_prefix='/donate')
 

	
 
    from .status.views import status_page
 
    app.register_blueprint(status_page, url_prefix='/status')
 

	
 
    from .statistics.views import statistics_page
 
    app.register_blueprint(statistics_page, url_prefix='/stats')
 
    app.register_blueprint(statistics_page, url_prefix='/statistics')
 

	
 
    from .data.views import data_page
 
    app.register_blueprint(data_page, url_prefix='/data')
 

	
 
    from .log import init_logging
 
    init_logging(app)
 

	
 
    if 'ENNSTATUS_GPG_HOME' in app.config:
 
        gpg = gnupg.GPG(gnupghome=app.config['ENNSTATUS_GPG_HOME'])
 
        gpg.encoding = 'utf-8'
 
        app.extensions['gnupg'] = gpg
 
    else:
 
        app.extensions['gnupg'] = False
 

	
 
    @app.errorhandler(404)
 
    def page_not_found(e):
 
        return render_template('errorpages/404.html')
 

	
 
    return app
ennstatus/data/__init__.py
Show inline comments
 
new file 100644
 
# Ënnstatus
 
# Copyright (C) 2015  Dennis Fink
 
#
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
ennstatus/data/views.py
Show inline comments
 
new file 100644
 
# Ënnstatus
 
# Copyright (C) 2015  Dennis Fink
 
#
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
 
from collections import defaultdict
 

	
 
from flask import Blueprint, jsonify
 

	
 
from ennstatus.status.functions import split_all_servers_to_types
 

	
 
data_page = Blueprint('data', __name__)
 

	
 

	
 
@data_page.route('/worldmap')
 
def worldmap():
 
    servers = split_all_servers_to_types()
 
    countries = {}
 

	
 
    for key, value in servers.items():
 
        for server in value:
 
            if server.country not in countries:
 
                countries[server.country] = defaultdict(int)
 
            countries[server.country][server.type] += 1
 
            countries[server.country]['total'] += 1
 

	
 
    maximum = max(i['total'] for i in countries.values())
 

	
 
    countries['max'] = maximum
 

	
 
    return jsonify(countries)
ennstatus/static/css/map.css
Show inline comments
 
/* Ënnstatus
 
   Copyright (C) 2015  Dennis Fink
 

	
 
   This program is free software: you can redistribute it and/or modify
 
   it under the terms of the GNU General Public License as published by
 
   the Free Software Foundation, either version 3 of the License, or
 
   (at your option) any later version.
 

	
 
   This program is distributed in the hope that it will be useful,
 
   but WITHOUT ANY WARRANTY; without even the implied warranty of
 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
   GNU General Public License for more details.
 

	
 
   You should have received a copy of the GNU General Public License
 
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
.country {
 
	stroke: #000;
 
	stroke-linejoin: round;
 
	stroke-width: 0.1px;
 
}
 

	
 
.tooltip {
 
    color: #222;
 
    background: #fff;
 
    padding: .5em;
 
    text-shadow: #f5f5f5 0 1px 0;
 
    border-radius: 2px;
 
    box-shadow: 0px 0px 2px 0px #a6a6a6;
 
    opacity: 0.9;
 
    position: absolute;
 
}
ennstatus/static/js/worldmap.js
Show inline comments
 
/* Ënnstatus
 
   Copyright (C) 2015  Dennis Fink
 

	
 
   This program is free software: you can redistribute it and/or modify
 
   it under the terms of the GNU General Public License as published by
 
   the Free Software Foundation, either version 3 of the License, or
 
   (at your option) any later version.
 

	
 
   This program is distributed in the hope that it will be useful,
 
   but WITHOUT ANY WARRANTY; without even the implied warranty of
 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
   GNU General Public License for more details.
 

	
 
   You should have received a copy of the GNU General Public License
 
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 

	
 
d3.select(window).on("resize", throttle);
 
	
 
var zoom = d3.behavior.zoom()
 
	.scaleExtent([1, 9])
 
	.on("zoom", move);
 

	
 
var width = document.getElementById('chart').offsetWidth;
 
var height = width / 2;
 

	
 
var topo, projection, path, svg, g;
 

	
 
var graticule = d3.geo.graticule();
 

	
 
var tooltip = d3.select("#chart").append("div").attr("class", "tooltip hidden");
 

	
 
setup(width, height);
 

	
 
function setup(width, height) {
 
	projection = d3.geo.mercator()
 
		.translate([(width / 2), (height / 2)])
 
		.scale(width / 2 / Math.PI)
 

	
 
	path = d3.geo.path().projection(projection);
 

	
 
	svg = d3.select("#chart").append("svg")
 
		.attr("width", width)
 
		.attr("height", height)
 
		.call(zoom)
 
		.append("g");
 

	
 
	g = svg.append("g")
 
		.on("click", click);
 
}
 

	
 
d3.json("/static/data/world-topo-min.json", function(error, world) {
 
	var countries = topojson.feature(world, world.objects.countries).features;
 

	
 
	topo = countries;
 
	draw(topo);
 
});
 

	
 
function draw(topo) {
 

	
 
	svg.append("path")
 
		.datum(graticule)
 
		.attr("class", "graticule")
 
		.attr("d", path);
 

	
 
	g.append("path")
 
		.datum({type: "LineString", coordinates: [[-180, 0], [-90, 0], [0, 0], [90, 0], [180, 0]]})
 
		.attr("class", "equator")
 
		.attr("d", path);
 

	
 
	var country = g.selectAll(".country").data(topo);
 
	d3.json("/stats/data/worldmap", function(err, data) {
 
	d3.json("/data/worldmap", function(err, data) {
 

	
 

	
 
		var colorscale = d3.scale.threshold().domain([2, 4, 8, 16, 32, 48]).range(["#f2f0f7", "#dadaeb", "#bcbddc", "#9e9ac8", "#756bb1", "#54278f"])
 
		var colorscale = d3.scale.threshold().domain(d3.range(1, data.max+1)).range([
 
				"#f2f0f7",
 
				"#dadaeb",
 
				"#bcbddc",
 
				"#9e9ac8",
 
				"#756bb1",
 
				"#54278f"
 
		])
 

	
 
		country.enter().insert("path")
 
			.attr("class", "country")
 
			.attr("d", path)
 
			.attr("id", function(d, i) { return d.id; })
 
			.attr("title", function(d, i) { 
 
				if (d.properties.name in data) {
 
					return "<p>" + d.properties.name + "</p>" + data[d.properties.name];
 
				} else {
 
					return d.properties.name;
 
				}
 
			})
 
			.style("fill", function(d, i) { 
 
				if (d.properties.name in data) {
 
					return colorscale(data[d.properties.name]);
 
					return colorscale(data[d.properties.name]['total']);
 
				} else {
 
					return "#fdf6e3";
 
				}
 
			});
 

	
 
		var offsetL = document.getElementById('chart').offsetLet+20;
 
		var offsetT = document.getElementById('chart').offsetTop+10;
 
		var tooltip = d3.select('#chart').append('div')
 
			.attr('class', 'tooltip')
 

	
 
		country.on("mousemove", function(d, i) {
 
	
 
			var mouse = d3.mouse(svg.node()).map(function(d) { return parseInt(d); });
 

	
 
			tooltip.classed("hidden", false)
 
				.attr("style", "left:"+(mouse[0]+offsetL)+"px;top:"+(mouse[1]+offsetT)+"px")
 
				.html(function(d, i) {
 
				.attr("style", "left:"+(mouse[0]+40)+"px;top:"+mouse[1]+"px")
 
				.html(function() {
 
					if (d.properties.name in data) {
 
						return d.properties.name + " " + data[d.properties.name];
 
						var text = d.properties.name + "<br>Total servers: " + data[d.properties.name]['total']
 
						if ('bridge' in data[d.properties.name]) {
 
							text = text + "<br>Bridge servers: " + data[d.properties.name]['bridge']
 
						}
 
						if ('exit' in data[d.properties.name]) {
 
							text = text + "<br>Exit servers: " + data[d.properties.name]['exit']
 
						}
 
						if ('relay' in data[d.properties.name]) {
 
							text = text + "<br>Relay server: " + data[d.properties.name]['relay']
 
						}
 
						return text
 
					} else {
 
						return d.properties.name;
 
						return d.properties.name
 
					}
 
				})
 
		})
 
		.on("mouseout", function(d, i) {
 
			tooltip.classed("hidden", true);
 
		});
 

	
 

	
 
	});
 
};
 

	
 

	
 
function redraw() {
 
  width = document.getElementById('chart').offsetWidth;
 
  height = width / 2;
 
  d3.select('svg').remove();
 
  setup(width,height);
 
  draw(topo);
 
}
 

	
 

	
 
function move() {
 

	
 
  var t = d3.event.translate;
 
  var s = d3.event.scale; 
 
  zscale = s;
 
  var h = height/4;
 

	
 

	
 
  t[0] = Math.min(
 
    (width/height)  * (s - 1), 
 
    Math.max( width * (1 - s), t[0] )
 
  );
 

	
 
  t[1] = Math.min(
 
    h * (s - 1) + h * s, 
 
    Math.max(height  * (1 - s) - h * s, t[1])
 
  );
 

	
 
  zoom.translate(t);
 
  g.attr("transform", "translate(" + t + ")scale(" + s + ")");
 

	
 
  //adjust the country hover stroke width based on zoom level
 
  d3.selectAll(".country").style("stroke-width", 1.5 / s);
 

	
 
}
 

	
 

	
 
var throttleTimer;
 
function throttle() {
 
	  window.clearTimeout(throttleTimer);
 
	      throttleTimer = window.setTimeout(function() {
 
		            redraw();
 
			        }, 200);
 
}
 

	
 
function click() {
 
	  var latlon = projection.invert(d3.mouse(this));
 
	    console.log(latlon);
 
}
ennstatus/statistics/views.py
Show inline comments
 
# Ënnstatus
 
# Copyright (C) 2015  Dennis Fink
 
#
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
 
from collections import defaultdict
 

	
 
from flask import Blueprint, render_template, current_app, jsonify
 

	
 
from ennstatus.status.functions import split_all_servers_to_types
 
from flask import Blueprint, render_template
 

	
 
statistics_page = Blueprint('statistics', __name__)
 

	
 

	
 
@statistics_page.route('/worldmap')
 
def worldmap():
 
    return render_template('statistics/worldmap.html')
 

	
 

	
 
@statistics_page.route('/data/worldmap')
 
def data_worldmap():
 
    servers = split_all_servers_to_types()
 
    countries = defaultdict(int)
 

	
 
    for key, value in servers.items():
 
        for server in value:
 
            countries[server.country] += 1
 

	
 
    maximum = max(countries.values())
 

	
 
    countries['max'] = maximum
 

	
 
    return jsonify(countries)
ennstatus/templates/base.html
Show inline comments
 
@@ -15,132 +15,138 @@
 
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
#}
 

	
 
{% extends "bootstrap/base.html" %}
 

	
 
{% import 'macros.html' as base_macros with context %}
 

	
 
{% if '.onion' in request.url_root %}
 
  {% set wiki_url = config['ENNSTATUS_WIKI_ONION_ADDRESS'] %}
 
{% elif '.bit' in request.url_root %}
 
  {% set wiki_url = config['ENNSTATUS_WIKI_BIT_ADDRESS'] %}
 
{% else %}
 
  {% set wiki_url = "wiki.enn.lu" %}
 
{% endif %}
 

	
 
{% block title %}
 
  Frënn vun der Ënn - {{ title }}
 
{% endblock %}
 

	
 
{% block metas %}
 
  {{ super() }}
 
  <meta charset="utf-8">
 
  <meta name="application-name" content="Ënnstatus">
 
  <meta name="author" content="Frënn vun der Ënn">
 

	
 
  <meta name="twitter:card" content="summary" />
 
  <meta name="twitter:site" content="@FrennVunDerEnn" />
 
  <meta name="twitter:title" content="Frënn vun der Ënn A.S.B.L." />
 
  <meta name="twitter:description" content="Luxembourg based non-profit organization defending civil rights on the internet." />
 
  <meta name="twitter:image" content="{{ url_for('static', filename='images/logo/FVDE_logo_resize.png', _external=True) }}" />
 
{% endblock %}
 

	
 
{% block styles %}
 
  {{ super() }}
 
  <link rel="stylesheet" href="{{ url_for('static', filename='css/ennstatus.css') }}" />
 
  <link rel="shortcut icon" href="{{ url_for('static', filename='images/favicon.png') }}">
 
{% endblock %}
 

	
 
{% block body %}
 
<a href="#content" class="sr-only">Skip to main content</a>
 
<div class="container">
 
  {% block navbar %}
 
  <div class="navbar navbar-default">
 
    <div class="navbar-header">
 
      <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
 
        <span class="icon-bar"></span>
 
        <span class="icon-bar"></span>
 
        <span class="icon-bar"></span>
 
      </button>
 
      <img class="navbar-brand" src="{{ url_for('static', filename='images/logo/FVDE_logo_thumbnail.png') }}"></img>
 
      <a class="navbar-brand" href="{{ url_for('root.index') }}">Enn.lu</a>
 
    </div>
 
    <div class="navbar-collapse collapse">
 
      <ul class="nav navbar-nav">
 
        <li class="dropdown">
 
          <a href="#" class="dropdown-toggle" data-toggle="dropdown">About <b class="caret"></b></a>
 
          <ul class="dropdown-menu">
 
            <li><a href="//{{ wiki_url }}/doku.php?id=news" target="blank">News</a></li>
 
            <li class="divider"></li>
 
            <li><a href="{{ url_for('root.about') }}">Organization</a></li>
 
            <li><a href="{{ url_for('root.partners') }}">Partners</a></li>
 
          </ul>
 
        </li>
 
        <li class="dropdown">
 
          <a href="#" class="dropdown-toggle" data-toggle="dropdown">Services <b class="caret"></b></a>
 
          <ul class="dropdown-menu">
 
            <li><a href="{{ url_for('status.index') }}">Tor Servers</a></li>
 
            <li class="divider"></li>
 
            <li><a href="{{ url_for('root.mirrors') }}">Mirrors</a></li>
 
            <li><a href="//{{ wiki_url }}">Wiki</a></li>
 
            <li class="divider"></li>
 
            <li><a href="{{ url_for('root.ennstatus') }}">About Ënnstatus</a></li>
 
          </ul>
 
        </li>
 
        <li class="dropdown">
 
          <a href="#" class="dropdown-toggle" data-toggle="dropdown">Support <b class="caret"></b></a>
 
          <ul class="dropdown-menu">
 
            <li><a href="{{ url_for('donate.index') }}">Donate</a></li>
 
            <li><a href="{{ url_for('donate.received') }}">Donation history</a></li>
 
            <li><a href="{{ url_for('root.bridgeprogram')}}">Adopt a Bridge</a></li>
 
            <li class="divider"></li>
 
            <li><a href="{{ url_for('root.member') }}">Join Us</a></li>
 
            <li class="dropdown-submenu">
 
            </li>
 
          </ul>
 
        </li>
 
        <li class="dropdown">
 
          <a href="#" class="dropdown-toggle" data-toggle="dropdown">Contact <b class="caret"></b></a>
 
          <ul class="dropdown-menu">
 
            <li><a href="{{ url_for('root.contact') }}">General</a></li>
 
            <li><a href="{{ url_for('root.abuse') }}">Abuse</a></li>
 
            <li class="divider"></li>
 
            <li><a href="https://twitter.com/FrennVunDerEnn" target="blank">Twitter</a></li>
 
            <li><a href="http://lists.enn.lu/listinfo/discuss" target="blank">Mailing List</a></li>
 
          </ul>
 
        </li>
 
        <li class="dropdown">
 
          <a href="#" class="dropdown-toggle" data-toggle="dropdown">Statistics <b class="caret"></b></a>
 
          <ul class="dropdown-menu">
 
            <li><a href="{{ url_for('statistics.worldmap') }}">Worldmap</a></li>
 
          </ul>
 
        </li>
 
      </ul>
 
    </div>
 
  </div>
 
  {% endblock %}
 
  <div class="row" id="content">
 
  {% with messages = get_flashed_messages(with_categories=True) %}
 
    {% if messages %}
 
      <div class="col-md-12">
 
        {% for category, message in messages %}
 
          {{ base_macros.display_error(category, message) }}
 
        {% endfor %}
 
      </div>
 
    {% endif %}
 
  {% endwith %}
 
  {% block content %}
 
  {% endblock %}
 
  </div>
 
  <footer class="col-md-12">
 
    <hr style="margin-bottom: 0.5%;">
 
    <div class="text-center clearfix">
 
      <div class="pull-left">
 
        <a href="https://twitter.com/FrennVunDerEnn" target="blank">@FrennVunDerEnn</a>
 
      </div>
 
      <strong>Frënn vun der Enn a.s.b.l.</strong>  <em>(R.C.S. Luxembourg F 9.478)</em>
 
      <div class="pull-right">
 
        <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US" target="blank">CC-BY-NC-SA</a>
 
      </div>
 
    </div>
 
  </footer>
 
</div>
 

	
 
{% block scripts %}
 
  {{ super() }}
 
{% endblock %}
 

	
 
{% endblock %}
ennstatus/templates/statistics/worldmap.html
Show inline comments
 
{# Ënnstatus
 
   Copyright (C) 2015  Dennis Fink
 

	
 
   This program is free software: you can redistribute it and/or modify
 
   it under the terms of the GNU General Public License as published by
 
   the Free Software Foundation, either version 3 of the License, or
 
   (at your option) any later version.
 

	
 
   This program is distributed in the hope that it will be useful,
 
   but WITHOUT ANY WARRANTY; without even the implied warranty of
 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
   GNU General Public License for more details.
 

	
 
   You should have received a copy of the GNU General Public License
 
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
#}
 

	
 
{% extends "base.html"%}
 

	
 
{% set title = "index" %}
 

	
 
{% block styles %}
 
  {{ super() }}
 
  <link rel="stylesheet" href="{{ url_for('static', filename='css/map.css') }}" />
 
{% endblock %}
 

	
 
{% block content %}
 
  <div class="col-md-12">
 
    <h2>Worldmap</h2>
 
    <div id="chart">
 
    </div>
 
  </div>
 
{% endblock %}
 

	
 
{% block scripts %}
 
  {{ super() }}
 
  <script src="{{ url_for('static', filename='js/d3/d3.min.js') }}"></script>
 
  <script src="{{ url_for('static', filename='js/topojson/topojson.js') }}"></script>
 
  <script src="{{ url_for('static', filename='js/worldmap.js') }}"></script>
 
{% endblock %}
0 comments (0 inline, 0 general)