diff --git a/app/portal/report.py b/app/portal/report.py
index 7564fe5..8ec0366 100644
--- a/app/portal/report.py
+++ b/app/portal/report.py
@@ -1,13 +1,71 @@
+from datetime import datetime, timedelta
+
from flask import Blueprint, render_template
from flask.typing import ResponseReturnValue
-from sqlalchemy import func, and_
+from sqlalchemy import func, and_, desc
+from sqlalchemy.orm import aliased
from app.extensions import db
-from app.models.mirrors import Proxy, Origin
+from app.models import Deprecation
+from app.models.mirrors import Proxy, Origin, Country
report = Blueprint("report", __name__)
+def countries_report():
+ now = datetime.utcnow()
+ hours_24_ago = now - timedelta(hours=24)
+ hours_72_ago = now - timedelta(hours=72)
+
+ deprecations_24hr_subquery = (
+ db.session.query(
+ Proxy.origin_id,
+ func.count(Deprecation.id).label('deprecations_24hr')
+ )
+ .join(Deprecation, Proxy.id == Deprecation.resource_id)
+ .filter(
+ Deprecation.deprecated_at >= hours_24_ago,
+ Deprecation.resource_type == "Proxy"
+ )
+ .group_by(Proxy.origin_id)
+ .subquery()
+ )
+
+ deprecations_72hr_subquery = (
+ db.session.query(
+ Proxy.origin_id,
+ func.count(Deprecation.id).label('deprecations_72hr')
+ )
+ .join(Deprecation, Proxy.id == Deprecation.resource_id)
+ .filter(
+ Deprecation.deprecated_at >= hours_72_ago,
+ Deprecation.resource_type == "Proxy"
+ )
+ .group_by(Proxy.origin_id)
+ .subquery()
+ )
+
+ return (
+ db.session.query(
+ Country,
+ func.coalesce(func.sum(deprecations_24hr_subquery.c.deprecations_24hr), 0).label('total_deprecations_24hr'),
+ func.coalesce(func.sum(deprecations_72hr_subquery.c.deprecations_72hr), 0).label('total_deprecations_72hr')
+ )
+ .join(Origin, Country.origins)
+ .join(Proxy, Origin.proxies)
+ .outerjoin(deprecations_24hr_subquery, Origin.id == deprecations_24hr_subquery.c.origin_id)
+ .outerjoin(deprecations_72hr_subquery, Origin.id == deprecations_72hr_subquery.c.origin_id)
+ .group_by(Country.id)
+ .all()
+ )
+
+
+@report.app_template_filter('country_name')
+def country_description_filter(country_code):
+ country = Country.query.filter_by(country_code=country_code).first()
+ return country.description if country else None
+
+
@report.route("/blocks", methods=['GET'])
def report_blocks() -> ResponseReturnValue:
blocked_today = db.session.query( # type: ignore[no-untyped-call]
@@ -17,5 +75,69 @@ def report_blocks() -> ResponseReturnValue:
Proxy.deprecated,
Proxy.deprecation_reason
).join(Origin, Origin.id == Proxy.origin_id
- ).filter(and_(Proxy.deprecated > func.current_date(), Proxy.deprecation_reason.like('block_%'))).all()
- return render_template("report_blocks.html.j2", blocked_today=blocked_today)
+ ).filter(and_(Proxy.deprecated > datetime.utcnow() - timedelta(days=1),
+ Proxy.deprecation_reason.like('block_%'))).all()
+
+ # Aliased to avoid confusion in the join
+ DeprecationAlias = aliased(Deprecation)
+
+ # Current time
+ now = datetime.utcnow()
+
+ # Subquery for deprecations in the last 24 hours
+ deprecations_24hr_subquery = (
+ db.session.query(
+ DeprecationAlias.resource_id,
+ func.count(DeprecationAlias.resource_id).label('deprecations_24hr')
+ )
+ .filter(
+ DeprecationAlias.deprecated_at >= now - timedelta(hours=24),
+ DeprecationAlias.resource_type == 'Proxy' # Adjust based on your polymorphic setup
+ )
+ .group_by(DeprecationAlias.resource_id)
+ .subquery()
+ )
+
+ # Subquery for deprecations in the last 72 hours
+ deprecations_72hr_subquery = (
+ db.session.query(
+ DeprecationAlias.resource_id,
+ func.count(DeprecationAlias.resource_id).label('deprecations_72hr')
+ )
+ .filter(
+ DeprecationAlias.deprecated_at >= now - timedelta(hours=72),
+ DeprecationAlias.resource_type == 'Proxy' # Adjust based on your polymorphic setup
+ )
+ .group_by(DeprecationAlias.resource_id)
+ .subquery()
+ )
+
+ origins_with_deprecations = (
+ db.session.query(
+ Origin,
+ func.coalesce(func.sum(deprecations_24hr_subquery.c.deprecations_24hr), 0).label('total_deprecations_24hr'),
+ func.coalesce(func.sum(deprecations_72hr_subquery.c.deprecations_72hr), 0).label('total_deprecations_72hr')
+ )
+ .outerjoin(Proxy, Origin.proxies)
+ .outerjoin(deprecations_24hr_subquery, Proxy.id == deprecations_24hr_subquery.c.resource_id)
+ .outerjoin(deprecations_72hr_subquery, Proxy.id == deprecations_72hr_subquery.c.resource_id)
+ .filter(Origin.destroyed.is_(None))
+ .group_by(Origin.id)
+ .order_by(desc("total_deprecations_24hr"))
+ .all()
+ )
+
+ countries = (
+ db.session.query(Country)
+ .outerjoin(Origin, Country.origins)
+ .outerjoin(Proxy, Origin.proxies)
+ .filter(Country.destroyed.is_(None))
+ .group_by(Country.id)
+ .all()
+ )
+
+ return render_template("report_blocks.html.j2",
+ blocked_today=blocked_today,
+ origins=origins_with_deprecations,
+ countries=countries_report(),
+ )
diff --git a/app/portal/static/print.css b/app/portal/static/print.css
new file mode 100644
index 0000000..9b25583
--- /dev/null
+++ b/app/portal/static/print.css
@@ -0,0 +1,36 @@
+@import url('https://fonts.googleapis.com/css2?family=Playfair+Display&display=swap');
+
+body, #content, #page {
+ font-family: 'Playfair Display', serif;
+ width: 100%;
+ margin: 0;
+ float: none;
+}
+
+table {
+ font-size: 10pt;
+ width: 100%;
+}
+
+td {
+ border: 1px solid #666;
+ padding: 2px;
+}
+
+@media print {
+ .noprint {
+ display: none;
+ }
+
+ @page :left {
+ margin: 1cm;
+ }
+
+ @page :right {
+ margin: 1cm;
+ }
+
+ .pagebreak {
+ page-break-after: always;
+ }
+}
diff --git a/app/portal/templates/report_blocks.html.j2 b/app/portal/templates/report_blocks.html.j2
index cfbf041..a6afc5a 100644
--- a/app/portal/templates/report_blocks.html.j2
+++ b/app/portal/templates/report_blocks.html.j2
@@ -1,11 +1,13 @@
Bypass Censorship
-
+
-
-
Blocked Today
+
+
For best results, print in landscape.
+
Bypass Censorship
+
Last 24 hours
Origin |
@@ -15,13 +17,82 @@
{% for block in blocked_today %}
- {{ block.domain_name }} ({{ block.description }}) |
+ {{ block.domain_name }} |
{{ block.deprecated }} |
{{ block.deprecation_reason }} |
{{ block.deprecated - block.added }} |
{% endfor %}
+
+
+
Bypass Censorship
+
Origin Censorship Report
+
+
+ Origin |
+ Description |
+ 24h |
+ 72h |
+
+ {% for origin in origins %}
+
+ {{ origin[0].domain_name }} |
+ {{ origin[0].description | truncate(25) }} |
+ {{ origin[1] }} |
+ {{ origin[2] }} |
+
+ {% for country in origin[0].risk_level %}
+
+ |
+ {{ country | country_flag }} {{ country | country_name }} |
+ {{ origin[0].risk_level[country] }} |
+
+ {% endfor %}
+ {% endfor %}
+
+
+
+
Bypass Censorship
+
Geography Censorship Report
+
+
+ Country |
+ Risk Level |
+ 24h |
+ 72h |
+
+ {% for country in countries %}
+
+ {{ country[0].country_code | country_flag }} {{ country[0].description }} |
+ {{ country[0].risk_level }} |
+ {{ country[1] }} |
+ {{ country[2] }} |
+
+ {% endfor %}
+