Changeset - 6a5a15855897
[Not reviewed]
version_5
0 5 0
Dennis Fink - 10 years ago 2015-08-26 18:32:29
dennis.fink@c3l.lu
Remove status from model

tor_status was renamed to status. Because people are only interested in if the
tor process is actually running!
5 files changed with 5 insertions and 16 deletions:
0 comments (0 inline, 0 general)
ennstatus/api/model.py
Show inline comments
 
import ipaddress
 
import json
 
import functools
 
import statistics
 

	
 
from pathlib import Path
 
from datetime import datetime
 

	
 
import jsonschema
 
import strict_rfc3339
 
import requests
 

	
 
from flask import current_app
 
from pkg_resources import resource_filename
 

	
 
schema = json.load(
 
    open(
 
        resource_filename('ennstatus.api', 'schema/server.json'),
 
        encoding='utf-8'
 
    )
 
)
 

	
 
validate = functools.partial(
 
    jsonschema.validate,
 
    schema=schema,
 
    format_checker=jsonschema.FormatChecker()
 
)
 

	
 

	
 
def calculate_weight(data):
 

	
 
    obj = {}
 

	
 
    for subkey in ('1_week', '1_month', '3_months', '1_year', '5_years'):
 

	
 
        subdata = data[subkey]
 
        factor = subdata['factor']
 

	
 
        values = [x * factor for x in subdata['values'] if x is not None]
 

	
 
        if values:
 
            obj[subkey] = statistics.mean(values) * 100
 
        else:
 
            obj[subkey] = None
 

	
 
    return obj
 

	
 

	
 
class ServerEncoder(json.JSONEncoder):
 

	
 
    def default(self, obj):
 

	
 
        if isinstance(obj, (ipaddress.IPv4Address, ipaddress.IPv6Address)):
 
            return str(obj)
 

	
 
        if isinstance(obj, datetime):
 
            return strict_rfc3339.timestamp_to_rfc3339_utcoffset(
 
                obj.timestamp()
 
            )
 

	
 
        return json.JSONEncoder.default(self, obj)
 

	
 

	
 
class ServerDecoder(json.JSONDecoder):
 

	
 
    def decode(self, json_string):
 

	
 
        default_obj = super().decode(json_string)
 

	
 
        for key in ('ip', 'ip6'):
 
            if key in default_obj:
 
                current_app.logger.debug('{}: {}'.format(key, default_obj[key]))
 
                default_obj[key] = ipaddress.ip_address(default_obj[key])
 

	
 
        current_app.logger.debug('Loading last_updated')
 
        default_obj['last_updated'] = datetime.fromtimestamp(
 
            strict_rfc3339.rfc3339_to_timestamp(default_obj['last_updated'])
 
        )
 

	
 
        return default_obj
 

	
 

	
 
class Server:
 

	
 
    def __init__(self, *args, **kwargs):
 

	
 
        self.name = kwargs['name']
 
        self.type = kwargs['type']
 
        self.status = kwargs.get('status')
 
        self.tor_status = kwargs.get('tor_status')
 
        self.fingerprint = kwargs['fingerprint']
 
        self.last_updated = kwargs['last_updated']
 
        self.country = kwargs['country']
 
        self.bandwith = kwargs.get('bandwith')
 

	
 
        if self.type == 'bridge':
 
            self.obfs = kwargs.get('obfs')
 
            self.fteproxy = kwargs.get('fteproxy')
 
            self.flashproxy = kwargs.get('flashproxy')
 
            self.meek = kwargs.get('meek')
 
        else:
 
            self.ip = kwargs['ip']
 

	
 
            if 'ip6' in kwargs:
 
                self.ip6 = kwargs['ip6']
 

	
 
            default_weights = {
 
                '1_week': None,
 
                '1_month': None,
 
                '3_months': None,
 
                '1_year': None,
 
                '5_years': None
 
            }
 

	
 
            self.mean_consensus_weight = kwargs.get(
 
                'mean_consensus_weight',
 
                default_weights
 
            )
 
            self.mean_guard_probability = kwargs.get(
 
                'mean_guard_probability',
 
                default_weights
 
            )
 
            self.mean_exit_probability = kwargs.get(
 
                'mean_exit_probability',
 
                default_weights
 
            )
 
            self.mean_consensus_weight_fraction = kwargs.get(
 
                'mean_consensus_weight_fraction',
 
                default_weights
 
            )
 
            self.mean_middle_probability = kwargs.get(
 
                'mean_middle_probability',
 
                default_weights
 
            )
 

	
 
    @classmethod
 
    def from_file_by_name(cls, name):
 

	
 
        filepath = Path('data') / (name.lower() + '.json')
 

	
 
        current_app.logger.info('Loading {}'.format(str(filepath)))
 
        if filepath.exists() and filepath.is_file():
 
            try:
 
                with filepath.open(encoding='utf-8') as f:
 
                    data = json.load(f, cls=ServerDecoder)
 
            except (IOError, ValueError):
 
                current_app.logger.error('IOError or ValueError')
 
                return False
 
            else:
 
                return cls(**data)
 
        else:
 
            current_app.logger.error('File error!')
 
            return False
 

	
 
    @classmethod
 
    def from_json(cls, server):
 

	
 
        try:
 
            if cls.check_json_format(json.loads(server)):
 
                decoded = json.loads(server, cls=ServerDecoder)
 
                return cls(**decoded)
 
        except (jsonschema.ValidationError, ValueError) as e:
 
            raise e
 

	
 
    @classmethod
 
    def from_dict(cls, server):
 
        return cls.from_json(json.dumps(server))
 

	
 
    def json(self):
 
        return json.dumps(self.__dict__, cls=ServerEncoder)
 

	
 
    @staticmethod
 
    def check_json_format(server):
 

	
 
        try:
 
            validate(server)
 
        except jsonschema.ValidationError as e:
 
            raise e
 

	
 
        for key in ('ip', 'ip6'):
 
            if key in server:
 
                address = ipaddress.ip_address(server[key])
 

	
 
                if any({address.is_private, address.is_multicast,
 
                        address.is_unspecified, address.is_reserved,
 
                        address.is_loopback}):
 
                    raise ValueError('{} is not accepted!\n'.format(key))
 

	
 
        return True
 

	
 
    def save(self):
 

	
 
        filepath = Path('data') / (self.name.lower() + '.json')
 

	
 
        try:
 
            with filepath.open(mode='w', encoding='utf-8') as f:
 
                json.dump(self.__dict__, f, cls=ServerEncoder)
 
        except Exception as e:
 
            raise e
 

	
 
    def update_weights(self):
 

	
 
        if self.type not in ('exit', 'relay'):
 
            raise NotImplementedError
 

	
 
        url = 'https://onionoo.torproject.org/weights?lookup={}'.format(
 
            self.fingerprint
 
        )
 

	
 
        data = requests.get(url)
 

	
 
        try:
 
            data.raise_for_status()
 
        except requests.HTTPError as e:
 
            raise e
 
        else:
 
            data = data.json()['relays'][0]
 

	
 
        self.mean_consensus_weight = calculate_weight(data['consensus_weight'])
 
        self.mean_exit_probability = calculate_weight(data['exit_probability'])
 
        self.mean_guard_probability = calculate_weight(
 
            data['guard_probability']
 
        )
 
        self.mean_middle_probability = calculate_weight(
 
            data['middle_probability']
 
        )
 
        self.mean_consensus_weight_fraction = calculate_weight(
 
            data['consensus_weight_fraction']
 
        )
 

	
 
    def check_status(self):
 

	
 
        now = datetime.utcnow()
 
        delta = now - self.last_updated
 

	
 
        if delta.seconds >= 3600:
 
            self.status = False
 
            self.tor_status = False
 
            return False
 
        else:
 
            return True
 
        elif delta.seconds >= 600:
 
            self.status = None
ennstatus/api/schema/server.json
Show inline comments
 
{
 
    "title": "Relay/Exit schema",
 
    "$schema": "http://json-schema.org/draft-04/schema#",
 
    "type": "object",
 
    "properties": {
 
        "name": {
 
            "type": "string"
 
        },
 
        "status": {
 
            "$ref": "#/definitions/status_data"
 
        },
 
        "tor_status": {
 
            "$ref": "#/definitions/status_data"
 
        },
 
        "country": {
 
            "type": "string",
 
            "enum": [
 
                "Afghanistan", "Aland Islands", "Albania", "Algeria", 
 
                "American Samoa", "Andorra", "Angola", "Anguilla",
 
                "Anonymous Proxy", "Antarctica", "Antigua and Barbuda",
 
                "Argentina", "Armenia", "Aruba", "Asia/Pacific Region",
 
                "Australia", "Austria", "Azerbaijan", "Bahamas", "Bahrain",
 
                "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize",
 
                "Benin", "Bermuda", "Bhutan", "Bolivia",
 
                "Bonaire, Sint Eustatius and Saba", "Bosnia and Herzegovina",
 
                "Botswana", "Bouvet Island", "Brazil",
 
                "British Indian Ocean Territory", "Brunei Darussalam",
 
                "Bulgaria", "Burkina Faso", "Burundi", "Cambodia",
 
                "Cameroon", "Canada", "Cape Verde", "Cayman Islands",
 
                "Central African Republic", "Chad", "Chile", "China",
 
                "Christmas Island", "Cocos (Keeling) Islands", "Colombia",
 
                "Comoros", "Congo", "Congo, The Democratic Republic of the",
 
                "Cook Islands", "Costa Rica", "Cote D'Ivoire", "Croatia",
 
                "Cuba", "Cyprus", "Czech Republic", "Denmark", "Djibouti",
 
                "Dominica", "Dominican Republic", "Ecuador", "Egypt",
 
                "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia",
 
                "Ethiopia", "Europe", "Falkland Islands (Malvinas)",
 
                "Faroe Islands", "Fiji", "Finland", "France",
 
                "France, Metropolitan", "French Guiana", "French Polynesia",
 
                "French Southern Territories", "Gabon", "Gambia", "Georgia",
 
                "Germany", "Ghana", "Gibraltar", "Greece", "Greenland",
 
                "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey",
 
                "Guinea", "Guinea-Bissau", "Guyana", "Haiti",
 
                "Heard Island and McDonald Islands",
 
                "Holy See (Vatican City State)", "Honduras", "Hong Kong",
 
                "Hungary", "Iceland", "India", "Indonesia",
 
                "Iran, Islamic Republic of", "Iraq", "Ireland", "Isle of Man",
 
                "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan",
 
                "Kazakhstan", "Kenya", "Kiribati",
 
                "Korea, Democratic People's Republic of",
 
                "Korea, Republic of", "Kuwait", "Kyrgyzstan",
 
                "Lao People's Democratic Republic", "Latvia", "Lebanon",
 
                "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania",
 
                "Luxembourg", "Macau", "Macedonia", "Madagascar", "Malawi",
 
                "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands",
 
                "Martinique", "Mauritania", "Mauritius", "Mayotte", "Mexico",
 
                "Micronesia, Federated States of", "Moldova, Republic of",
 
                "Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco",
 
                "Mozambique", "Myanmar", "Namibia", "Nauru", "Nepal",
 
                "Netherlands", "Netherlands Antilles", "New Caledonia",
 
                "New Zealand", "Nicaragua", "Niger", "Nigeria", "Niue",
 
                "Norfolk Island", "Northern Mariana Islands", "Norway",
 
                "Oman", "Other", "Pakistan", "Palau", "Palestinian Territory",
 
                "Panama", "Papua New Guinea", "Paraguay", "Peru",
 
                "Philippines", "Pitcairn Islands", "Poland", "Portugal",
 
                "Puerto Rico", "Qatar", "Reunion", "Romania",
 
                "Russian Federation", "Rwanda", "Saint Barthelemy",
 
                "Saint Helena", "Saint Kitts and Nevis", "Saint Lucia",
 
                "Saint Martin", "Saint Pierre and Miquelon",
 
                "Saint Vincent and the Grenadines", "Samoa", "San Marino",
 
                "Sao Tome and Principe", "Satellite Provider", "Saudi Arabia",
 
                "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore",
 
                "Slovakia", "Slovenia", "Solomon Islands", "Somalia",
 
                "South Africa", "South Georgia and the South Sandwich Islands",
 
                "South Sudan", "Spain", "Sri Lanka", "Sudan", "Suriname",
 
                "Svalbard and Jan Mayen", "Swaziland", "Sweden", "Switzerland",
 
                "Syrian Arab Republic", "Taiwan", "Tajikistan",
 
                "Tanzania, United Republic of", "Thailand", "Timor-Leste",
 
                "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia",
 
                "Turkey", "Turkmenistan", "Turks and Caicos Islands", "Tuvalu",
 
                "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom",
 
                "United States", "United States Minor Outlying Islands",
 
                "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela", "Vietnam",
 
                "Virgin Islands, British", "Virgin Islands, U.S.",
 
                "Wallis and Futuna", "Western Sahara", "Yemen", "Zambia",
 
                "Zimbabwe"
 
            ]
 
        },
 
        "fingerprint": {
 
            "type": "string",
 
            "pattern": "^[a-zA-Z0-9]{40}$"
 
        },
 
        "last_updated": {
 
            "type": "string",
 
            "format": "date-time"
 
        },
 
        "bandwith": {
 
            "type": [
 
                "string",
 
                "null"
 
            ],
 
            "default": null
 
        },
 
        "type": {
 
            "type": "string"
 
        }
 
    },
 
    "required": [
 
        "name",
 
        "status",
 
        "tor_status",
 
        "last_updated",
 
        "country",
 
        "fingerprint",
 
        "type"
 
    ],
 
    "definitions": {
 
        "weights": {
 
            "type": "object",
 
            "properties": {
 
                "1_week": {
 
                    "$ref": "#/definitions/weights_data"
 
                },
 
                "1_month": {
 
                    "$ref": "#/definitions/weights_data"
 
                },
 
                "3_months": {
 
                    "$ref": "#/definitions/weights_data"
 
                },
 
                "1_year": {
 
                    "$ref": "#/definitions/weights_data"
 
                },
 
                "5_years": {
 
                    "$ref": "#/definitions/weights_data"
 
                }
 
            },
 
            "required": [
 
                "1_week",
 
                "1_month",
 
                "3_months",
 
                "1_year",
 
                "5_years"
 
            ],
 
            "additionalProperties": false
 
        },
 
        "weights_data": {
 
            "type": [
 
                "number",
 
                "null"
 
            ],
 
            "default": null
 
        },
 
        "status_data": {
 
            "type": [
 
                "boolean",
 
                "null"
 
            ],
 
            "default": null
 
        }
 
    },
 
    "anyOf": [
 
        {
 
            "properties": {
 
                "type": {
 
                    "enum": [
 
                        "exit",
 
                        "relay"
 
                    ]
 
                },
 
                "ip": {
 
                    "type": "string",
 
                    "format": "ipv4"
 
                },
 
                "ip6": {
 
                    "type": "string",
 
                    "format": "ipv6"
 
                },
 
                "mean_consensus_weight": {
 
                    "$ref": "#/definitions/weights"
 
                },
 
                "mean_guard_probability": {
 
                    "$ref": "#/definitions/weights"
 
                },
 
                "mean_exit_probability": {
 
                    "$ref": "#/definitions/weights"
 
                },
 
                "mean_middle_probability": {
 
                    "$ref": "#/definitions/weights"
 
                },
 
                "mean_consensus_weight_fraction": {
 
                    "$ref": "#/definitions/weights"
 
                }
 
            },
 
            "required": [
 
                "ip"
 
            ],
 
            "not": {
 
                "required": [
 
                    "obfs",
 
                    "fteproxy",
 
                    "flashproxy",
 
                    "meek"
 
                ]
 
            }
 
        },
 
        {
 
            "properties": {
ennstatus/api/views.py
Show inline comments
 
import ipaddress
 
import json
 

	
 
from datetime import datetime
 

	
 
from flask import (Blueprint, request, current_app, jsonify, render_template,
 
                   abort)
 

	
 
import strict_rfc3339
 
import pygeoip
 

	
 
from ennstatus.status.functions import (single_server, all_servers,
 
                                        all_servers_by_type)
 
from .model import Server
 

	
 

	
 
api_page = Blueprint('api', __name__)
 
gi4 = pygeoip.GeoIP('/usr/share/GeoIP/GeoIP.dat', pygeoip.MEMORY_CACHE)
 
gi6 = pygeoip.GeoIP('/usr/share/GeoIP/GeoIPv6.dat', pygeoip.MEMORY_CACHE)
 

	
 

	
 
@api_page.route('/update', methods=('POST',))
 
def update():
 

	
 
    current_app.logger.info('Handling update')
 
    if current_app.debug:
 
        accepted_ips = ['127.0.0.1']
 
    else:
 
        accepted_ips = current_app.config.get('ENNSTATUS_ACCEPTED_IPS', [])
 

	
 
    if request.remote_addr not in accepted_ips:
 
        current_app.logger.warn('Unallowed IP %s tried to update data!'
 
                                % request.remote_addr)
 
        return 'IP not allowed!\n', 403, {'Content-Type': 'text/plain'}
 

	
 
    data = request.get_json()
 

	
 
    if data is None:
 
        current_app.logger.info('No JSON data supplied!')
 
        return 'No JSON data supplied!\n', 400, {'Content-Type': 'text/plain'}
 

	
 
    if 'ip' in data:
 
        ip = data['ip']
 
    elif 'ip6' in data:
 
        ip = data['ip6']
 
    else:
 
        ip = request.remote_addr
 

	
 
    try:
 
        ip = ipaddress.ip_address(ip)
 
    except ipaddress.AddressValueError:
 
        return 'IP not allowed!\n', 403, {'Content-Type': 'text/plain'}
 

	
 
    if ip.version == 4:
 
        data['country'] = gi4.country_name_by_addr(str(ip))
 
    elif ip.version == 6:
 
        data['country'] = gi6.country_name_by_addr(str(ip))
 
    else:
 
        data['country'] = None
 

	
 
    data['last_updated'] = strict_rfc3339.timestamp_to_rfc3339_utcoffset(
 
        datetime.utcnow().timestamp()
 
    )
 
    data['status'] = True
 

	
 
    try:
 
        server = Server.from_json(json.dumps(data))
 
    except Exception as e:
 
        current_app.logger.warning(' '.join([str(e), str(data)]))
 
        return str(e), 409, {'Content-Type': 'text/plain'}
 

	
 
    if server.type in ('exit', 'relay'):
 
        server.update_weights()
 

	
 
    try:
 
        server.save()
 
    except Exception as e:
 
        current_app.logger.error(str(e))
 
        return str(e), 500, {'Content-Type': 'text/plain'}
 

	
 
    current_app.logger.info('Return result')
 
    return (server.json(), 201,
 
            {'Location': '/api/export/json/single?server_name=%s'
 
             % server.name})
 

	
 

	
 
@api_page.route('/export', defaults={'server_type': 'all',
 
                                     'export_format': 'json'})
 
@api_page.route('/export/<any("json", "xml"):export_format>',
 
                defaults={'server_type': 'all'})
 
@api_page.route(('/export/<any("json", "xml"):export_format>'
 
                 '/<any("all", "exit", "bridge", "relay", "single")'
 
                 ':server_type>'))
 
def export(export_format, server_type):
 

	
 
    current_app.logger.info('Handling export')
 
    if server_type == 'single':
 
        server_name = request.args.get('server_name', None)
 
        if server_name is not None:
 
            server = single_server(server_name)
 
            if server:
 
                if export_format == 'json':
 
                    current_app.logger.info('Returning server as json!')
 
                    return (server.json(), 200,
 
                            {'Content-Type': 'application/json'})
 
                else:
 
                    current_app.logger.info('Returning server as xml!')
 
                    return (
 
                        render_template(
 
                            'api/export/xml/single_server.xml',
 
                            server=server),
 
                        200, {'Content-Type': 'text/xml'})
 
            else:
 
                current_app.logger.warning('Server not found!')
 
                return ('Server not found!\n',
 
                        404, {'Content-Type': 'text/plain'})
 
        else:
 
            current_app.logger.warning('No server_name specified!')
 
            return ('No server_name specified!\n',
 
                    400, {'Content-Type': 'text/plain'})
 

	
 
    else:
 
        if server_type == 'all':
 
            current_app.logger.info('Getting all servers!')
 
            servers = [server.json() for server in all_servers()]
 
        else:
 
            current_app.logger.info('Getting all %s!' % server_type)
 
            servers = list(all_servers_by_type(server_type.lower()))
 

	
 
        if export_format == 'json':
 
            current_app.logger.info('Returning as json!')
 
            return str(servers)
 
        else:
 
            current_app.logger.info('Returning as xml!')
 
            return (render_template('api/export/xml/network.xml',
 
                                    servers=servers),
 
                    200, {'Content-Type': 'text/xml'})
ennstatus/templates/api/export/xml/server.xml
Show inline comments
 
<server>
 
    <type>{{ server.type }}</type>
 
    <name>{{ server.name }}</name>
 
    <status>{{ server.status }}</status>
 
    <tor_status>{{ server.tor_status }}</tor_status>
 
    <country>{{ server.country }}</country>
 
    <last_updated>{{ server.last_updated }}</last_updated>
 
    <fingerprint>{{ serverfingerprint }}</fingerprint>
 
    {% if server.type == 'bridge' %}
 
    <obfs>{{ server.obfs }}</obfs>
 
    <fteproxy>{{ server.fteproxy }}</fteproxy>
 
    <flashproxy>{{ server.flashproxy }}</flashproxy>
 
    <meek>{{ server.meek }}</meek>
 
    {% else %}
 
    <ip>{{ server.ip }}</ip>
 
      {% if 'ip6' in server %}
 
        <ip6>{{ server.ip6</ip6>
 
      {% endif %}
 
    {% endif %}
 
  </server>
ennstatus/templates/status/macros.html
Show inline comments
 
{% macro colorize_status(status) %}
 
  {% if status %}
 
    {% set color = "text-success" %}
 
  {% elif status is none %}
 
    {% set color = "text-warning" %}
 
  {% else %}
 
    {% set color = "text-danger" %}
 
  {% endif %}
 
  <p class={{ color }}>{{ status }}</p>
 
{% endmacro %}
 

	
 
{% macro colorize_obfs(obfs) %}
 
  {% if obfs %}
 
    {% set color = "text-success" %}
 
  {% else %}
 
    {% set color = "text-danger" %}
 
  {% endif %}
 
  <p class={{ color }}>{{ obfs }}</p>
 
{% endmacro %}
 

	
 
{% macro create_country(country) %}
 
  {% set country_class = "flag-" + country|lower|replace(' ', '-') %}
 
  <i class={{ country_class }}></i> {{ country|title }}
 
{% endmacro %}
 

	
 
{% macro create_fingerprint(fingerprint, server_type) %}
 
  {% if server_type in ('exit', 'relay') %}
 
    {% set url_type = 'relay' %}
 
  {% else %}
 
    {% set url_type = 'bridge' %}
 
  {% endif %}
 
  {% if '.onion' in request.url_root %}
 
    {% set url_root = '//%s' % config['ENNSTATUS_GLOBE_ONION_ADDRESS'] %}
 
  {% elif '.bit' in request.url_root %}
 
    {% set url_root = '//%s' % config['ENNSTATUS_GLOBE_BIT_ADDRESS'] %}
 
  {% else %}
 
    {% set url_root = '//globe.enn.lu' %}
 
  {% endif %}
 
  <a href="{{ url_root }}/#/{{ url_type }}/{{ fingerprint }}">{{ fingerprint|upper}}</a>
 
{% endmacro %}
 

	
 
{% macro create_name(name) %}
 
  <a href="http://{{ name|lower }}.enn.lu">{{ name }}</a>
 
{% endmacro %}
 

	
 
{% macro create_date(date) %}
 
  {{ moment(date).format(config['ENNSTATUS_MOMENTJS_FORMAT']) }}
 
  <noscript>{{ date.strftime(config['ENNSTATUS_STRFTIME_FORMAT']) }}</noscript>
 
{% endmacro %}
 

	
 
{% macro create_server_table(server_type, servers) %}
 
  {% if server_type in ('exit', 'relay') %}
 
    {% set headers = ('#', 'Name', 'IP', 'IP6', 'Server Status', 'Tor Status', 'Country', 'Fingerprint', 'Last Updated <noscript>(UTC)</noscript>') %}
 
    {% set headers = ('#', 'Name', 'IP', 'IP6', 'Tor Status', 'Country', 'Fingerprint', 'Last Updated <noscript>(UTC)</noscript>') %}
 
  {% else %}
 
    {% set headers = ('#', 'Name', 'Server Status', 'Tor Status', 'Country', 'Fingerprint', 'OBFS', 'FTEProxy', 'Flashproxy', 'meek', 'Last Updated <noscript>(UTC)</noscript>') %}
 
    {% set headers = ('#', 'Name', 'Tor Status', 'Country', 'Fingerprint', 'OBFS', 'FTEProxy', 'Flashproxy', 'meek', 'Last Updated <noscript>(UTC)</noscript>') %}
 
  {% endif %}
 
  <h2>{{ server_type|title }}</h2>
 
  <table class="table table-bordered table-striped sortable">
 
    <thead>
 
      <tr>
 
        {% for name in headers %}
 
          <th>{{ name|safe }}</th>
 
        {% endfor %}
 
      </tr>
 
    </thead>
 
    <tbody>
 
      {% for server in servers|sort(attribute='name')|sort(attribute='country') %}
 
      <tr {% if server.name in config['ENNSTATUS_BRIDGE_PROGRAM'] %}class="info"{% endif %}>
 
          <td>{{ loop.index }}</td>
 
          {% if server_type in ('exit', 'relay') %}
 
            <td>{{ create_name(server.name) }}</td>
 
            <td>{{ server.ip }}</td>
 
            <td>{{ server.ip6 or 'N/A' }}</td>
 
          {% else %}
 
            <td>
 
              {% if server.name in config['ENNSTATUS_BRIDGE_PROGRAM'] %}
 
                <a href="{{ url_for('root.bridgeprogram') }}">{{ server.name }}</a>
 
              {% else %}
 
                {{ server.name }}
 
              {% endif %}
 
            </td>
 
          {% endif %}
 
          {% for status in (server.status, server.tor_status) %}
 
            <td>{{ colorize_status(status) }}</td>
 
          {% endfor %}
 
          <td>{{ colorize_status(server.status) }}</td>
 
          <td>{{ create_country(server.country) }}</td>
 
          <td>{{ create_fingerprint(server.fingerprint, server.type) }}</td>
 
          {% if server_type == 'bridge' %}
 
            <td>{{ colorize_obfs(server.obfs) }}</td>
 
            <td>{{ colorize_obfs(server.fteproxy) }}</td>
 
            <td>{{ colorize_obfs(server.flashproxy) }}</td>
 
            <td>{{ colorize_obfs(server.meek) }}</td>
 
          {% endif %}
 
          <td>{{ create_date(server.last_updated) }}
 
          </td>
 
        </tr>
 
      {% endfor %}
 
    </tbody>
 
  </table>
 
  {% if server_type == 'bridge' %}
 
    <div class="alert alert-info alert-dismissible" role="alert">
 
      <button type="button" class="close" data-dismiss="alert"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
 
      <p>See our <a class="alert-link" href="{{ url_for('root.bridgeprogram') }}">bridge program</a>, if you want to fund some bridges!</p>
 
    </div>
 
  {% endif %}
 
{% endmacro %}
0 comments (0 inline, 0 general)