Refine publisher dashboard layout
All checks were successful
buildbot/nix-eval Build done.
buildbot/nix-build Build done.
buildbot/nix-effects Build done.

This commit is contained in:
Abel Luck 2026-06-02 11:11:36 +02:00
parent 2147d9c999
commit 813f19f355
8 changed files with 350 additions and 53 deletions

View file

@ -93,31 +93,73 @@ def operational_snapshot(*, snapshot: Mapping[str, str] | None = None) -> Render
]
def _source_feed_time(
source_feed: Mapping[str, object],
*,
iso_key: str,
label_key: str,
class_name: str,
data_attr: str | None = None,
inline: bool = False,
) -> Node:
iso_value = source_feed.get(iso_key)
label = str(source_feed[label_key])
if iso_value is not None:
attrs = {
"datetime": str(iso_value),
"title": str(iso_value),
"class": class_name,
}
if data_attr is not None:
attrs[data_attr] = str(iso_value)
return h.time(attrs)[label]
if inline:
return h.span(class_=class_name)[label]
return h.p(class_=class_name)[label]
def _source_feed_row(
source_feed: Mapping[str, object], *, show_feed_url: bool
source_feed: Mapping[str, object], *, show_feed_url: bool, compact_mobile: 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"])]
last_updated = _source_feed_time(
source_feed,
iso_key="last_updated_iso",
label_key="last_updated",
class_name="font-medium text-slate-900",
)
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"])]
next_run = _source_feed_time(
source_feed,
iso_key="next_run_at",
label_key="next_run",
class_name="font-medium text-slate-900",
data_attr="data-next-run-at",
)
mobile_meta = (
h.div(class_="mt-2 grid gap-1 text-xs text-slate-500 md:hidden")[
h.p(class_="flex flex-wrap gap-x-1.5")[
h.span(class_="font-medium text-slate-600")["Updated"],
_source_feed_time(
source_feed,
iso_key="last_updated_iso",
label_key="last_updated",
class_name="font-medium text-slate-700",
inline=True,
),
],
h.p(class_="flex flex-wrap gap-x-1.5")[
h.span(class_="font-medium text-slate-600")["Next"],
_source_feed_time(
source_feed,
iso_key="next_run_at",
label_key="next_run",
class_name="font-medium text-slate-700",
data_attr="data-next-run-at",
inline=True,
),
],
]
if compact_mobile
else None
)
feed_url_cells = (
(
@ -138,6 +180,7 @@ def _source_feed_row(
h.p(class_="mt-0.5 font-mono text-[11px] text-slate-500")[
str(source_feed["slug"])
],
mobile_meta,
],
*feed_url_cells,
status_badge(
@ -160,12 +203,40 @@ def published_feeds_table(
manage_sources_href: str | None = "/admin/sources",
show_heading: bool = True,
show_feed_url: bool = True,
compact_mobile: bool = False,
) -> Renderable:
rows = tuple(
_source_feed_row(source_feed, show_feed_url=show_feed_url)
_source_feed_row(
source_feed,
show_feed_url=show_feed_url,
compact_mobile=compact_mobile,
)
for source_feed in (source_feeds or ())
)
feed_url_headers = ("Feed URL",) if show_feed_url else ()
use_compact_columns = compact_mobile and not show_feed_url
header_classes = (
(
"w-[48%] px-3 py-2.5 text-left text-xs font-semibold uppercase tracking-[0.18em] whitespace-nowrap text-slate-500 md:w-[32%] sm:pl-4",
"w-[26%] px-2.5 py-2.5 text-left text-xs font-semibold uppercase tracking-[0.18em] whitespace-nowrap text-slate-500 md:w-[16%]",
"hidden px-2.5 py-2.5 text-left text-xs font-semibold uppercase tracking-[0.18em] whitespace-nowrap text-slate-500 md:table-cell md:w-[22%]",
"hidden px-2.5 py-2.5 text-left text-xs font-semibold uppercase tracking-[0.18em] whitespace-nowrap text-slate-500 md:table-cell md:w-[18%]",
"w-[26%] px-2.5 py-2.5 text-right text-xs font-semibold uppercase tracking-[0.18em] whitespace-nowrap text-slate-500 md:w-[12%]",
)
if use_compact_columns
else None
)
cell_classes = (
(
"w-[48%] py-3 pr-3 pl-3 text-sm font-medium text-slate-950 md:w-[32%] sm:pl-4",
"w-[26%] px-2.5 py-3 align-top text-sm whitespace-nowrap text-slate-600 md:w-[16%]",
"hidden px-2.5 py-3 align-top text-sm whitespace-nowrap text-slate-600 md:table-cell md:w-[22%]",
"hidden px-2.5 py-3 align-top text-sm whitespace-nowrap text-slate-600 md:table-cell md:w-[18%]",
"w-[26%] px-2.5 py-3 text-right align-top text-sm whitespace-nowrap text-slate-600 md:w-[12%]",
)
if use_compact_columns
else None
)
return table_section(
eyebrow="Published feeds" if show_heading else None,
title="Published feeds" if show_heading else None,
@ -179,6 +250,13 @@ def published_feeds_table(
"Actions",
),
rows=rows,
header_classes=header_classes,
cell_classes=cell_classes,
table_class=(
"relative w-full min-w-0 divide-y divide-slate-200 table-fixed"
if use_compact_columns
else "relative w-full min-w-[64rem] divide-y divide-slate-200 table-auto"
),
actions=(
muted_action_link(href=manage_sources_href, label="Manage sources")
if manage_sources_href is not None

View file

@ -47,11 +47,13 @@ def publisher_page(
manage_sources_href=None,
show_heading=False,
show_feed_url=False,
compact_mobile=True,
),
live_work_section(
running_executions=running_executions,
queued_executions=queued_executions,
show_row_actions=False,
compact_mobile=True,
),
relative_time_formatter_script(),
),

View file

@ -161,8 +161,11 @@ def _live_status_cell(
status_tone: str,
clock_label: str,
calendar_label: Node,
compact_mobile: bool = False,
) -> Node:
return h.div(class_="min-w-[10rem]")[
return h.div(
class_=("min-w-0 md:min-w-[10rem]" if compact_mobile else "min-w-[10rem]")
)[
h.div(class_="flex items-center gap-2")[
h.span(class_="font-mono text-xs text-slate-500")[f"#{execution_id}"],
h.span(
@ -191,7 +194,10 @@ def _queue_row_attrs(execution: Mapping[str, object]) -> dict[str, str]:
def _running_row(
execution: Mapping[str, object], *, show_row_actions: bool = True
execution: Mapping[str, object],
*,
show_row_actions: bool = True,
compact_mobile: bool = False,
) -> tuple[Node, ...]:
started_at = _maybe_text(execution, "started_at_iso")
started_at_label: Node = h.p(class_="truncate")[_text(execution, "started_at")]
@ -205,6 +211,20 @@ def _running_row(
class_="truncate",
)[_text(execution, "started_at")]
mobile_details = (
h.div(class_="mt-2 grid gap-1 text-xs text-slate-500 md:hidden")[
h.p(class_="flex flex-wrap gap-x-1.5")[
h.span(class_="font-medium text-slate-600")["Stats"],
h.span[_text(execution, "stats")],
],
h.p(class_="flex flex-wrap gap-x-1.5")[
h.span(class_="font-medium text-slate-600")["Worker"],
h.span[_text(execution, "worker")],
],
]
if compact_mobile
else None
)
cells = (
_live_status_cell(
execution_id=_text(execution, "execution_id"),
@ -213,12 +233,14 @@ def _running_row(
clock_label=_maybe_text(execution, "duration")
or _text(execution, "runtime"),
calendar_label=started_at_label,
compact_mobile=compact_mobile,
),
h.div[
h.div(class_="font-semibold text-slate-950")[_text(execution, "source")],
h.p(class_="mt-0.5 font-mono text-xs text-slate-500")[
_text(execution, "slug")
],
mobile_details,
],
h.div(class_="max-w-xs whitespace-normal")[
h.p(class_="font-medium text-slate-900")[_text(execution, "stats")],
@ -245,7 +267,10 @@ def _running_row(
def _queued_row(
execution: Mapping[str, object], *, show_row_actions: bool = True
execution: Mapping[str, object],
*,
show_row_actions: bool = True,
compact_mobile: bool = False,
) -> tuple[Node, ...]:
queued_at = _maybe_text(execution, "queued_at_iso")
queued_label: Node = h.p(class_="truncate")[_text(execution, "queued_at")]
@ -259,6 +284,20 @@ def _queued_row(
class_="truncate",
)[_text(execution, "queued_at")]
mobile_details = (
h.div(class_="mt-2 grid gap-1 text-xs text-slate-500 md:hidden")[
h.p(class_="flex flex-wrap gap-x-1.5")[
h.span(class_="font-medium text-slate-600")["Queued"],
h.span[f"Queue position #{_text(execution, 'queue_position')}"],
],
h.p(class_="flex flex-wrap gap-x-1.5")[
h.span(class_="font-medium text-slate-600")["State"],
h.span["waiting for capacity"],
],
]
if compact_mobile
else None
)
cells = (
_live_status_cell(
execution_id=_text(execution, "execution_id"),
@ -266,12 +305,14 @@ def _queued_row(
status_tone="queued",
clock_label="Waiting",
calendar_label=queued_label,
compact_mobile=compact_mobile,
),
h.div[
h.div(class_="font-semibold text-slate-950")[_text(execution, "source")],
h.p(class_="mt-0.5 font-mono text-xs text-slate-500")[
_text(execution, "slug")
],
mobile_details,
],
h.div(class_="max-w-xs whitespace-normal")[
h.p(class_="font-medium text-slate-900")[
@ -525,21 +566,49 @@ def live_work_section(
queued_executions: tuple[Mapping[str, object], ...] | None = None,
actions: Node | None = None,
show_row_actions: bool = True,
compact_mobile: bool = False,
) -> Renderable:
running_items = running_executions or ()
queued_items = queued_executions or ()
running_rows = tuple(
_running_row(execution, show_row_actions=show_row_actions)
_running_row(
execution,
show_row_actions=show_row_actions,
compact_mobile=compact_mobile,
)
for execution in running_items
)
queued_rows = tuple(
_queued_row(execution, show_row_actions=show_row_actions)
_queued_row(
execution,
show_row_actions=show_row_actions,
compact_mobile=compact_mobile,
)
for execution in queued_items
)
live_rows = running_rows + queued_rows
live_row_attrs = tuple(
_queue_row_attrs(execution) for execution in running_items + queued_items
)
use_compact_columns = compact_mobile and not show_row_actions
header_classes = (
(
"w-[34%] px-3 py-2.5 text-left text-xs font-semibold uppercase tracking-[0.18em] whitespace-nowrap text-slate-500 md:w-[24%] sm:pl-4",
"w-[66%] px-2.5 py-2.5 text-left text-xs font-semibold uppercase tracking-[0.18em] whitespace-nowrap text-slate-500 md:w-[34%]",
"hidden px-2.5 py-2.5 text-left text-xs font-semibold uppercase tracking-[0.18em] whitespace-nowrap text-slate-500 md:table-cell md:w-[42%]",
)
if use_compact_columns
else None
)
cell_classes = (
(
"w-[34%] py-3 pr-3 pl-3 text-sm font-medium text-slate-950 md:w-[24%] sm:pl-4",
"w-[66%] px-2.5 py-3 align-top text-sm whitespace-normal text-slate-600 md:w-[34%]",
"hidden px-2.5 py-3 align-top text-sm whitespace-normal text-slate-600 md:table-cell md:w-[42%]",
)
if use_compact_columns
else None
)
return table_section(
eyebrow="Live work",
title="Running jobs",
@ -551,6 +620,13 @@ def live_work_section(
),
rows=live_rows,
row_attrs=live_row_attrs,
header_classes=header_classes,
cell_classes=cell_classes,
table_class=(
"relative w-full min-w-0 divide-y divide-slate-200 table-fixed"
if use_compact_columns
else "relative w-full min-w-[64rem] divide-y divide-slate-200 table-auto"
),
actions=actions,
)