Initial import

This commit is contained in:
Iain Learmonth 2022-03-10 14:26:22 +00:00
commit 09f0b0672d
64 changed files with 3735 additions and 0 deletions

View file

@ -0,0 +1,31 @@
{% extends "base.html.j2" %}
{% from 'bootstrap5/form.html' import render_form %}
{% block content %}
<h1 class="h2 mt-3">Alarms</h1>
<h2 class="h3">Proxies</h2>
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th scope="col">Resource</th>
<th scope="col">Type</th>
<th scope="col">State</th>
</tr>
</thead>
<tbody>
{% for alarm in alarms %}
<tr class="bg-{% if alarm.alarm_state.name == "OK" %}success{% elif alarm.alarm_state.name == "UNKNOWN" %}dark{% else %}danger{% endif %} text-light">
{% if alarm.target == "proxy" %}
<td>Proxy: {{ alarm.proxy.url }} ({{ alarm.proxy.origin.domain_name }})</td>
{% elif alarm.target == "service/cloudfront" %}
<td>AWS CloudFront</td>
{% endif %}
<td>{{ alarm.alarm_type }}</td>
<td>{{ alarm.alarm_state.name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View file

@ -0,0 +1,162 @@
<!doctype html>
<html lang="en">
<head>
{% block head %}
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="generator" content="Bypass Censorship Portal">
{% block styles %}
<!-- Bootstrap CSS -->
{{ bootstrap.load_css() }}
{% endblock %}
<title>Bypass Censorship Portal</title>
<style>
.bd-placeholder-img {
font-size: 1.125rem;
text-anchor: middle;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
@media (min-width: 768px) {
.bd-placeholder-img-lg {
font-size: 3.5rem;
}
}
</style>
<!-- Custom styles for this template -->
<link href="/portal/static/portal.css" rel="stylesheet">
{% endblock %}
</head>
<body>
<header class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
<a class="navbar-brand col-md-3 col-lg-2 me-0 px-3" href="#">Bypass Censorship</a>
<button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-bs-toggle="collapse"
data-bs-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<form class="w-100" action="{{ url_for("portal.search") }}">
<input class="form-control form-control-dark w-100" type="text" name="query" placeholder="Search"
aria-label="Search">
</form>
<div class="navbar-nav">
<div class="nav-item text-nowrap">
<a class="nav-link px-3" href="#">{{ request.headers.get('X-User-Name', 'Default User') }}</a>
</div>
</div>
</header>
<div class="container-fluid">
<div class="row">
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
<div class="position-sticky pt-3">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link{% if section == "home" %} active{% endif %}"
href="{{ url_for("portal.portal_home") }}">
Home
</a>
</li>
</ul>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>Configuration</span>
</h6>
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link{% if section == "group" %} active{% endif %}"
href="{{ url_for("portal.view_groups") }}">
Groups
</a>
</li>
<li class="nav-item">
<a class="nav-link{% if section == "origin" %} active{% endif %}"
href="{{ url_for("portal.view_origins") }}">
Origins
</a>
</li>
<li class="nav-item">
<a class="nav-link{% if section == "list" %} active{% endif %}"
href="{{ url_for("portal.view_mirror_lists") }}">
Mirror Lists
</a>
</li>
<li class="nav-item">
<a class="nav-link{% if section == "bridgeconf" %} active{% endif %}"
href="{{ url_for("portal.view_bridgeconfs") }}">
Tor Bridges
</a>
</li>
</ul>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>Infrastructure</span>
</h6>
<ul class="nav flex-column">
<li class="nav-item">
<a class="disabled nav-link{% if section == "eotk" %} active{% endif %}"
href="#">
EOTK Instances
</a>
</li>
<li class="nav-item">
<a class="nav-link{% if section == "proxy" %} active{% endif %}"
href="{{ url_for("portal.view_proxies") }}">
Proxies
</a>
</li>
<li class="nav-item">
<a class="nav-link{% if section == "bridge" %} active{% endif %}"
href="{{ url_for("portal.view_bridges") }}">
Tor Bridges
</a>
</li>
</ul>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>Monitoring</span>
</h6>
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link{% if section == "alarm" %} active{% endif %}"
href="{{ url_for("portal.view_alarms") }}">
Alarms
</a>
</li>
</ul>
</div>
</nav>
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
{% with messages = get_flashed_messages(with_categories=true) %}
{% for category, message in messages %}
<div class="alert alert-{{ category }} mt-2">
{{ message }}
</div>
{% endfor %}
{% endwith %}
{% block content %}
{% endblock %}
</main>
</div>
</div>
{% block scripts %}
<!-- Optional JavaScript -->
{{ bootstrap.load_js() }}
{% endblock %}
<script src="https://cdn.jsdelivr.net/npm/feather-icons@4.28.0/dist/feather.min.js"
integrity="sha384-uO3SXW5IuS1ZpFPKugNNWqTZRRglnUJK6UAZ/gxOX80nxEkN9NcGZTftn6RzhGWE"
crossorigin="anonymous"></script>
</body>
</html>

View file

@ -0,0 +1,16 @@
{% extends "base.html.j2" %}
{% from 'bootstrap5/form.html' import render_form %}
{% from "tables.html.j2" import bridges_table %}
{% block content %}
<h1 class="h2 mt-3">Tor Bridge Configuration</h1>
<h2 class="h3">{{ bridgeconf.group.group_name }}: {{ bridgeconf.provider }}/{{ bridgeconf.method }}</h2>
<div style="border: 1px solid #666;" class="p-3">
{{ render_form(form) }}
</div>
<h3>Bridges</h3>
{{ bridges_table(bridgeconf.bridges) }}
{% endblock %}

View file

@ -0,0 +1,8 @@
{% extends "base.html.j2" %}
{% from "tables.html.j2" import bridgeconfs_table %}
{% block content %}
<h1 class="h2 mt-3">Tor Bridge Configurations</h1>
<a href="{{ url_for("portal.new_bridgeconf") }}" class="btn btn-success">Create new configuration</a>
{{ bridgeconfs_table(bridgeconfs) }}
{% endblock %}

View file

@ -0,0 +1,7 @@
{% extends "base.html.j2" %}
{% from "tables.html.j2" import bridges_table %}
{% block content %}
<h1 class="h2 mt-3">Tor Bridges</h1>
{{ bridges_table(bridges) }}
{% endblock %}

View file

@ -0,0 +1,6 @@
{% extends "base.html.j2" %}
{% block content %}
<h1 class="h2 mt-3">{{ header }}</h1>
<p>{{ message }}</p>
{% endblock %}

View file

@ -0,0 +1,18 @@
{% extends "base.html.j2" %}
{% from 'bootstrap5/form.html' import render_form %}
{% from "tables.html.j2" import origins_table %}
{% block content %}
<h1 class="h2 mt-3">Groups</h1>
<h2 class="h3">{{ group.group_name }}</h2>
<div style="border: 1px solid #666;" class="p-3">
{{ render_form(form) }}
</div>
<h3 class="mt-3">Origins</h3>
<a href="{{ url_for("portal.new_origin", group_id=group.id) }}" class="btn btn-success btn-sm">Create new origin</a>
{% if group.origins %}
{{ origins_table(group.origins) }}
{% endif %}
{% endblock %}

View file

@ -0,0 +1,30 @@
{% extends "base.html.j2" %}
{% block content %}
<h1 class="h2 mt-3">Groups</h1>
<a href="{{ url_for("portal.new_group") }}" class="btn btn-success">Create new group</a>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Description</th>
<th scope="col">EOTK</th>
<th scope="col">Sites</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
{% for group in groups %}
<tr>
<td>{{ group.group_name }}</td>
<td>{{ group.description }}</td>
<td>{% if group.eotk %}√{% else %}x{% endif %}</td>
<td>{{ group.origins | length }}</td>
<td><a href="{{ url_for("portal.edit_group", group_id=group.id) }}" class="btn btn-primary btn-sm">View/Edit</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View file

@ -0,0 +1,6 @@
{% extends "base.html.j2" %}
{% block content %}
<h1 class="h2 mt-3">Welcome</h1>
<p>Welcome to the Bypass Censorship portal.</p>
{% endblock %}

View file

@ -0,0 +1,8 @@
{% extends "base.html.j2" %}
{% from 'bootstrap5/form.html' import render_form %}
{% block content %}
<h1 class="h2 mt-3">{{ header }}</h1>
<p>{{ message }}</p>
{{ render_form(form) }}
{% endblock %}

View file

@ -0,0 +1,8 @@
{% extends "base.html.j2" %}
{% from "tables.html.j2" import mirrorlists_table %}
{% block content %}
<h1 class="h2 mt-3">Mirror Lists</h1>
<a href="{{ url_for("portal.new_mirror_list") }}" class="btn btn-success">Create new mirror list</a>
{{ mirrorlists_table(mirrorlists) }}
{% endblock %}

View file

@ -0,0 +1,12 @@
{% extends "base.html.j2" %}
{% from 'bootstrap5/form.html' import render_form %}
{% block content %}
<h1 class="h2 mt-3">{{ (resource_p or (section + "s")).title() }}</h1>
<h2 class="h3">New {{ section.lower() }}</h2>
<div style="border: 1px solid #666;" class="p-3">
{{ render_form(form) }}
</div>
{% endblock %}

View file

@ -0,0 +1,20 @@
{% extends "base.html.j2" %}
{% from 'bootstrap5/form.html' import render_form %}
{% from "tables.html.j2" import proxies_table %}
{% block content %}
<h1 class="h2 mt-3">Origins</h1>
<h2 class="h3">
{{ origin.group.group_name }}: {{ origin.domain_name }}
<a href="{{ origin.domain_name }}" class="btn btn-secondary btn-sm" target="_bypass"
rel="noopener noreferer">⎋</a>
</h2>
<div style="border: 1px solid #666;" class="p-3">
{{ render_form(form) }}
</div>
<h3>Proxies</h3>
{{ proxies_table(origin.proxies) }}
{% endblock %}

View file

@ -0,0 +1,8 @@
{% extends "base.html.j2" %}
{% from "tables.html.j2" import origins_table %}
{% block content %}
<h1 class="h2 mt-3">Origins</h1>
<a href="{{ url_for("portal.new_origin") }}" class="btn btn-success">Create new origin</a>
{{ origins_table(origins) }}
{% endblock %}

View file

@ -0,0 +1,7 @@
{% extends "base.html.j2" %}
{% from "tables.html.j2" import proxies_table %}
{% block content %}
<h1 class="h2 mt-3">Proxies</h1>
{{ proxies_table(proxies) }}
{% endblock %}

View file

@ -0,0 +1,14 @@
{% extends "base.html.j2" %}
{% from "tables.html.j2" import origins_table %}
{% from "tables.html.j2" import proxies_table %}
{% block content %}
<h1 class="h2 mt-3">Search Results</h1>
{% if origins %}
<h2 class="h3">Origins</h2>
{{ origins_table(origins) }}
{% endif %}{% if proxies %}
<h2 class="h3">Proxies</h2>
{{ proxies_table(proxies) }}
{% endif %}
{% endblock %}

View file

@ -0,0 +1,253 @@
{% macro origins_table(origins) %}
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Description</th>
<th scope="col">Mirrors</th>
<th scope="col">Proxies</th>
<th scope="col">Group</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
{% for origin in origins %}
{% if not origin.destroyed %}
<tr>
<td>
<a href="https://{{ origin.domain_name }}" target="_bypass" rel="noopener noreferer"
class="btn btn-secondary btn-sm">⎋</a>
{{ origin.domain_name }}
</td>
<td>{{ origin.description }}</td>
<td>{{ origin.mirrors | length }}</td>
<td>{{ origin.proxies | length }}</td>
<td>
<a href="{{ url_for("portal.edit_group", group_id=origin.group.id) }}">{{ origin.group.group_name }}</a>
</td>
<td>
<a href="{{ url_for("portal.edit_origin", origin_id=origin.id) }}"
class="btn btn-primary btn-sm">View/Edit</a>
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
{% endmacro %}
{% macro proxies_table(proxies) %}
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th scope="col">Origin Domain Name</th>
<th scope="col">Group</th>
<th scope="col">Provider</th>
<th scope="col">URL</th>
<th scope="col">Alarms</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
{% for proxy in proxies %}
{% if not proxy.destroyed %}
<tr class="align-middle{% if proxy.deprecated %} bg-warning{% endif %}">
<td>
<a href="https://{{ proxy.origin.domain_name }}" class="btn btn-secondary btn-sm"
target="_bypass"
rel="noopener noreferer">⎋</a>
<a href="{{ url_for("portal.edit_origin", origin_id=proxy.origin.id) }}">{{ proxy.origin.domain_name }}</a>
</td>
<td>
<a href="{{ url_for("portal.edit_group", group_id=proxy.origin.group.id) }}">{{ proxy.origin.group.group_name }}</a>
</td>
<td>{{ proxy.provider }}</td>
<td>
<a href="{{ proxy.url }}" class="btn btn-secondary btn-sm" target="_bypass"
rel="noopener noreferer">⎋</a>
<span{% if proxy.deprecated %}
class="text-decoration-line-through"{% endif %}>{{ proxy.url }}</span>
</td>
<td>
{% for alarm in proxy.alarms %}
<span title="{{ alarm.alarm_type }}">
{% if alarm.alarm_state.name == "OK" %}
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
class="bi bi-check-circle text-success" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
<path d="M10.97 4.97a.235.235 0 0 0-.02.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-1.071-1.05z"/>
</svg>
{% elif alarm.alarm_state.name == "UNKNOWN" %}
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
class="bi bi-question-circle text-muted" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
<path d="M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286zm1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94z"/>
</svg>
{% else %}
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
class="bi bi-exclamation-circle text-danger" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
<path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z"/>
</svg>
{% endif %}
</span>
{% endfor %}
</td>
<td>
{% if proxy.deprecated %}
<a href="#" class="disabled btn btn-sm btn-outline-dark">Expiring
in {{ proxy.deprecated | mirror_expiry }}</a>
{% else %}
<a href="{{ url_for("portal.blocked_proxy", proxy_id=proxy.id) }}"
class="btn btn-warning btn-sm">Mark blocked</a>
{% endif %}
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
{% endmacro %}
{% macro bridgeconfs_table(bridgeconfs) %}
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th scope="col">Group</th>
<th scope="col">Provider</th>
<th scope="col">Distribution Method</th>
<th scope="col">Number</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
{% for bridgeconf in bridgeconfs %}
{% if not bridgeconf.destroyed %}
<tr class="align-middle">
<td>
<a href="{{ url_for("portal.edit_group", group_id=bridgeconf.group.id) }}">{{ bridgeconf.group.group_name }}</a>
</td>
<td>{{ bridgeconf.provider }}</td>
<td>{{ bridgeconf.method }}</td>
<td>{{ bridgeconf.number }}</td>
<td>
<a href="{{ url_for("portal.edit_bridgeconf", bridgeconf_id=bridgeconf.id) }}"
class="btn btn-primary btn-sm">View/Edit</a>
<a href="{{ url_for("portal.destroy_bridgeconf", bridgeconf_id=bridgeconf.id) }}"
class="btn btn-danger btn-sm">Destroy</a>
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
{% endmacro %}
{% macro bridges_table(bridges) %}
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th scope="col">Group</th>
<th scope="col">Configuration</th>
<th scope="col">Nickname</th>
<th scope="col">Hashed Fingerprint</th>
<th scope="col">Alarms</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
{% for bridge in bridges %}
{% if not bridge.destroyed %}
<tr class="align-middle{% if bridge.deprecated %} bg-warning{% endif %}">
<td>
<a href="{{ url_for("portal.edit_group", group_id=bridge.conf.group.id) }}">{{ bridge.conf.group.group_name }}</a>
</td>
<td>{{ bridge.conf.description }} ({{ bridge.conf.provider }}/{{ bridge.conf.method }})</td>
<td>
<a href="https://metrics.torproject.org/rs.html#details/{{ bridge.hashed_fingerprint }}">
{{ bridge.nickname }}
</a>
</td>
<td>
<code>{{ bridge.hashed_fingerprint }}</code>
</td>
<td>
{% for alarm in bridge.alarms %}
<span title="{{ alarm.alarm_type }}">
{% if alarm.alarm_state.name == "OK" %}
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
class="bi bi-check-circle text-success" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
<path d="M10.97 4.97a.235.235 0 0 0-.02.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-1.071-1.05z"/>
</svg>
{% elif alarm.alarm_state.name == "UNKNOWN" %}
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
class="bi bi-question-circle text-muted" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
<path d="M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286zm1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94z"/>
</svg>
{% else %}
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
class="bi bi-exclamation-circle text-danger" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
<path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z"/>
</svg>
{% endif %}
</span>
{% endfor %}
</td>
<td>
{% if bridge.deprecated %}
<a href="#" class="disabled btn btn-sm btn-outline-dark">Expiring
in {{ bridge.deprecated | mirror_expiry }}</a>
{% else %}
<a href="{{ url_for("portal.blocked_bridge", bridge_id=bridge.id) }}"
class="btn btn-warning btn-sm">Mark blocked</a>
{% endif %}
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
{% endmacro %}
{% macro mirrorlists_table(mirrorlists) %}
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th scope="col">Provider</th>
<th scope="col">Format</th>
<th scope="col">URI</th>
<th scope="col">Description</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
{% for list in mirrorlists %}
{% if not list.destroyed %}
<tr class="align-middle">
<td>{{ list.provider }}</td>
<td>{{ list.format }}</td>
<td>{{ list.url() }}</td>
<td>{{ list.description }}</td>
<td>
<a href="{{ url_for("portal.destroy_mirror_list", list_id=list.id) }}"
class="btn btn-danger btn-sm">Destroy</a>
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
{% endmacro %}