2026-03-30 12:13:04 +02:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import htpy as h
|
|
|
|
|
from htpy import Node, Renderable
|
|
|
|
|
|
|
|
|
|
from repub.components import (
|
2026-03-30 13:11:37 +02:00
|
|
|
admin_sidebar,
|
|
|
|
|
header_action_link,
|
|
|
|
|
inline_button,
|
|
|
|
|
inline_link,
|
|
|
|
|
muted_action_link,
|
2026-03-30 12:13:04 +02:00
|
|
|
stat_card,
|
|
|
|
|
status_badge,
|
|
|
|
|
)
|
2026-03-30 13:11:37 +02:00
|
|
|
from repub.pages.runs import RUNNING_EXECUTIONS
|
2026-03-30 12:13:04 +02:00
|
|
|
|
|
|
|
|
|
2026-03-30 13:11:37 +02:00
|
|
|
def _running_execution_row(execution: dict[str, str | bool]) -> tuple[Node, ...]:
|
|
|
|
|
status_tone = "running" if execution["is_running"] else "done"
|
|
|
|
|
return (
|
|
|
|
|
h.div[
|
|
|
|
|
h.div(class_="font-semibold text-slate-950")[execution["source"]],
|
|
|
|
|
h.p(class_="mt-0.5 font-mono text-[11px] text-slate-500")[
|
|
|
|
|
execution["slug"]
|
2026-03-30 12:13:04 +02:00
|
|
|
],
|
2026-03-30 13:11:37 +02:00
|
|
|
],
|
|
|
|
|
h.div[
|
|
|
|
|
h.p(class_="font-medium text-slate-900")[f"#{execution['execution_id']}"],
|
|
|
|
|
h.p(class_="mt-0.5 text-[11px] text-slate-500")[
|
|
|
|
|
f"job {execution['job_id']}"
|
2026-03-30 12:13:04 +02:00
|
|
|
],
|
|
|
|
|
],
|
2026-03-30 13:11:37 +02:00
|
|
|
h.div[
|
|
|
|
|
h.p(class_="font-medium text-slate-900")[execution["started_at"]],
|
|
|
|
|
h.p(class_="mt-0.5 text-[11px] text-slate-500")[execution["runtime"]],
|
|
|
|
|
],
|
|
|
|
|
status_badge(label=str(execution["status"]), tone=status_tone),
|
|
|
|
|
h.div(class_="min-w-56 whitespace-normal")[
|
|
|
|
|
h.p(class_="font-medium text-slate-900")[execution["stats"]],
|
|
|
|
|
h.p(class_="mt-0.5 text-[11px] text-slate-500")[execution["worker"]],
|
|
|
|
|
],
|
|
|
|
|
h.div(class_="flex flex-nowrap items-center gap-3")[
|
|
|
|
|
inline_link(
|
|
|
|
|
href=str(execution["log_href"]),
|
|
|
|
|
label="View log",
|
|
|
|
|
tone="amber",
|
|
|
|
|
),
|
|
|
|
|
inline_button(label="Stop", tone="danger"),
|
|
|
|
|
],
|
|
|
|
|
)
|
2026-03-30 12:13:04 +02:00
|
|
|
|
|
|
|
|
|
2026-03-30 13:11:37 +02:00
|
|
|
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"
|
2026-03-30 12:13:04 +02:00
|
|
|
],
|
2026-03-30 13:11:37 +02:00
|
|
|
h.p(class_="mt-1 text-sm text-slate-600")[
|
|
|
|
|
"Operational status and live executions."
|
2026-03-30 12:13:04 +02:00
|
|
|
],
|
|
|
|
|
],
|
2026-03-30 13:11:37 +02:00
|
|
|
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"),
|
2026-03-30 12:13:04 +02:00
|
|
|
],
|
2026-03-30 13:11:37 +02:00
|
|
|
]
|
2026-03-30 12:13:04 +02:00
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
2026-03-30 13:11:37 +02:00
|
|
|
def operational_snapshot() -> Renderable:
|
2026-03-30 12:13:04 +02:00
|
|
|
return h.section[
|
2026-03-30 13:11:37 +02:00
|
|
|
h.div(class_="mb-3 flex items-end justify-between gap-4")[
|
2026-03-30 12:13:04 +02:00
|
|
|
h.div[
|
|
|
|
|
h.p(
|
2026-03-30 13:11:37 +02:00
|
|
|
class_="text-xs font-semibold uppercase tracking-[0.22em] text-slate-500"
|
2026-03-30 12:13:04 +02:00
|
|
|
)["Overview"],
|
2026-03-30 13:11:37 +02:00
|
|
|
h.h2(class_="mt-1 text-xl font-semibold tracking-tight text-slate-950")[
|
|
|
|
|
"Operational snapshot"
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
h.p(class_="text-xs text-slate-500")[
|
|
|
|
|
"Static fixture data shaped around the intended operator dashboard"
|
2026-03-30 12:13:04 +02:00
|
|
|
],
|
|
|
|
|
],
|
2026-03-30 13:11:37 +02:00
|
|
|
h.dl(class_="grid gap-3 md:grid-cols-2 xl:grid-cols-4")[
|
2026-03-30 12:34:38 +02:00
|
|
|
stat_card(
|
2026-03-30 13:11:37 +02:00
|
|
|
label="Running now",
|
|
|
|
|
value="3",
|
|
|
|
|
detail="Two feed workers and one Pangea worker are active.",
|
2026-03-30 12:34:38 +02:00
|
|
|
),
|
2026-03-30 12:13:04 +02:00
|
|
|
stat_card(
|
2026-03-30 13:11:37 +02:00
|
|
|
label="Upcoming today",
|
|
|
|
|
value="11",
|
|
|
|
|
detail="Next scheduled job fires in 13 minutes.",
|
2026-03-30 12:13:04 +02:00
|
|
|
),
|
|
|
|
|
stat_card(
|
2026-03-30 13:11:37 +02:00
|
|
|
label="Failures in 24h",
|
|
|
|
|
value="2",
|
|
|
|
|
detail="One network timeout and one source parsing error.",
|
|
|
|
|
),
|
|
|
|
|
stat_card(
|
|
|
|
|
label="Output footprint",
|
|
|
|
|
value="18.4 GB",
|
|
|
|
|
detail="Mirrored feeds, media, logs, and execution stats.",
|
2026-03-30 12:13:04 +02:00
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
2026-03-30 13:11:37 +02:00
|
|
|
def running_executions_table() -> Renderable:
|
|
|
|
|
rows = tuple(_running_execution_row(execution) for execution in RUNNING_EXECUTIONS)
|
|
|
|
|
headers = ("Source", "Execution", "Started", "Status", "Stats", "Actions")
|
2026-03-30 12:13:04 +02:00
|
|
|
|
2026-03-30 13:11:37 +02:00
|
|
|
def render_row(row: tuple[Node, ...]) -> Renderable:
|
|
|
|
|
first_cell, *other_cells = row
|
|
|
|
|
return h.tr(class_="align-top")[
|
|
|
|
|
h.td(class_="py-3 pr-6 pl-4 text-sm font-medium text-slate-950 sm:pl-4")[
|
|
|
|
|
first_cell
|
2026-03-30 12:13:04 +02:00
|
|
|
],
|
2026-03-30 13:11:37 +02:00
|
|
|
(
|
|
|
|
|
h.td(
|
|
|
|
|
class_="px-3 py-3 align-top text-sm whitespace-nowrap text-slate-600"
|
|
|
|
|
)[cell]
|
|
|
|
|
for cell in other_cells
|
|
|
|
|
),
|
|
|
|
|
]
|
2026-03-30 12:13:04 +02:00
|
|
|
|
2026-03-30 13:11:37 +02:00
|
|
|
return h.section[
|
|
|
|
|
h.div(class_="mb-3 flex items-end justify-between gap-4")[
|
2026-03-30 12:13:04 +02:00
|
|
|
h.div[
|
|
|
|
|
h.p(
|
|
|
|
|
class_="text-xs font-semibold uppercase tracking-[0.22em] text-amber-600"
|
2026-03-30 13:11:37 +02:00
|
|
|
)["Live work"],
|
|
|
|
|
h.h2(class_="mt-1 text-xl font-semibold text-slate-950")[
|
|
|
|
|
"Running executions"
|
2026-03-30 12:13:04 +02:00
|
|
|
],
|
2026-03-30 13:11:37 +02:00
|
|
|
h.p(class_="mt-1 text-sm text-slate-600")[
|
|
|
|
|
"Dashboard keeps only the in-flight executions visible here. The full run history lives on the Runs page."
|
2026-03-30 12:13:04 +02:00
|
|
|
],
|
|
|
|
|
],
|
2026-03-30 13:11:37 +02:00
|
|
|
muted_action_link(href="/runs", label="Open runs"),
|
2026-03-30 12:13:04 +02:00
|
|
|
],
|
2026-03-30 13:11:37 +02:00
|
|
|
h.div(
|
|
|
|
|
class_="overflow-hidden rounded-2xl bg-white shadow-sm ring-1 ring-slate-200"
|
2026-03-30 12:13:04 +02:00
|
|
|
)[
|
2026-03-30 13:11:37 +02:00
|
|
|
h.div(class_="overflow-x-auto")[
|
|
|
|
|
h.table(
|
|
|
|
|
class_="w-full min-w-[70rem] divide-y divide-slate-200 table-auto"
|
|
|
|
|
)[
|
|
|
|
|
h.thead(class_="bg-stone-50")[
|
|
|
|
|
h.tr[
|
|
|
|
|
(
|
|
|
|
|
h.th(
|
|
|
|
|
scope="col",
|
|
|
|
|
class_="px-3 py-2.5 text-left text-[11px] font-semibold uppercase tracking-[0.18em] whitespace-nowrap text-slate-500 first:pl-4",
|
|
|
|
|
)[header]
|
|
|
|
|
for header in headers
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
],
|
|
|
|
|
h.tbody(class_="divide-y divide-slate-200 bg-white")[
|
|
|
|
|
(render_row(row) for row in rows)
|
|
|
|
|
],
|
2026-03-30 12:13:04 +02:00
|
|
|
]
|
2026-03-30 13:11:37 +02:00
|
|
|
]
|
2026-03-30 12:13:04 +02:00
|
|
|
],
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
2026-03-30 13:11:37 +02:00
|
|
|
def dashboard_page() -> Renderable:
|
2026-03-30 12:27:45 +02:00
|
|
|
return h.main(
|
|
|
|
|
id="morph",
|
|
|
|
|
class_="min-h-screen lg:grid lg:grid-cols-[18rem_minmax(0,1fr)]",
|
|
|
|
|
)[
|
2026-03-30 13:11:37 +02:00
|
|
|
admin_sidebar(current_path="/"),
|
|
|
|
|
h.div(class_="px-4 py-4 sm:px-5 lg:px-6 lg:py-5")[
|
|
|
|
|
h.div(class_="mx-auto max-w-7xl space-y-5")[
|
|
|
|
|
dashboard_header(),
|
|
|
|
|
operational_snapshot(),
|
|
|
|
|
running_executions_table(),
|
2026-03-30 12:27:45 +02:00
|
|
|
]
|
2026-03-30 12:13:04 +02:00
|
|
|
],
|
2026-03-30 12:27:45 +02:00
|
|
|
]
|