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:
parent
928edc46c2
commit
d54fae7423
9 changed files with 151 additions and 74 deletions
|
@ -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={
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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) }}
|
||||
|
|
|
@ -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) }}"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue