republisher/repub/pages/dashboard.py
Abel Luck 2147d9c999
All checks were successful
buildbot/nix-eval Build done.
buildbot/nix-build Build done.
buildbot/nix-effects Build done.
remove feed url from publisher dashboard
2026-06-02 10:56:36 +02:00

224 lines
7.1 KiB
Python

from __future__ import annotations
from collections.abc import Mapping
from typing import cast
import htpy as h
from htpy import Node, Renderable
from repub.components import (
action_button,
app_shell,
inline_link,
muted_action_link,
stat_card,
status_badge,
table_section,
)
from repub.pages.runs import live_work_section, relative_time_formatter_script
def dashboard_header(
*, path_prefix: str = "/admin", reader_app_url: str | None = None
) -> Renderable:
return h.section[
h.div(
class_="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between"
)[
h.div[
h.h1(class_="text-3xl font-semibold tracking-tight text-slate-950")[
"Republisher"
],
],
h.div(class_="flex flex-wrap gap-2")[
(
muted_action_link(
href=reader_app_url,
label="Open AnyNews",
target="_blank",
rel="noopener noreferrer",
)
if reader_app_url is not None
else None
),
muted_action_link(
href=f"{path_prefix}/publisher",
label="Publisher View",
),
],
]
]
def operational_snapshot(*, snapshot: Mapping[str, str] | None = None) -> Renderable:
values = snapshot or {
"running_now": "0",
"upcoming_today": "0",
"failures_24h": "0",
"artifact_footprint": "0 B",
}
return h.section[
h.div(class_="mb-3 flex items-end justify-between gap-4")[
h.div[
h.p(
class_="text-xs font-semibold uppercase tracking-[0.22em] text-slate-500"
)["Overview"],
h.h2(class_="mt-1 text-xl font-semibold tracking-tight text-slate-950")[
"Operational snapshot"
],
],
],
h.dl(class_="grid gap-3 md:grid-cols-2 xl:grid-cols-4")[
stat_card(
label="Running now",
value=values["running_now"],
detail="Currently active job executions.",
),
stat_card(
label="Upcoming today",
value=values["upcoming_today"],
detail="Enabled jobs that are ready for their next run.",
),
stat_card(
label="Failures in 24h",
value=values["failures_24h"],
detail="Recent failed executions recorded by the scheduler.",
),
stat_card(
label="Artifact footprint",
value=values["artifact_footprint"],
detail="Current artifact size under the output path.",
),
],
]
def _source_feed_row(
source_feed: Mapping[str, object], *, show_feed_url: bool
) -> tuple[Node, ...]:
last_updated_iso = source_feed.get("last_updated_iso")
last_updated = (
h.time(
datetime=str(last_updated_iso),
title=str(last_updated_iso),
class_="font-medium text-slate-900",
)[str(source_feed["last_updated"])]
if last_updated_iso is not None
else h.p(class_="font-medium text-slate-900")[str(source_feed["last_updated"])]
)
next_run_iso = source_feed.get("next_run_at")
next_run = (
h.time(
{
"data-next-run-at": str(next_run_iso),
"title": str(next_run_iso),
},
datetime=str(next_run_iso),
class_="font-medium text-slate-900",
)[str(source_feed["next_run"])]
if next_run_iso is not None
else h.p(class_="font-medium text-slate-900")[str(source_feed["next_run"])]
)
feed_url_cells = (
(
h.div(class_="min-w-64")[
inline_link(
href=str(source_feed["feed_href"]),
label=str(source_feed["feed_href"]),
tone="amber",
)
],
)
if show_feed_url
else ()
)
return (
h.div[
h.div(class_="font-semibold text-slate-950")[str(source_feed["source"])],
h.p(class_="mt-0.5 font-mono text-[11px] text-slate-500")[
str(source_feed["slug"])
],
],
*feed_url_cells,
status_badge(
label=str(source_feed["feed_status_label"]),
tone=str(source_feed["feed_status_tone"]),
),
last_updated,
next_run,
action_button(
label="Run now",
disabled=bool(source_feed["run_disabled"]),
post_path=cast(str | None, source_feed.get("run_post_path")),
),
)
def published_feeds_table(
*,
source_feeds: tuple[Mapping[str, object], ...] | None = None,
manage_sources_href: str | None = "/admin/sources",
show_heading: bool = True,
show_feed_url: bool = True,
) -> Renderable:
rows = tuple(
_source_feed_row(source_feed, show_feed_url=show_feed_url)
for source_feed in (source_feeds or ())
)
feed_url_headers = ("Feed URL",) if show_feed_url else ()
return table_section(
eyebrow="Published feeds" if show_heading else None,
title="Published feeds" if show_heading else None,
empty_message="No feeds have been published yet.",
headers=(
"Source",
*feed_url_headers,
"Status",
"Last updated",
"Next run",
"Actions",
),
rows=rows,
actions=(
muted_action_link(href=manage_sources_href, label="Manage sources")
if manage_sources_href is not None
else None
),
)
def dashboard_page() -> Renderable:
return dashboard_page_with_data()
def dashboard_page_with_data(
*,
current_path: str = "/admin",
path_prefix: str = "/admin",
snapshot: Mapping[str, str] | None = None,
running_executions: tuple[Mapping[str, object], ...] | None = None,
queued_executions: tuple[Mapping[str, object], ...] | None = None,
source_feeds: tuple[Mapping[str, object], ...] | None = None,
reader_app_url: str | None = None,
) -> Renderable:
running_items = running_executions or ()
queued_items = queued_executions or ()
source_items = source_feeds or ()
return app_shell(
current_path=current_path,
source_count=len(source_items),
running_count=len(running_items),
content=(
dashboard_header(path_prefix=path_prefix, reader_app_url=reader_app_url),
operational_snapshot(snapshot=snapshot),
live_work_section(
running_executions=running_items,
queued_executions=queued_items,
),
published_feeds_table(
source_feeds=source_items,
manage_sources_href=f"{path_prefix}/sources",
),
relative_time_formatter_script(),
),
)