159 lines
5.2 KiB
Python
159 lines
5.2 KiB
Python
from __future__ import annotations
|
|
|
|
from collections.abc import Mapping
|
|
|
|
import htpy as h
|
|
from htpy import Node, Renderable
|
|
|
|
from repub.components import (
|
|
app_shell,
|
|
header_action_link,
|
|
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() -> 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")[
|
|
header_action_link(href="/sources/create", label="Create source"),
|
|
muted_action_link(href="/sources", label="View sources"),
|
|
],
|
|
]
|
|
]
|
|
|
|
|
|
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]) -> 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"])]
|
|
)
|
|
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"])
|
|
],
|
|
],
|
|
h.div(class_="min-w-64")[
|
|
inline_link(
|
|
href=str(source_feed["feed_href"]),
|
|
label=str(source_feed["feed_href"]),
|
|
tone="amber",
|
|
)
|
|
],
|
|
status_badge(
|
|
label=str(source_feed["feed_status_label"]),
|
|
tone=str(source_feed["feed_status_tone"]),
|
|
),
|
|
last_updated,
|
|
h.p(class_="font-medium text-slate-900")[
|
|
str(source_feed["artifact_footprint"])
|
|
],
|
|
)
|
|
|
|
|
|
def published_feeds_table(
|
|
*, source_feeds: tuple[Mapping[str, object], ...] | None = None
|
|
) -> Renderable:
|
|
rows = tuple(_source_feed_row(source_feed) for source_feed in (source_feeds or ()))
|
|
return table_section(
|
|
eyebrow="Published feeds",
|
|
title="Published feeds",
|
|
empty_message="No feeds have been published yet.",
|
|
headers=("Source", "Feed URL", "Status", "Last updated", "Disk usage"),
|
|
rows=rows,
|
|
actions=muted_action_link(href="/sources", label="Manage sources"),
|
|
)
|
|
|
|
|
|
def dashboard_page() -> Renderable:
|
|
return dashboard_page_with_data()
|
|
|
|
|
|
def dashboard_page_with_data(
|
|
*,
|
|
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,
|
|
) -> Renderable:
|
|
running_items = running_executions or ()
|
|
queued_items = queued_executions or ()
|
|
source_items = source_feeds or ()
|
|
return app_shell(
|
|
current_path="/",
|
|
source_count=len(source_items),
|
|
running_count=len(running_items),
|
|
content=(
|
|
dashboard_header(),
|
|
operational_snapshot(snapshot=snapshot),
|
|
live_work_section(
|
|
running_executions=running_items,
|
|
queued_executions=queued_items,
|
|
),
|
|
published_feeds_table(source_feeds=source_items),
|
|
relative_time_formatter_script(),
|
|
),
|
|
)
|