From d4a03623544aac0ce911d8471ff4b033bc3255e1 Mon Sep 17 00:00:00 2001 From: Dennis Fink Date: Mon, 17 Aug 2026 20:06:22 +0200 Subject: feat(sender): add manual source rescan action Add an authenticated action to queue a source rescan from the sent Webmentions overview. Extract the reusable confirmation modal and its JavaScript so received and sent admin actions share the same implementation. --- webmentions_ssg/static/js/confirmation-modal.js | 26 ++++++++++ webmentions_ssg/tasks/scanner.py | 9 +++- .../templates/includes/confirmation_modal.html | 27 ++++++++++ webmentions_ssg/templates/received.html | 57 +--------------------- webmentions_ssg/templates/sent.html | 19 +++++++- webmentions_ssg/views.py | 18 ++++++- 6 files changed, 97 insertions(+), 59 deletions(-) create mode 100644 webmentions_ssg/static/js/confirmation-modal.js create mode 100644 webmentions_ssg/templates/includes/confirmation_modal.html (limited to 'webmentions_ssg') diff --git a/webmentions_ssg/static/js/confirmation-modal.js b/webmentions_ssg/static/js/confirmation-modal.js new file mode 100644 index 0000000..e278790 --- /dev/null +++ b/webmentions_ssg/static/js/confirmation-modal.js @@ -0,0 +1,26 @@ +document.addEventListener("DOMContentLoaded", () => { + const confirmationModal = document.getElementById("confirmation-modal"); + + if (confirmationModal === null) { + return; + } + + confirmationModal.addEventListener("show.bs.modal", (event) => { + const button = event.relatedTarget; + + const form = confirmationModal.querySelector("#confirmation-modal-form"); + const title = confirmationModal.querySelector("#confirmation-modal-title"); + const message = confirmationModal.querySelector( + "#confirmation-modal-message", + ); + const submit = form.querySelector("#submit"); + + form.action = button.dataset.action; + + title.textContent = button.dataset.title; + message.textContent = button.dataset.message; + + submit.value = button.dataset.confirmLabel; + submit.className = `btn ${button.dataset.confirmClass}`; + }); +}); diff --git a/webmentions_ssg/tasks/scanner.py b/webmentions_ssg/tasks/scanner.py index f80e5f4..ff75697 100644 --- a/webmentions_ssg/tasks/scanner.py +++ b/webmentions_ssg/tasks/scanner.py @@ -1,6 +1,6 @@ from collections.abc import Iterator from dataclasses import dataclass -from datetime import datetime, timezone +from datetime import UTC, datetime from fnmatch import fnmatchcase from itertools import chain from pathlib import Path @@ -327,7 +327,7 @@ def scan_sources() -> None: seen_paths: set[str] = set() scanned_count = 0 - scan_time = datetime.now(timezone.utc) + scan_time = datetime.now(UTC) for index_path in root.rglob("index.html"): relative_path = index_path.relative_to(root) @@ -395,3 +395,8 @@ def scan_sources() -> None: scanned_count, pending_count, ) + + +@huey.task() +def manual_scan_sources() -> None: + return scan_sources() diff --git a/webmentions_ssg/templates/includes/confirmation_modal.html b/webmentions_ssg/templates/includes/confirmation_modal.html new file mode 100644 index 0000000..ff9bd06 --- /dev/null +++ b/webmentions_ssg/templates/includes/confirmation_modal.html @@ -0,0 +1,27 @@ + diff --git a/webmentions_ssg/templates/received.html b/webmentions_ssg/templates/received.html index a44b706..525e811 100644 --- a/webmentions_ssg/templates/received.html +++ b/webmentions_ssg/templates/received.html @@ -84,65 +84,12 @@ {% if webmentions.pages > 1 %}
{{ render_pagination(webmentions) }}
{% endif %} - + {% include "includes/confirmation_modal.html" %} {% else %}

No Webmentions have been received yet.

{% endif %} {% endblock %} {% block scripts %} {{ super() }} - + {% endblock %} diff --git a/webmentions_ssg/templates/sent.html b/webmentions_ssg/templates/sent.html index 4993bef..bc9e021 100644 --- a/webmentions_ssg/templates/sent.html +++ b/webmentions_ssg/templates/sent.html @@ -1,9 +1,21 @@ {% extends "base.html" %} +{% from "bootstrap5/form.html" import render_form %} {% from "bootstrap5/pagination.html" import render_pagination %} {% block content %}

Sources

- {{ sources.total }} sources +
+ {{ sources.total }} sources + +
{% if sources.items %}
@@ -60,7 +72,12 @@ {% if sources.pages > 1 %}
{{ render_pagination(sources) }}
{% endif %} + {% include "includes/confirmation_modal.html" %} {% else %}

No Webmention sources have been discovered yet.

{% endif %} {% endblock %} +{% block scripts %} + {{ super() }} + +{% endblock %} diff --git a/webmentions_ssg/views.py b/webmentions_ssg/views.py index eeea765..13e5086 100644 --- a/webmentions_ssg/views.py +++ b/webmentions_ssg/views.py @@ -22,6 +22,7 @@ from . import CSRF, forms from . import DATABASE as db from .models import ReceivedWebmention, Source, User from .tasks.receiver import verify_webmention +from .tasks.scanner import manual_scan_sources root_page = Blueprint("root", __name__) @@ -138,7 +139,22 @@ def sent() -> ResponseReturnValue: .order_by(Source.last_seen_at.desc()), per_page=25, ) - return render_template("sent.html", title="Sent Webmentions", sources=sources) + return render_template( + "sent.html", + title="Sent Webmentions", + sources=sources, + action_form=forms.AdminActionForm(), + ) + + +@root_page.post("/sent/rescan") +@login_required +def rescan_sent_sources(): + form = forms.AdminActionForm() + if form.validate_on_submit(): + manual_scan_sources() + flash("Source rescan queued.", "success") + return redirect(url_for("root.sent")) @root_page.route("/sent/") -- cgit v1.3.1