aboutsummaryrefslogtreecommitdiff
path: root/webmentions_ssg/templates
diff options
context:
space:
mode:
authorDennis Fink2026-08-09 14:15:29 +0200
committerDennis Fink2026-08-09 14:15:29 +0200
commite4a8194e6de25e8a48e2c61ce7f89a8159fd99d9 (patch)
tree234f63980aba8fe3c8d03e7bc8959598bce7e790 /webmentions_ssg/templates
parentf53c1136184d2afabfb1e5974e527229aa71939a (diff)
downloadwebmentions-ssg-e4a8194e6de25e8a48e2c61ce7f89a8159fd99d9.tar.gz
webmentions-ssg-e4a8194e6de25e8a48e2c61ce7f89a8159fd99d9.zip
Implement received Webmention handling
Add the Flask application setup, database models and migrations, authentication, and configuration for development and testing. Implement asynchronous Webmention verification with Huey, including HTML and plain-text source validation, retries, status tracking, and size limits. Add status, login, and paginated received-Webmention views together with comprehensive tests for forms, views, and receiver tasks.
Diffstat (limited to 'webmentions_ssg/templates')
-rw-r--r--webmentions_ssg/templates/base.html44
-rw-r--r--webmentions_ssg/templates/login.html8
-rw-r--r--webmentions_ssg/templates/received.html69
-rw-r--r--webmentions_ssg/templates/status.html64
4 files changed, 185 insertions, 0 deletions
diff --git a/webmentions_ssg/templates/base.html b/webmentions_ssg/templates/base.html
new file mode 100644
index 0000000..237a437
--- /dev/null
+++ b/webmentions_ssg/templates/base.html
@@ -0,0 +1,44 @@
+{% from "bootstrap5/utils.html" import render_messages %}
+<!DOCTYPE html>
+<html lang="en" data-bs-theme="dark">
+ <head>
+ {% block head %}
+ <!-- Required meta tags -->
+ <meta charset="utf-8">
+ <meta name="viewport"
+ content="width=device-width, initial-scale=1, shrink-to-fit=no">
+ {% block styles %}
+ <!-- Bootstrap CSS -->
+ {{ bootstrap.load_css() }}
+ {% endblock %}
+ <title>{{ title }}</title>
+ {% endblock %}
+ </head>
+ <body>
+ <header>
+ <nav class="navbar bg-primary py-1">
+ <div class="container-fluid px3">
+ <a class="navbar-brand fs-6 mb-0" href="{{ url_for("root.index") }}">Webmentions</a>
+ {% if current_user.is_authenticated %}
+ <div class="navbar-nav flex-row gap-3">
+ <a class="nav-link{% if request.endpoint == 'root.received' %} active{% endif %}"
+ href="{{ url_for("root.received") }}">Received</a>
+ <span class="nav-link disabled">Sent</span>
+ <a class="nav-link" href="{{ url_for("root.logout") }}">Logout</a>
+ </div>
+ {% endif %}
+ </div>
+ </div>
+ </nav>
+ </header>
+ <div class="mt-4">{{ render_messages(container=True) }}</div>
+ <div class="container py-4">
+ {% block content %}
+ {% endblock %}
+ </div>
+ {% block scripts %}
+ <!-- Optional JavaScript -->
+ {{ bootstrap.load_js() }}
+ {% endblock %}
+</body>
+</html>
diff --git a/webmentions_ssg/templates/login.html b/webmentions_ssg/templates/login.html
new file mode 100644
index 0000000..e7d858c
--- /dev/null
+++ b/webmentions_ssg/templates/login.html
@@ -0,0 +1,8 @@
+{% extends "base.html" %}
+{% set title = "Login" %}
+{% from "bootstrap5/form.html" import render_form %}
+{% block content %}
+ <div class="row justify-content-center">
+ <div class="col-12 col-sm-8 col-md-6 col-lg-4">{{ render_form(form) }}</div>
+ </div>
+{% endblock %}
diff --git a/webmentions_ssg/templates/received.html b/webmentions_ssg/templates/received.html
new file mode 100644
index 0000000..ea74557
--- /dev/null
+++ b/webmentions_ssg/templates/received.html
@@ -0,0 +1,69 @@
+{% extends "base.html" %}
+{% from "bootstrap5/pagination.html" import render_pagination %}
+{% block content %}
+ <div class="d-flex justify-content-between align-items-center mb-3">
+ <h1 class="h3 mb-0">Received Webmentions</h1>
+ <span class="text-body-secondary">{{ webmentions.total }} total</span>
+ </div>
+ {% if webmentions.items %}
+ <div class="table-responsive">
+ <table class="table table-bordered table-striped table-hover align-middle">
+ <thead>
+ <tr>
+ <th scope="col">Status</th>
+ <th scope="col">Source</th>
+ <th scope="col">Target</th>
+ <th scope="col">Received</th>
+ <th scope="col">Updated</th>
+ <th scope="col">Reason</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for webmention in webmentions %}
+ {% if webmention.status == "verified" %}
+ {% set row_class = "table-success" %}
+ {% set display_status = "Verified" %}
+ {% elif webmention.status in ("received", "verifying") %}
+ {% set row_class = "table-warning" %}
+ {% set display_status = "Pending" %}
+ {% else %}
+ {% set row_class = "table-danger" %}
+ {% set display_status = "Declined" %}
+ {% endif %}
+ <tr class="{{ row_class }}">
+ <td>
+ <strong>{{ display_status }}</strong>
+ </td>
+ <td class="text-break">
+ <a href="{{ webmention.source }}">{{ webmention.source }}</a>
+ </td>
+ <td class="text-break">
+ <a href="{{ webmention.target }}">{{ webmention.target }}</a>
+ </td>
+ <td class="text-nowrap">
+ <time datetime="{{ webmention.created_at.isoformat() }}">
+ {{ webmention.created_at.strftime("%Y-%m-%d %H:%M:%S") }}
+ </time>
+ </td>
+ <td class="text-nowrap">
+ <time datetime="{{ webmention.updated_at.isoformat() }}">
+ {{ webmention.updated_at.strftime("%Y-%m-%d %H:%M:%S") }}
+ </time>
+ </td>
+ <td>{{ webmention.failure_reason or "—" }}</td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ </div>
+ {% if webmentions.pages > 1 %}
+ <div class="mt-3">
+ {{ render_pagination(webmentions,
+ align="center",
+ size="sm") }}
+ </div>
+ {% endif %}
+ {% else %}
+ <p class="text-body-secondary">No Webmentions have been received yet.</p>
+ {% endif %}
+{% endblock %}
diff --git a/webmentions_ssg/templates/status.html b/webmentions_ssg/templates/status.html
new file mode 100644
index 0000000..451f26f
--- /dev/null
+++ b/webmentions_ssg/templates/status.html
@@ -0,0 +1,64 @@
+{% extends "base.html" %}
+{% from "bootstrap5/utils.html" import render_icon %}
+{% block content %}
+ <main class="py-5">
+ <div class="text-center mb-5">
+ {% if webmention.status == "verified" %}
+ <div class="text-success mb-3">{{ render_icon("check-circle-fill", size="5rem", title="Verified") }}</div>
+ <h1 class="h3">Webmention verified</h1>
+ {% elif webmention.status in ("received", "verifying") %}
+ <div class="text-warning mb-3">{{ render_icon("hourglass-split", size="5rem", title="Verification pending") }}</div>
+ <h1 class="h3">Webmention verification pending</h1>
+ {% elif webmention.status == "deleted" %}
+ <div class="text-danger mb-3">{{ render_icon("x-circle-fill", size="5rem", title="Not verified") }}</div>
+ <h1 class="h3">Webmention not verified</h1>
+ {% elif webmention.status == "failed" %}
+ <div class="text-danger mb-3">{{ render_icon("x-circle-fill", size="5rem", title="Verification failed") }}</div>
+ <h1 class="h3">Webmention verification failed</h1>
+ {% else %}
+ <div class="text-secondary mb-3">{{ render_icon("question-circle-fill", size="5rem", title="Unknown status") }}</div>
+ <h1 class="h3">Unknown Webmention status</h1>
+ {% endif %}
+ </div>
+ <div class="card mx-auto" style="max-width: 48rem;">
+ <div class="card-body">
+ <dl class="row mb-0">
+ <dt class="col-sm-3">Status</dt>
+ <dd class="col-sm-9">
+ {{ webmention.status }}
+ </dd>
+ <dt class="col-sm-3">Source</dt>
+ <dd class="col-sm-9 text-break">
+ <a href="{{ webmention.source }}">{{ webmention.source }}</a>
+ </dd>
+ <dt class="col-sm-3">Target</dt>
+ <dd class="col-sm-9 text-break">
+ <a href="{{ webmention.target }}">{{ webmention.target }}</a>
+ </dd>
+ <dt class="col-sm-3">Received</dt>
+ <dd class="col-sm-9">
+ <time datetime="{{ webmention.created_at.isoformat() }}">
+ {{ webmention.created_at.strftime("%Y-%m-%d %H:%M:%S %Z") }}
+ </time>
+ </dd>
+ <dt class="col-sm-3">Last updated</dt>
+ <dd class="col-sm-9">
+ <time datetime="{{ webmention.updated_at.isoformat() }}">
+ {{ webmention.updated_at.strftime("%Y-%m-%d %H:%M:%S %Z") }}
+ </time>
+ </dd>
+ <dt class="col-sm-3">ID</dt>
+ <dd class="col-sm-9">
+ <code>{{ webmention.uuid }}</code>
+ </dd>
+ {% if webmention.failure_reason %}
+ <dt class="col-sm-3">Reason</dt>
+ <dd class="col-sm-9">
+ {{ webmention.failure_reason }}
+ </dd>
+ {% endif %}
+ </dl>
+ </div>
+ </div>
+ </main>
+{% endblock %}