]> git.ipfire.org Git - ipfire.org.git/commitdiff
dnsbl: Show more details about a list
authorMichael Tremer <michael.tremer@ipfire.org>
Mon, 29 Dec 2025 17:49:52 +0000 (17:49 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Mon, 29 Dec 2025 17:49:52 +0000 (17:49 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
Makefile.am
src/backend/dnsbl.py
src/templates/dnsbl/list.html [new file with mode: 0644]
src/web/__init__.py
src/web/dnsbl.py

index 2614058b33115b090c3615c14ba1104c44dd200a..f13fff0919041a6c607c134ede5c127180f872ed 100644 (file)
@@ -184,7 +184,8 @@ templates_blog_modules_DATA = \
 templates_blog_modulesdir = $(templates_blogdir)/modules
 
 templates_dnsbl_DATA = \
-       src/templates/dnsbl/index.html
+       src/templates/dnsbl/index.html \
+       src/templates/dnsbl/list.html
 
 templates_dnsbldir = $(templatesdir)/dnsbl
 
index 60f234b940ed303def0ca4281eb8a5e7266c2457..6244106a362a07243a651a9219cdfca6d34b0906 100644 (file)
@@ -4,6 +4,7 @@ import datetime
 import json
 import logging
 import pydantic
+import tornado.httpclient
 import urllib.parse
 
 from . import base
@@ -14,7 +15,7 @@ from .decorators import *
 log = logging.getLogger(__name__)
 
 class DNSBL(Object):
-       async def __fetch(self, path, **kwargs):
+       async def _fetch(self, path, **kwargs):
                url = urllib.parse.urljoin(
                        #"https://api.dnsbl.ipfire.org",
                        "http://dnsbl01.haj.ipfire.org:8000", path,
@@ -32,10 +33,24 @@ class DNSBL(Object):
                """
                        Fetches all available lists
                """
-               response = await self.__fetch("/lists")
+               response = await self._fetch("/lists")
 
                return [List(self.backend, **list) for list in response]
 
+       async def get_list(self, slug):
+               try:
+                       response = await self._fetch("/lists/%s" % slug)
+
+               # Return nothing if we received 404
+               except tornado.httpclient.HTTPClientError as e:
+                       if e.code == 404:
+                               return
+
+                       raise e
+
+               # Return the list
+               return List(self.backend, **response)
+
 
 class Model(pydantic.BaseModel):
        """
@@ -92,3 +107,57 @@ class List(Model):
                        return self.slug < other.slug
 
                return NotImplemented
+
+       # Sources
+
+       async def get_sources(self):
+               response = await self._backend.dnsbl._fetch("/lists/%s/sources" % self.slug)
+
+               return [Source(self._backend, **data) for data in response]
+
+
+class Source(Model):
+       """
+               Represents a source of a list
+       """
+       # Name
+       name : str
+
+       # URL
+       url : str
+
+       # Created At
+       created_at : datetime.datetime
+
+       # Created By
+       created_by : str
+
+       # Deleted At
+       deleted_at : datetime.datetime | None
+
+       # Deleted By
+       deleted_by : str | None
+
+       # License
+       license : str
+
+       # Updated At
+       updated_at : datetime.datetime | None
+
+       # Total Domains
+       total_domains : int
+
+       # Dead Domains
+       dead_domains : int
+
+       def __eq__(self, other):
+               if isinstance(other, self.__class__):
+                       return self.name == other.name
+
+               return NotImplemented
+
+       def __lt__(self, other):
+               if isinstance(other, self.__class__):
+                       return self.name < other.name
+
+               return NotImplemented
diff --git a/src/templates/dnsbl/list.html b/src/templates/dnsbl/list.html
new file mode 100644 (file)
index 0000000..5c00ee4
--- /dev/null
@@ -0,0 +1,90 @@
+{% extends "../base.html" %}
+
+{% block head %}
+       {% module OpenGraph(
+               title=_("IPFire DNSBL - %s") % list.name,
+               description=list.description,
+       ) %}
+{% end block %}
+
+{% block title %}
+       {{ _("IPFire DNSBL") }}{% if list.description %} - {{ list.description }}{% end %}
+{% end block %}
+
+{% block container %}
+       <section class="hero">
+               <div class="hero-body">
+                       <div class="container">
+                               <h1 class="title">
+                                       {{ list.name }}
+                               </h1>
+
+                               {% if list.description %}
+                                       <h6 class="subtitle">
+                                               {{ list.description }}
+                                       </h6>
+                               {% end %}
+                       </div>
+               </div>
+       </section>
+
+       {# Sources #}
+       {% if sources %}
+               <section>
+                       <div class="container">
+                               <h5 class="title is-5">{{ _("Sources") }}</h5>
+
+                               <table class="table is-fullwidth is-striped is-hoverable is-narrow">
+                                       <thead>
+                                               <tr>
+                                                       <th>
+                                                               {{ _("Name") }}
+                                                       </th>
+
+                                                       <th class="has-text-right">
+                                                               {{ _("Last Update") }}
+                                                       </th>
+
+                                                       <th class="has-text-right">
+                                                               {{ _("Listed Domains") }}
+                                                       </th>
+                                               </tr>
+                                       </thead>
+
+                                       <tbody>
+                                               {% for source in sorted(sources) %}
+                                                       <tr>
+                                                               <th scope="row">
+                                                                       <a href="{{ source.url }}" target="_blank">
+                                                                               {{ source.name }}
+                                                                       </a>
+
+                                                                       <br>
+
+                                                                       <small>{{ source.license }}</small>
+                                                               </th>
+
+                                                               <td class="has-text-right">
+                                                                       {{ locale.format_date(source.updated_at, shorter=True) }}
+                                                               </td>
+
+                                                               <td class="has-text-right">
+                                                                       {{ format_number(source.total_domains) }}
+
+                                                                       <br>
+
+                                                                       {# Dead Domains #}
+                                                                       {% if source.total_domains and source.dead_domains %}
+                                                                               <small>
+                                                                                       {{ _("Dead Domains: %s") % format_percent(source.dead_domains / source.total_domains) }}
+                                                                               </small>
+                                                                       {% end %}
+                                                               </td>
+                                                       </tr>
+                                               {% end %}
+                                       </tbody>
+                               </table>
+                       </div>
+               </section>
+       {% end %}
+{% end block %}
index cc81fc278134e50cce622b7c184ba3c5581c6173..0a50a39d947a406d718e911fe470d40415781142 100644 (file)
@@ -59,6 +59,7 @@ class Application(tornado.web.Application):
                                "format_language_name"         : self.format_language_name,
                                "format_month_name"            : self.format_month_name,
                                "format_number"                : self.format_number,
+                               "format_percent"               : self.format_percent,
                                "format_phone_number"          : self.format_phone_number,
                                "format_phone_number_to_e164"  : self.format_phone_number_to_e164,
                                "format_phone_number_location" : self.format_phone_number_location,
@@ -214,6 +215,7 @@ class Application(tornado.web.Application):
 
                        # DNSBL
                        (r"/dnsbl/?", dnsbl.IndexHandler),
+                       (r"/dnsbl/lists/(\w+)", dnsbl.ListHandler),
 
                        # Single-Sign-On for Discourse
                        (r"/sso/discourse", auth.SSODiscourse),
@@ -454,6 +456,9 @@ class Application(tornado.web.Application):
        def format_number(self, handler, *args, **kwargs):
                return babel.numbers.format_number(*args, locale=handler.locale.code, **kwargs)
 
+       def format_percent(self, handler, *args, **kwargs):
+               return babel.numbers.format_percent(*args, locale=handler.locale.code, **kwargs)
+
        def format_phone_number(self, handler, number):
                if not isinstance(number, phonenumbers.PhoneNumber):
                        try:
index 94147f10dc4533d82674847a460cf454956c9354..a8a4e21d965e1d971aa4bc34ab2f5bf2a3bec8eb 100644 (file)
@@ -1,4 +1,5 @@
 
+import tornado.web
 
 from . import base
 from . import ui_modules
@@ -12,6 +13,20 @@ class IndexHandler(base.AnalyticsMixin, base.BaseHandler):
                self.render("dnsbl/index.html", lists=lists)
 
 
+class ListHandler(base.AnalyticsMixin, base.BaseHandler):
+       async def get(self, slug):
+               # Fetch the list
+               list = await self.backend.dnsbl.get_list(slug)
+               if not list:
+                       raise tornado.web.HTTPError(404, "Could not find list '%s'" % slug)
+
+               # Fetch the sources
+               sources = await list.get_sources()
+
+               # Render the page
+               self.render("dnsbl/list.html", list=list, sources=sources)
+
+
 class ListsModule(ui_modules.UIModule):
        def render(self, lists):
                return self.render_string("dnsbl/modules/lists.html", lists=lists)