majuna/app/portal/__init__.py

225 lines
7.8 KiB
Python
Raw Permalink Normal View History

import json
import logging
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
from flask import Blueprint, redirect, render_template, request, url_for
2022-05-16 11:44:03 +01:00
from flask.typing import ResponseReturnValue
2023-10-23 17:53:48 +01:00
from markupsafe import Markup
from sqlalchemy import desc, func, or_
2022-03-10 14:26:22 +00:00
2022-06-22 16:34:45 +01:00
from app.alarms import alarms_for
from app.models.activity import Activity
2022-05-14 11:04:34 +01:00
from app.models.alarms import Alarm, AlarmState
from app.models.base import Group
2022-05-14 11:03:25 +01:00
from app.models.bridges import Bridge
from app.models.mirrors import Origin, Proxy
from app.models.onions import Eotk
from app.portal.automation import bp as automation
from app.portal.bridge import bp as bridge
from app.portal.bridgeconf import bp as bridgeconf
from app.portal.cloud import bp as cloud
from app.portal.country import bp as country
from app.portal.eotk import bp as eotk
from app.portal.group import bp as group
from app.portal.list import bp as list_
2022-05-04 15:36:36 +01:00
from app.portal.onion import bp as onion
from app.portal.origin import bp as origin
2022-09-26 13:40:59 +01:00
from app.portal.pool import bp as pool
from app.portal.proxy import bp as proxy
from app.portal.smart_proxy import bp as smart_proxy
from app.portal.static import bp as static
from app.portal.storage import bp as storage
from app.portal.webhook import bp as webhook
2022-03-10 14:26:22 +00:00
2024-12-06 18:15:47 +00:00
portal = Blueprint(
"portal", __name__, template_folder="templates", static_folder="static"
)
portal.register_blueprint(automation, url_prefix="/automation")
portal.register_blueprint(bridgeconf, url_prefix="/bridgeconf")
portal.register_blueprint(bridge, url_prefix="/bridge")
2023-02-26 12:52:08 +00:00
portal.register_blueprint(cloud, url_prefix="/cloud")
2023-10-29 15:45:10 +00:00
portal.register_blueprint(country, url_prefix="/country")
portal.register_blueprint(eotk, url_prefix="/eotk")
portal.register_blueprint(group, url_prefix="/group")
portal.register_blueprint(list_, url_prefix="/list")
portal.register_blueprint(origin, url_prefix="/origin")
2022-05-04 15:36:36 +01:00
portal.register_blueprint(onion, url_prefix="/onion")
2022-09-26 13:40:59 +01:00
portal.register_blueprint(pool, url_prefix="/pool")
portal.register_blueprint(proxy, url_prefix="/proxy")
portal.register_blueprint(smart_proxy, url_prefix="/smart")
portal.register_blueprint(static, url_prefix="/static")
portal.register_blueprint(storage, url_prefix="/state")
portal.register_blueprint(webhook, url_prefix="/webhook")
2022-03-10 14:26:22 +00:00
@portal.app_template_filter("bridge_expiry")
def calculate_bridge_expiry(b: Bridge) -> str:
if b.deprecated is None:
2024-12-06 18:15:47 +00:00
logging.warning(
"Bridge expiry requested by template for a bridge %s that was not expiring.",
b.id,
)
return "Not expiring"
expiry = b.deprecated + timedelta(hours=b.conf.expiry_hours)
countdown = expiry - datetime.now(tz=timezone.utc)
if countdown.days == 0:
return f"{countdown.seconds // 3600} hours"
return f"{countdown.days} days"
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)
countdown = expiry - datetime.now(tz=timezone.utc)
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")
@portal.app_template_filter("describe_brn")
2022-06-17 12:42:42 +01:00
def describe_brn(s: str) -> ResponseReturnValue:
parts = s.split(":")
if parts[3] == "mirror":
if parts[5].startswith("origin/"):
origin = Origin.query.filter(
2024-12-06 18:15:47 +00:00
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(
2024-12-06 18:15:47 +00:00
Proxy.id == int(parts[5][len("proxy/") :])
).first()
if not proxy:
return s
2023-10-23 17:53:48 +01:00
return Markup(
2024-12-06 18:15:47 +00:00
f"Proxy: {proxy.url}<br>({proxy.origin.group.group_name}: {proxy.origin.domain_name})"
)
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(
2024-12-06 18:15:47 +00:00
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
@portal.app_template_filter("pretty_json")
2023-01-21 15:15:07 +00:00
def pretty_json(json_str: Optional[str]) -> str:
if not json_str:
return "None"
2023-01-21 15:15:07 +00:00
return json.dumps(json.loads(json_str), indent=2)
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):
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())
2024-12-06 18:15:47 +00:00
lastweek = len(
Proxy.query.filter(Proxy.deprecated > (now - timedelta(days=7))).all()
)
2022-05-12 14:48:33 +01:00
alarms = {
2024-12-06 18:15:47 +00:00
s: len(
Alarm.query.filter(
Alarm.alarm_state == s.upper(),
Alarm.last_updated > (now - timedelta(days=1)),
).all()
)
2022-05-12 14:48:33 +01:00
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]
}
2024-12-06 18:15:47 +00:00
activity = (
Activity.query.filter(Activity.added > (now - timedelta(days=2)))
.order_by(desc(Activity.added))
.all()
)
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())
2024-12-06 18:15:47 +00:00
return render_template(
"home.html.j2",
section="home",
groups=groups,
last24=last24,
last72=last72,
lastweek=lastweek,
proxies=proxies,
**alarms,
activity=activity,
total_origins=total_origins,
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-08-30 14:15:06 +01:00
if query is None:
return redirect(url_for("portal.portal_home"))
proxies = Proxy.query.filter(
2024-12-06 18:15:47 +00:00
or_(func.lower(Proxy.url).contains(query.lower())), Proxy.destroyed.is_(None)
).all()
origins = Origin.query.filter(
2024-12-06 18:15:47 +00:00
or_(
func.lower(Origin.description).contains(query.lower()),
func.lower(Origin.domain_name).contains(query.lower()),
)
).all()
return render_template(
"search.html.j2", section="home", proxies=proxies, origins=origins
)
2022-03-10 14:26:22 +00:00
2024-12-06 18:15:47 +00:00
@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)
2024-12-06 18:15:47 +00:00
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
)