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 tldextract import extract
from app import app
from app.models.base import Group
from app.models.mirrors import Proxy
@ -31,6 +30,7 @@ class MirrorMapping(BaseModel):
def mirror_mapping():
from app import app
return MirrorMapping(
version="1.1",
mappings={

View file

@ -24,11 +24,25 @@ class Group(AbstractConfiguration):
class MirrorList(AbstractConfiguration):
provider = db.Column(db.String(255), 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)
branch = db.Column(db.String(255), nullable=False)
role = db.Column(db.String(255), nullable=True)
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):
self.destroyed = 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 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.portal.list import NewMirrorListForm
from app.portal.automation import bp as automation
@ -76,5 +76,3 @@ def view_alarms():
section="alarm",
title="Alarms",
items=alarms)

View file

@ -1,18 +1,32 @@
import json
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 sqlalchemy import exc
from wtforms import SelectField, StringField, SubmitField
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.portal.util import response_404, view_lifecycle
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')
def list_list():
lists = MirrorList.query.filter(MirrorList.destroyed == None).all()
@ -21,7 +35,27 @@ def list_list():
title="Mirror Lists",
item="mirror list",
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'])
@ -44,17 +78,8 @@ def list_destroy(list_id: int):
@bp.route("/new/<group_id>", methods=['GET', 'POST'])
def list_new(group_id=None):
form = NewMirrorListForm()
form.provider.choices = [
("github", "GitHub"),
("gitlab", "GitLab"),
("s3", "AWS S3"),
]
form.format.choices = [
("bc2", "Bypass Censorship v2"),
("bc3", "Bypass Censorship v3"),
("bca", "Bypass Censorship Analytics"),
("bridgelines", "Tor Bridge Lines")
]
form.provider.choices = [(k, v) for k, v in MirrorList.providers_supported]
form.format.choices = [(k, v) for k, v in MirrorList.formats_supported]
if form.validate_on_submit():
list_ = MirrorList()
list_.provider = form.provider.data

View file

@ -89,10 +89,15 @@ def origin_list():
origins = Origin.query.order_by(Origin.domain_name).all()
return render_template("list.html.j2",
section="origin",
title="Origins",
title="Web Origins",
item="origin",
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")

View file

@ -1,11 +1,11 @@
body {
font-size: .875rem;
font-size: .875rem;
}
.feather {
width: 16px;
height: 16px;
vertical-align: text-bottom;
width: 16px;
height: 16px;
vertical-align: text-bottom;
}
/*
@ -13,56 +13,56 @@ body {
*/
.sidebar {
position: fixed;
top: 0;
/* rtl:raw:
right: 0;
*/
bottom: 0;
/* rtl:remove */
left: 0;
z-index: 100; /* Behind the navbar */
padding: 48px 0 0; /* Height of navbar */
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
position: fixed;
top: 0;
/* rtl:raw:
right: 0;
*/
bottom: 0;
/* rtl:remove */
left: 0;
z-index: 100; /* Behind the navbar */
padding: 48px 0 0; /* Height of navbar */
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
}
@media (max-width: 767.98px) {
.sidebar {
top: 5rem;
}
.sidebar {
top: 5rem;
}
}
.sidebar-sticky {
position: relative;
top: 0;
height: calc(100vh - 48px);
padding-top: .5rem;
overflow-x: hidden;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
position: relative;
top: 0;
height: calc(100vh - 48px);
padding-top: .5rem;
overflow-x: hidden;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
}
.sidebar .nav-link {
font-weight: 500;
color: #333;
font-weight: 500;
color: #333;
}
.sidebar .nav-link .feather {
margin-right: 4px;
color: #727272;
margin-right: 4px;
color: #727272;
}
.sidebar .nav-link.active {
color: #2470dc;
color: #2470dc;
}
.sidebar .nav-link:hover .feather,
.sidebar .nav-link.active .feather {
color: inherit;
color: inherit;
}
.sidebar-heading {
font-size: .75rem;
text-transform: uppercase;
font-size: .75rem;
text-transform: uppercase;
}
/*
@ -70,31 +70,63 @@ body {
*/
.navbar-brand {
padding-top: .75rem;
padding-bottom: .75rem;
font-size: 1rem;
background-color: rgba(0, 0, 0, .25);
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .25);
padding-top: .75rem;
padding-bottom: .75rem;
font-size: 1rem;
background-color: rgba(0, 0, 0, .25);
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .25);
}
.navbar .navbar-toggler {
top: .25rem;
right: 1rem;
top: .25rem;
right: 1rem;
}
.navbar .form-control {
padding: .75rem 1rem;
border-width: 0;
border-radius: 0;
padding: .75rem 1rem;
border-width: 0;
border-radius: 0;
}
.form-control-dark {
color: #fff;
background-color: rgba(255, 255, 255, .1);
border-color: rgba(255, 255, 255, .1);
color: #fff;
background-color: rgba(255, 255, 255, .1);
border-color: rgba(255, 255, 255, .1);
}
.form-control-dark:focus {
border-color: transparent;
box-shadow: 0 0 0 3px rgba(255, 255, 255, .25);
border-color: transparent;
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>
</li>
<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="#">
{{ icon("file-earmark-excel") }} Block Lists
</a>
@ -119,13 +119,13 @@
</a>
</li>
<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="#">
{{ icon("globe") }} Smart Proxy Instances
</a>
</li>
<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="#">
{{ icon("server") }} EOTK Instances
</a>

View file

@ -5,7 +5,10 @@
{% block content %}
<h1 class="h2 mt-3">{{ title }}</h1>
{% 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 %}
{% if section == "alarm" %}
{{ alarms_table(items) }}

View file

@ -465,9 +465,9 @@
{% 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.provider | provider_name }}</td>
<td>{{ list.format | format_name }}</td>
<td><a href="{{ list.url() }}">{{ list.url() }}</a></td>
<td>{{ list.description }}</td>
<td>
<a href="{{ url_for("portal.list.list_destroy", list_id=list.id) }}"