2022-04-20 16:01:36 +01:00
|
|
|
from datetime import datetime, timedelta, timezone
|
2022-06-17 12:42:42 +01:00
|
|
|
from typing import Optional
|
2022-03-10 14:26:22 +00:00
|
|
|
|
2022-05-11 15:47:39 +01:00
|
|
|
from flask import Blueprint, render_template, request
|
2022-05-16 11:44:03 +01:00
|
|
|
from flask.typing import ResponseReturnValue
|
2022-08-12 11:53:17 +01:00
|
|
|
from jinja2.markupsafe import Markup
|
2022-05-11 15:47:39 +01:00
|
|
|
from sqlalchemy import desc, or_
|
2022-03-10 14:26:22 +00:00
|
|
|
|
2022-06-22 16:34:45 +01:00
|
|
|
from app.alarms import alarms_for
|
2022-05-14 10:18:00 +01:00
|
|
|
from app.models.activity import Activity
|
2022-05-14 11:04:34 +01:00
|
|
|
from app.models.alarms import Alarm, AlarmState
|
2022-05-14 11:03:25 +01:00
|
|
|
from app.models.bridges import Bridge
|
2022-05-12 17:03:26 +01:00
|
|
|
from app.models.mirrors import Origin, Proxy
|
2022-05-11 15:47:39 +01:00
|
|
|
from app.models.base import Group
|
2022-05-18 15:49:36 +01:00
|
|
|
from app.models.onions import Eotk
|
2022-05-08 17:20:04 +01:00
|
|
|
from app.portal.automation import bp as automation
|
2022-05-04 14:03:04 +01:00
|
|
|
from app.portal.bridgeconf import bp as bridgeconf
|
|
|
|
from app.portal.bridge import bp as bridge
|
2022-05-13 15:40:59 +01:00
|
|
|
from app.portal.eotk import bp as eotk
|
2022-05-04 13:31:14 +01:00
|
|
|
from app.portal.group import bp as group
|
2022-05-11 15:47:39 +01:00
|
|
|
from app.portal.list import bp as list_
|
2022-05-04 13:46:52 +01:00
|
|
|
from app.portal.origin import bp as origin
|
2022-05-04 15:36:36 +01:00
|
|
|
from app.portal.onion import bp as onion
|
2022-05-04 13:31:14 +01:00
|
|
|
from app.portal.proxy import bp as proxy
|
2022-05-24 19:51:38 +01:00
|
|
|
from app.portal.smart_proxy import bp as smart_proxy
|
2022-05-14 10:18:00 +01:00
|
|
|
from app.portal.webhook import bp as webhook
|
2022-03-10 14:26:22 +00:00
|
|
|
|
|
|
|
portal = Blueprint("portal", __name__, template_folder="templates", static_folder="static")
|
2022-05-08 17:20:04 +01:00
|
|
|
portal.register_blueprint(automation, url_prefix="/automation")
|
2022-05-04 14:03:04 +01:00
|
|
|
portal.register_blueprint(bridgeconf, url_prefix="/bridgeconf")
|
|
|
|
portal.register_blueprint(bridge, url_prefix="/bridge")
|
2022-05-13 15:40:59 +01:00
|
|
|
portal.register_blueprint(eotk, url_prefix="/eotk")
|
2022-05-04 13:17:01 +01:00
|
|
|
portal.register_blueprint(group, url_prefix="/group")
|
2022-05-11 15:47:39 +01:00
|
|
|
portal.register_blueprint(list_, url_prefix="/list")
|
2022-05-04 13:46:52 +01:00
|
|
|
portal.register_blueprint(origin, url_prefix="/origin")
|
2022-05-04 15:36:36 +01:00
|
|
|
portal.register_blueprint(onion, url_prefix="/onion")
|
2022-05-04 13:31:14 +01:00
|
|
|
portal.register_blueprint(proxy, url_prefix="/proxy")
|
2022-05-24 19:51:38 +01:00
|
|
|
portal.register_blueprint(smart_proxy, url_prefix="/smart")
|
2022-05-14 10:18:00 +01:00
|
|
|
portal.register_blueprint(webhook, url_prefix="/webhook")
|
2022-05-04 13:17:01 +01:00
|
|
|
|
2022-03-10 14:26:22 +00:00
|
|
|
|
|
|
|
@portal.app_template_filter("mirror_expiry")
|
2022-04-22 14:56:59 +01:00
|
|
|
def calculate_mirror_expiry(s: datetime) -> str:
|
2022-03-10 14:26:22 +00:00
|
|
|
expiry = s + timedelta(days=3)
|
2022-04-22 14:56:59 +01:00
|
|
|
countdown = expiry - datetime.utcnow()
|
2022-03-10 14:26:22 +00:00
|
|
|
if countdown.days == 0:
|
|
|
|
return f"{countdown.seconds // 3600} hours"
|
|
|
|
return f"{countdown.days} days"
|
|
|
|
|
|
|
|
|
2022-04-20 15:56:09 +01:00
|
|
|
@portal.app_template_filter("format_datetime")
|
2022-05-16 11:44:03 +01:00
|
|
|
def format_datetime(s: Optional[datetime]) -> str:
|
2022-04-20 15:56:09 +01:00
|
|
|
if s is None:
|
|
|
|
return "Unknown"
|
|
|
|
return s.strftime("%a, %d %b %Y %H:%M:%S")
|
|
|
|
|
|
|
|
|
2022-05-18 15:49:36 +01:00
|
|
|
@portal.app_template_filter("describe_brn")
|
2022-06-17 12:42:42 +01:00
|
|
|
def describe_brn(s: str) -> ResponseReturnValue:
|
2022-05-18 15:49:36 +01:00
|
|
|
parts = s.split(":")
|
|
|
|
if parts[3] == "mirror":
|
|
|
|
if parts[5].startswith("origin/"):
|
|
|
|
origin = Origin.query.filter(
|
|
|
|
Origin.domain_name == parts[5][len("origin/"):]
|
|
|
|
).first()
|
|
|
|
if not origin:
|
|
|
|
return s
|
|
|
|
return f"Origin: {origin.domain_name} ({origin.group.group_name})"
|
|
|
|
if parts[5].startswith("proxy/"):
|
|
|
|
proxy = Proxy.query.filter(
|
|
|
|
Proxy.id == int(parts[5][len("proxy/"):])
|
|
|
|
).first()
|
|
|
|
if not proxy:
|
|
|
|
return s
|
2022-06-17 12:42:42 +01:00
|
|
|
return Markup( # type: ignore[no-untyped-call]
|
|
|
|
f"Proxy: {proxy.url}<br>({proxy.origin.group.group_name}: {proxy.origin.domain_name})")
|
2022-05-18 15:49:36 +01:00
|
|
|
if parts[5].startswith("quota/"):
|
|
|
|
if parts[4] == "cloudfront":
|
|
|
|
return f"Quota: CloudFront {parts[5][len('quota/'):]}"
|
|
|
|
if parts[3] == "eotk":
|
|
|
|
if parts[5].startswith("instance/"):
|
|
|
|
eotk = Eotk.query.filter(
|
|
|
|
Eotk.group_id == parts[2],
|
|
|
|
Eotk.region == parts[5][len("instance/"):]
|
|
|
|
).first()
|
|
|
|
if not eotk:
|
|
|
|
return s
|
|
|
|
return f"EOTK Instance: {eotk.group.group_name} in {eotk.provider} {eotk.region}"
|
|
|
|
return s
|
|
|
|
|
|
|
|
|
2022-05-16 11:44:03 +01:00
|
|
|
def total_origins_blocked() -> int:
|
2022-05-14 11:03:25 +01:00
|
|
|
count = 0
|
2022-05-16 13:29:48 +01:00
|
|
|
for o in Origin.query.filter(Origin.destroyed.is_(None)).all():
|
2022-06-22 16:34:45 +01:00
|
|
|
for a in alarms_for(o.brn):
|
2022-05-18 15:49:36 +01:00
|
|
|
if a.aspect.startswith("origin-block-ooni-"):
|
2022-05-14 11:03:25 +01:00
|
|
|
if a.alarm_state == AlarmState.WARNING:
|
|
|
|
count += 1
|
|
|
|
break
|
|
|
|
return count
|
|
|
|
|
2022-05-16 11:44:03 +01:00
|
|
|
|
2022-03-10 14:26:22 +00:00
|
|
|
@portal.route("/")
|
2022-05-16 11:44:03 +01:00
|
|
|
def portal_home() -> ResponseReturnValue:
|
2022-04-21 17:10:38 +01:00
|
|
|
groups = Group.query.order_by(Group.group_name).all()
|
|
|
|
now = datetime.now(timezone.utc)
|
2022-05-16 13:29:48 +01:00
|
|
|
proxies = Proxy.query.filter(Proxy.destroyed.is_(None)).all()
|
2022-04-21 17:10:38 +01:00
|
|
|
last24 = len(Proxy.query.filter(Proxy.deprecated > (now - timedelta(days=1))).all())
|
|
|
|
last72 = len(Proxy.query.filter(Proxy.deprecated > (now - timedelta(days=3))).all())
|
|
|
|
lastweek = len(Proxy.query.filter(Proxy.deprecated > (now - timedelta(days=7))).all())
|
2022-05-12 14:48:33 +01:00
|
|
|
alarms = {
|
|
|
|
s: len(Alarm.query.filter(Alarm.alarm_state == s.upper(), Alarm.last_updated > (now - timedelta(days=1))).all())
|
|
|
|
for s in ["critical", "warning", "ok", "unknown"]
|
|
|
|
}
|
2022-05-16 13:29:48 +01:00
|
|
|
bridges = Bridge.query.filter(Bridge.destroyed.is_(None)).all()
|
2022-05-14 11:03:25 +01:00
|
|
|
br_last = {
|
|
|
|
d: len(Bridge.query.filter(Bridge.deprecated > (now - timedelta(days=d))).all())
|
|
|
|
for d in [1, 3, 7]
|
|
|
|
}
|
2022-05-14 10:18:00 +01:00
|
|
|
activity = Activity.query.filter(Activity.added > (now - timedelta(days=2))).order_by(desc(Activity.added)).all()
|
2022-05-16 13:29:48 +01:00
|
|
|
onionified = len([o for o in Origin.query.filter(Origin.destroyed.is_(None)).all() if o.onion() is not None])
|
2022-05-14 11:03:25 +01:00
|
|
|
ooni_blocked = total_origins_blocked()
|
2022-05-16 13:29:48 +01:00
|
|
|
total_origins = len(Origin.query.filter(Origin.destroyed.is_(None)).all())
|
2022-05-01 16:23:45 +01:00
|
|
|
return render_template("home.html.j2", section="home", groups=groups, last24=last24, last72=last72,
|
2022-05-14 11:03:25 +01:00
|
|
|
lastweek=lastweek, proxies=proxies, **alarms, activity=activity, total_origins=total_origins,
|
2022-05-14 14:47:39 +01:00
|
|
|
onionified=onionified, br_last=br_last, ooni_blocked=ooni_blocked, bridges=bridges)
|
2022-03-10 14:26:22 +00:00
|
|
|
|
|
|
|
|
|
|
|
@portal.route("/search")
|
2022-05-16 11:44:03 +01:00
|
|
|
def search() -> ResponseReturnValue:
|
2022-03-10 14:26:22 +00:00
|
|
|
query = request.args.get("query")
|
2022-05-16 13:29:48 +01:00
|
|
|
proxies = Proxy.query.filter(or_(Proxy.url.contains(query)), Proxy.destroyed.is_(None)).all()
|
2022-03-10 14:26:22 +00:00
|
|
|
origins = Origin.query.filter(or_(Origin.description.contains(query), Origin.domain_name.contains(query))).all()
|
|
|
|
return render_template("search.html.j2", section="home", proxies=proxies, origins=origins)
|
|
|
|
|
|
|
|
|
|
|
|
@portal.route('/alarms')
|
2022-05-16 11:44:03 +01:00
|
|
|
def view_alarms() -> ResponseReturnValue:
|
2022-05-11 14:26:14 +01:00
|
|
|
one_day_ago = datetime.now(timezone.utc) - timedelta(days=1)
|
|
|
|
alarms = Alarm.query.filter(Alarm.last_updated >= one_day_ago).order_by(
|
|
|
|
desc(Alarm.alarm_state), desc(Alarm.state_changed)).all()
|
|
|
|
return render_template("list.html.j2",
|
|
|
|
section="alarm",
|
|
|
|
title="Alarms",
|
|
|
|
items=alarms)
|