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

@@ -15,13 +17,82 @@ {% for block in blocked_today %} - + {% endfor %}
Origin
{{ block.domain_name }} ({{ block.description }}){{ block.domain_name }} {{ block.deprecated }} {{ block.deprecation_reason }} {{ block.deprecated - block.added }}
+
+ +

Bypass Censorship

+

Origin Censorship Report

+ + + + + + + + {% for origin in origins %} + + + + + + + {% for country in origin[0].risk_level %} + + + + + + {% endfor %} + {% endfor %} +
OriginDescription24h72h
{{ origin[0].domain_name }}{{ origin[0].description | truncate(25) }}{{ origin[1] }}{{ origin[2] }}
{{ country | country_flag }} {{ country | country_name }}{{ origin[0].risk_level[country] }}
+
+ +

Bypass Censorship

+

Geography Censorship Report

+ + + + + + + + {% for country in countries %} + + + + + + + {% endfor %} +
CountryRisk Level24h72h
{{ country[0].country_code | country_flag }} {{ country[0].description }}{{ country[0].risk_level }}{{ country[1] }}{{ country[2] }}