portal: additional buttons on list pages

link to onion services page from origin page
link to previews of distribution lists from lists
This commit is contained in:
Iain Learmonth 2022-05-12 17:03:26 +01:00
parent 928edc46c2
commit d54fae7423
9 changed files with 151 additions and 74 deletions

View file

@ -4,7 +4,6 @@ from typing import Dict, List
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from tldextract import extract from tldextract import extract
from app import app
from app.models.base import Group from app.models.base import Group
from app.models.mirrors import Proxy from app.models.mirrors import Proxy
@ -31,6 +30,7 @@ class MirrorMapping(BaseModel):
def mirror_mapping(): def mirror_mapping():
from app import app
return MirrorMapping( return MirrorMapping(
version="1.1", version="1.1",
mappings={ mappings={

View file

@ -24,11 +24,25 @@ class Group(AbstractConfiguration):
class MirrorList(AbstractConfiguration): class MirrorList(AbstractConfiguration):
provider = db.Column(db.String(255), nullable=False) provider = db.Column(db.String(255), nullable=False)
format = db.Column(db.String(20), nullable=False) format = db.Column(db.String(20), nullable=False)
# obfuscate = db.Column(db.Boolean(), nullable=False)
container = db.Column(db.String(255), nullable=False) container = db.Column(db.String(255), nullable=False)
branch = db.Column(db.String(255), nullable=False) branch = db.Column(db.String(255), nullable=False)
role = db.Column(db.String(255), nullable=True) role = db.Column(db.String(255), nullable=True)
filename = db.Column(db.String(255), nullable=False) filename = db.Column(db.String(255), nullable=False)
providers_supported = {
"github": "GitHub",
"gitlab": "GitLab",
"s3": "AWS S3",
}
formats_supported = {
"bc2": "Bypass Censorship v2",
"bc3": "Bypass Censorship v3",
"bca": "Bypass Censorship Analytics",
"bridgelines": "Tor Bridge Lines"
}
def destroy(self): def destroy(self):
self.destroyed = datetime.utcnow() self.destroyed = datetime.utcnow()
self.updated = datetime.utcnow() self.updated = datetime.utcnow()

View file

@ -4,7 +4,7 @@ from flask import Blueprint, render_template, request
from sqlalchemy import desc, or_ from sqlalchemy import desc, or_
from app.models.alarms import Alarm from app.models.alarms import Alarm
from app import Origin, Proxy from app.models.mirrors import Origin, Proxy
from app.models.base import Group from app.models.base import Group
from app.portal.list import NewMirrorListForm from app.portal.list import NewMirrorListForm
from app.portal.automation import bp as automation from app.portal.automation import bp as automation
@ -76,5 +76,3 @@ def view_alarms():
section="alarm", section="alarm",
title="Alarms", title="Alarms",
items=alarms) items=alarms)

View file

@ -1,18 +1,32 @@
import json
from datetime import datetime from datetime import datetime
from flask import render_template, url_for, flash, redirect, Blueprint from flask import render_template, url_for, flash, redirect, Blueprint, Response
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from sqlalchemy import exc from sqlalchemy import exc
from wtforms import SelectField, StringField, SubmitField from wtforms import SelectField, StringField, SubmitField
from wtforms.validators import DataRequired from wtforms.validators import DataRequired
from app import db from app.extensions import db
from app.lists.bc2 import mirror_sites
from app.lists.bridgelines import bridgelines
from app.lists.mirror_mapping import mirror_mapping
from app.models.base import MirrorList from app.models.base import MirrorList
from app.portal.util import response_404, view_lifecycle from app.portal.util import response_404, view_lifecycle
bp = Blueprint("list", __name__) bp = Blueprint("list", __name__)
@bp.app_template_filter("provider_name")
def list_provider_name(s: str) -> str:
return MirrorList.providers_supported.get(s, "Unknown")
@bp.app_template_filter("format_name")
def list_format_name(s: str) -> str:
return MirrorList.formats_supported.get(s, "Unknown")
@bp.route('/list') @bp.route('/list')
def list_list(): def list_list():
lists = MirrorList.query.filter(MirrorList.destroyed == None).all() lists = MirrorList.query.filter(MirrorList.destroyed == None).all()
@ -21,7 +35,27 @@ def list_list():
title="Mirror Lists", title="Mirror Lists",
item="mirror list", item="mirror list",
new_link=url_for("portal.list.list_new"), new_link=url_for("portal.list.list_new"),
items=lists) items=lists,
extra_buttons=[
{
"link": url_for("portal.list.list_preview", format_=k),
"text": f"Preview {v}",
"style": "secondary"
}
for k, v in MirrorList.formats_supported.items()
]
)
@bp.route('/preview/<format_>')
def list_preview(format_: str):
if format_ == "bca":
return Response(json.dumps(mirror_mapping()), content_type="application/json")
if format_ == "bc2":
return Response(json.dumps(mirror_sites()), content_type="application/json")
if format_ == "bridgelines":
return Response(json.dumps(bridgelines()), content_type="application/json")
return response_404(message="Format not found")
@bp.route("/destroy/<list_id>", methods=['GET', 'POST']) @bp.route("/destroy/<list_id>", methods=['GET', 'POST'])
@ -44,17 +78,8 @@ def list_destroy(list_id: int):
@bp.route("/new/<group_id>", methods=['GET', 'POST']) @bp.route("/new/<group_id>", methods=['GET', 'POST'])
def list_new(group_id=None): def list_new(group_id=None):
form = NewMirrorListForm() form = NewMirrorListForm()
form.provider.choices = [ form.provider.choices = [(k, v) for k, v in MirrorList.providers_supported]
("github", "GitHub"), form.format.choices = [(k, v) for k, v in MirrorList.formats_supported]
("gitlab", "GitLab"),
("s3", "AWS S3"),
]
form.format.choices = [
("bc2", "Bypass Censorship v2"),
("bc3", "Bypass Censorship v3"),
("bca", "Bypass Censorship Analytics"),
("bridgelines", "Tor Bridge Lines")
]
if form.validate_on_submit(): if form.validate_on_submit():
list_ = MirrorList() list_ = MirrorList()
list_.provider = form.provider.data list_.provider = form.provider.data

View file

@ -89,10 +89,15 @@ def origin_list():
origins = Origin.query.order_by(Origin.domain_name).all() origins = Origin.query.order_by(Origin.domain_name).all()
return render_template("list.html.j2", return render_template("list.html.j2",
section="origin", section="origin",
title="Origins", title="Web Origins",
item="origin", item="origin",
new_link=url_for("portal.origin.origin_new"), new_link=url_for("portal.origin.origin_new"),
items=origins) items=origins,
extra_buttons=[{
"link": url_for("portal.origin.origin_onion"),
"text": "Onion services",
"style": "onion"
}])
@bp.route("/onion") @bp.route("/onion")

View file

@ -98,3 +98,35 @@ body {
border-color: transparent; border-color: transparent;
box-shadow: 0 0 0 3px rgba(255, 255, 255, .25); box-shadow: 0 0 0 3px rgba(255, 255, 255, .25);
} }
.btn-onion {
background-color: #7d4698;
border-color: #7d4698;
color: #fff
}
.btn-check:focus + .btn-onion, .btn-onion:focus, .btn-onion:hover {
background-color: #6a3c81;
border-color: #64387a;
color: #fff
}
.btn-check:focus + .btn-onion, .btn-onion:focus {
box-shadow: 0 0 0 .25rem rgba(145, 98, 167, .5)
}
.btn-check:active + .btn-onion, .btn-check:checked + .btn-onion, .btn-onion.active, .btn-onion:active, .show > .btn-onion.dropdown-toggle {
background-color: #64387a;
border-color: #5e3572;
color: #fff
}
.btn-check:active + .btn-onion:focus, .btn-check:checked + .btn-onion:focus, .btn-onion.active:focus, .btn-onion:active:focus, .show > .btn-onion.dropdown-toggle:focus {
box-shadow: 0 0 0 .25rem rgba(145, 98, 167, .5)
}
.btn-onion.disabled, .btn-onion:disabled {
background-color: #7d4698;
border-color: #7d4698;
color: #fff
}

View file

@ -102,7 +102,7 @@
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link{% if section == "blocklist" %} active{% endif %}" <a class="nav-link{% if section == "blocklist" %} active{% endif %} disabled text-secondary"
href="#"> href="#">
{{ icon("file-earmark-excel") }} Block Lists {{ icon("file-earmark-excel") }} Block Lists
</a> </a>
@ -119,13 +119,13 @@
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link{% if section == "smart_proxy" %} active{% endif %}" <a class="nav-link{% if section == "smart_proxy" %} active{% endif %} disabled text-secondary"
href="#"> href="#">
{{ icon("globe") }} Smart Proxy Instances {{ icon("globe") }} Smart Proxy Instances
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link{% if section == "eotk" %} active{% endif %}" <a class="nav-link{% if section == "eotk" %} active{% endif %} disabled text-secondary"
href="#"> href="#">
{{ icon("server") }} EOTK Instances {{ icon("server") }} EOTK Instances
</a> </a>

View file

@ -6,6 +6,9 @@
<h1 class="h2 mt-3">{{ title }}</h1> <h1 class="h2 mt-3">{{ title }}</h1>
{% if new_link %} {% if new_link %}
<a href="{{ new_link }}" class="btn btn-success">Create new {{ item }}</a> <a href="{{ new_link }}" class="btn btn-success">Create new {{ item }}</a>
{% for extra_button in extra_buttons %}
<a href="{{ extra_button.link }}" class="btn btn-{{ extra_button.style }}">{{ extra_button.text }}</a>
{% endfor %}
{% endif %} {% endif %}
{% if section == "alarm" %} {% if section == "alarm" %}
{{ alarms_table(items) }} {{ alarms_table(items) }}

View file

@ -465,9 +465,9 @@
{% for list in mirrorlists %} {% for list in mirrorlists %}
{% if not list.destroyed %} {% if not list.destroyed %}
<tr class="align-middle"> <tr class="align-middle">
<td>{{ list.provider }}</td> <td>{{ list.provider | provider_name }}</td>
<td>{{ list.format }}</td> <td>{{ list.format | format_name }}</td>
<td>{{ list.url() }}</td> <td><a href="{{ list.url() }}">{{ list.url() }}</a></td>
<td>{{ list.description }}</td> <td>{{ list.description }}</td>
<td> <td>
<a href="{{ url_for("portal.list.list_destroy", list_id=list.id) }}" <a href="{{ url_for("portal.list.list_destroy", list_id=list.id) }}"