Add publisher dashboard routes
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 10:18:59 +02:00
parent 96551c2788
commit e4a5246ab3
31 changed files with 1603 additions and 516 deletions

View file

@ -190,7 +190,9 @@ def _queue_row_attrs(execution: Mapping[str, object]) -> dict[str, str]:
}
def _running_row(execution: Mapping[str, object]) -> tuple[Node, ...]:
def _running_row(
execution: Mapping[str, object], *, show_row_actions: bool = True
) -> tuple[Node, ...]:
started_at = _maybe_text(execution, "started_at_iso")
started_at_label: Node = h.p(class_="truncate")[_text(execution, "started_at")]
if started_at is not None:
@ -203,7 +205,7 @@ def _running_row(execution: Mapping[str, object]) -> tuple[Node, ...]:
class_="truncate",
)[_text(execution, "started_at")]
return (
cells = (
_live_status_cell(
execution_id=_text(execution, "execution_id"),
status=_text(execution, "status"),
@ -222,6 +224,11 @@ def _running_row(execution: Mapping[str, object]) -> tuple[Node, ...]:
h.p(class_="font-medium text-slate-900")[_text(execution, "stats")],
h.p(class_="mt-0.5 text-xs text-slate-500")[_text(execution, "worker")],
],
)
if not show_row_actions:
return cells
return (
*cells,
h.div(class_="flex flex-wrap items-center gap-2")[
inline_link(
href=_text(execution, "log_href"),
@ -237,7 +244,9 @@ def _running_row(execution: Mapping[str, object]) -> tuple[Node, ...]:
)
def _queued_row(execution: Mapping[str, object]) -> tuple[Node, ...]:
def _queued_row(
execution: Mapping[str, object], *, show_row_actions: bool = True
) -> tuple[Node, ...]:
queued_at = _maybe_text(execution, "queued_at_iso")
queued_label: Node = h.p(class_="truncate")[_text(execution, "queued_at")]
if queued_at is not None:
@ -250,7 +259,7 @@ def _queued_row(execution: Mapping[str, object]) -> tuple[Node, ...]:
class_="truncate",
)[_text(execution, "queued_at")]
return (
cells = (
_live_status_cell(
execution_id=_text(execution, "execution_id"),
status="Queued",
@ -270,6 +279,11 @@ def _queued_row(execution: Mapping[str, object]) -> tuple[Node, ...]:
],
h.p(class_="mt-0.5 text-xs text-slate-500")["waiting for capacity"],
],
)
if not show_row_actions:
return cells
return (
*cells,
h.div(class_="flex flex-wrap items-center gap-2")[
action_button(
label=_queue_icon("up"),
@ -362,8 +376,8 @@ def _completed_row(execution: Mapping[str, object]) -> tuple[Node, ...]:
)
def _completed_page_action_path(page: int) -> str:
return f"/actions/runs/completed-page/{page}"
def _completed_page_action_path(page: int, *, path_prefix: str = "/admin") -> str:
return f"{path_prefix}/actions/runs/completed-page/{page}"
def _pagination_button(
@ -372,9 +386,12 @@ def _pagination_button(
page: int,
current: bool = False,
class_name: str,
path_prefix: str = "/admin",
) -> Renderable:
attributes = {
"data-on:pointerdown": f"@post('{_completed_page_action_path(page)}')",
"data-on:pointerdown": (
f"@post('{_completed_page_action_path(page, path_prefix=path_prefix)}')"
),
}
if current:
attributes["aria-current"] = "page"
@ -391,6 +408,7 @@ def _completed_history_pagination(
completed_page_size: int,
completed_total_count: int,
completed_total_pages: int,
path_prefix: str = "/admin",
) -> Renderable | None:
if completed_total_count <= completed_page_size:
return None
@ -410,6 +428,7 @@ def _completed_history_pagination(
_pagination_button(
label="Previous",
page=max(1, completed_page - 1),
path_prefix=path_prefix,
class_name=(
"relative inline-flex items-center rounded-xl border border-slate-200 "
"bg-white px-4 py-2 text-sm font-medium text-slate-700 hover:bg-stone-50"
@ -418,6 +437,7 @@ def _completed_history_pagination(
_pagination_button(
label="Next",
page=min(completed_total_pages, completed_page + 1),
path_prefix=path_prefix,
class_name=(
"relative ml-3 inline-flex items-center rounded-xl border border-slate-200 "
"bg-white px-4 py-2 text-sm font-medium text-slate-700 hover:bg-stone-50"
@ -443,6 +463,7 @@ def _completed_history_pagination(
label=str(page_number),
page=page_number,
current=page_number == completed_page,
path_prefix=path_prefix,
class_name=(
"relative z-10 inline-flex items-center bg-amber-500 px-4 py-2 text-sm font-semibold text-slate-950"
if page_number == completed_page
@ -463,12 +484,14 @@ def _completed_history_section(
completed_page_size: int,
completed_total_count: int,
completed_total_pages: int,
path_prefix: str = "/admin",
) -> Renderable:
pagination = _completed_history_pagination(
completed_page=completed_page,
completed_page_size=completed_page_size,
completed_total_count=completed_total_count,
completed_total_pages=completed_total_pages,
path_prefix=path_prefix,
)
return h.section[
table_section(
@ -486,7 +509,7 @@ def _completed_history_section(
action_button(
label="Clear history",
tone="danger",
post_path="/actions/completed-executions/clear",
post_path=f"{path_prefix}/actions/completed-executions/clear",
)
if completed_total_count > 0
else None
@ -501,11 +524,18 @@ def live_work_section(
running_executions: tuple[Mapping[str, object], ...] | None = None,
queued_executions: tuple[Mapping[str, object], ...] | None = None,
actions: Node | None = None,
show_row_actions: bool = True,
) -> Renderable:
running_items = running_executions or ()
queued_items = queued_executions or ()
running_rows = tuple(_running_row(execution) for execution in running_items)
queued_rows = tuple(_queued_row(execution) for execution in queued_items)
running_rows = tuple(
_running_row(execution, show_row_actions=show_row_actions)
for execution in running_items
)
queued_rows = tuple(
_queued_row(execution, show_row_actions=show_row_actions)
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
@ -515,10 +545,9 @@ def live_work_section(
title="Running jobs",
empty_message="No jobs are running or queued.",
headers=(
"State",
"Source",
"Details",
"Actions",
("State", "Source", "Details", "Actions")
if show_row_actions
else ("State", "Source", "Details")
),
rows=live_rows,
row_attrs=live_row_attrs,
@ -585,6 +614,7 @@ def runs_page(
completed_total_count: int | None = None,
completed_total_pages: int | None = None,
source_count: int = 0,
path_prefix: str = "/admin",
) -> Renderable:
upcoming_items = upcoming_jobs or ()
completed_items = completed_executions or ()
@ -598,10 +628,13 @@ def runs_page(
)
return page_shell(
current_path="/runs",
current_path=f"{path_prefix}/runs",
eyebrow="Execution control",
title="Runs",
actions=muted_action_link(href="/sources", label="Back to sources"),
actions=muted_action_link(
href=f"{path_prefix}/sources",
label="Back to sources",
),
source_count=source_count,
running_count=len(running_executions or ()),
content=(
@ -629,6 +662,7 @@ def runs_page(
completed_page_size=completed_page_size,
completed_total_count=resolved_completed_total_count,
completed_total_pages=resolved_completed_total_pages,
path_prefix=path_prefix,
),
relative_time_formatter_script(),
),
@ -640,6 +674,7 @@ def execution_logs_page(
job_id: int,
execution_id: int,
log_view: Mapping[str, object] | None = None,
path_prefix: str = "/admin",
) -> Renderable:
if log_view is None:
log_view = {
@ -664,10 +699,10 @@ def execution_logs_page(
)
return page_shell(
current_path=f"/job/{job_id}/execution/{execution_id}/logs",
current_path=f"{path_prefix}/job/{job_id}/execution/{execution_id}/logs",
eyebrow="Execution log",
title=_text(log_view, "title"),
actions=muted_action_link(href="/runs", label="Back to runs"),
actions=muted_action_link(href=f"{path_prefix}/runs", label="Back to runs"),
content=(
section_card(
content=(
@ -677,7 +712,7 @@ def execution_logs_page(
class_="text-xs font-semibold uppercase tracking-[0.22em] text-amber-600"
)["Route"],
h.h2(class_="mt-2 text-xl font-semibold text-slate-950")[
f"/job/{job_id}/execution/{execution_id}/logs"
f"{path_prefix}/job/{job_id}/execution/{execution_id}/logs"
],
],
status_badge(