Add settings and live sidebar counts

This commit is contained in:
Abel Luck 2026-03-30 18:26:02 +02:00
parent 2a99edeec3
commit a809bde16c
16 changed files with 696 additions and 51 deletions

View file

@ -1,5 +1,6 @@
from repub.pages.dashboard import dashboard_page, dashboard_page_with_data
from repub.pages.runs import execution_logs_page, runs_page
from repub.pages.settings import settings_page
from repub.pages.shim import shim_page
from repub.pages.sources import create_source_page, edit_source_page, sources_page
@ -10,6 +11,7 @@ __all__ = [
"edit_source_page",
"execution_logs_page",
"runs_page",
"settings_page",
"shim_page",
"sources_page",
]

View file

@ -251,17 +251,23 @@ def dashboard_page_with_data(
running_executions: tuple[Mapping[str, object], ...] | None = None,
source_feeds: tuple[Mapping[str, object], ...] | None = None,
) -> Renderable:
running_items = running_executions or ()
source_items = source_feeds or ()
return h.main(
id="morph",
class_="min-h-screen lg:grid lg:grid-cols-[18rem_minmax(0,1fr)]",
)[
admin_sidebar(current_path="/"),
admin_sidebar(
current_path="/",
source_count=len(source_items),
running_count=len(running_items),
),
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(snapshot=snapshot),
running_executions_table(running_executions=running_executions),
published_feeds_table(source_feeds=source_feeds),
running_executions_table(running_executions=running_items),
published_feeds_table(source_feeds=source_items),
]
],
]

View file

@ -194,6 +194,7 @@ def runs_page(
running_executions: tuple[Mapping[str, object], ...] | None = None,
upcoming_jobs: tuple[Mapping[str, object], ...] | None = None,
completed_executions: tuple[Mapping[str, object], ...] | None = None,
source_count: int = 0,
) -> Renderable:
running_items = running_executions or ()
upcoming_items = upcoming_jobs or ()
@ -207,6 +208,8 @@ def runs_page(
eyebrow="Execution control",
title="Runs",
actions=muted_action_link(href="/sources", label="Back to sources"),
source_count=source_count,
running_count=len(running_items),
content=(
table_section(
eyebrow="Live work",

82
repub/pages/settings.py Normal file
View file

@ -0,0 +1,82 @@
from __future__ import annotations
from collections.abc import Mapping
import htpy as h
from htpy import Renderable
from repub.components import input_field, muted_action_link, page_shell, section_card
def _value(settings: Mapping[str, object] | None, key: str, default: str = "") -> str:
if settings is None:
return default
return str(settings.get(key, default))
def settings_page(
*,
settings: Mapping[str, object] | None = None,
action_path: str = "/actions/settings",
source_count: int = 0,
running_count: int = 0,
) -> Renderable:
return page_shell(
current_path="/settings",
eyebrow="Configuration",
title="Settings",
description="Global runtime controls for the republisher.",
source_count=source_count,
running_count=running_count,
content=section_card(
content=(
h.form(
{
"data-signals": "{_formError: '', _formSuccess: ''}",
"data-signals__ifmissing": (
"{"
f"maxConcurrentJobs: '{_value(settings, 'max_concurrent_jobs', '1')}'"
"}"
),
"data-on:submit": f"@post('{action_path}')",
},
class_="space-y-6 rounded-[1.5rem] bg-white p-6 shadow-sm ring-1 ring-slate-200",
)[
h.div[
h.p(
class_="text-xs font-semibold uppercase tracking-[0.22em] text-amber-600"
)["Scheduler"],
h.h2(class_="mt-2 text-xl font-semibold text-slate-950")[
"Runtime settings"
],
h.p(class_="mt-2 text-sm text-slate-600")[
"Limit how many jobs the scheduler and manual runs can execute at the same time."
],
],
h.div(
{
"data-show": "$_formError !== ''",
"data-text": "$_formError",
},
class_="rounded-2xl bg-rose-50 px-4 py-3 text-sm font-medium text-rose-800",
),
input_field(
label="Max concurrent jobs",
field_id="max-concurrent-jobs",
value=_value(settings, "max_concurrent_jobs", "1"),
help_text="Must be an integer greater than or equal to 1.",
signal_name="maxConcurrentJobs",
),
h.div(
class_="flex flex-wrap justify-end gap-3 border-t border-slate-200 pt-6"
)[
muted_action_link(href="/", label="Back to dashboard"),
h.button(
type="submit",
class_="rounded-full bg-slate-950 px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-slate-800",
)["Save settings"],
],
],
)
),
)

View file

@ -37,7 +37,11 @@ def shim_page(
id="morph",
class_="min-h-screen lg:grid lg:grid-cols-[18rem_minmax(0,1fr)]",
)[
admin_sidebar(current_path=current_path),
admin_sidebar(
current_path=current_path,
source_count=0,
running_count=0,
),
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")[
h.section[

View file

@ -128,13 +128,18 @@ def sources_table(
def sources_page(
*, sources: tuple[Mapping[str, object], ...] | None = None
*,
sources: tuple[Mapping[str, object], ...] | None = None,
running_count: int = 0,
) -> Renderable:
source_items = sources or ()
return page_shell(
current_path="/sources",
eyebrow="Source management",
title="Sources",
content=sources_table(sources=sources),
source_count=len(source_items),
running_count=running_count,
content=sources_table(sources=source_items),
)
@ -398,6 +403,18 @@ def source_form(
signal_name="jobEnabled",
checked=_checked(source, "enabled", True),
),
toggle_field(
label="Convert images",
description="Normalize mirrored images through the image conversion pipeline for this source.",
signal_name="convertImages",
checked=_checked(source, "convert_images", True),
),
toggle_field(
label="Convert video",
description="Run mirrored videos through the video conversion pipeline for this source.",
signal_name="convertVideo",
checked=_checked(source, "convert_video", True),
),
],
],
],
@ -415,7 +432,12 @@ def source_form(
)
def create_source_page(*, action_path: str = "/actions/sources/create") -> Renderable:
def create_source_page(
*,
action_path: str = "/actions/sources/create",
source_count: int = 0,
running_count: int = 0,
) -> Renderable:
actions = (
muted_action_link(href="/sources", label="Back to sources"),
header_action_link(href="/runs", label="View runs"),
@ -425,6 +447,8 @@ def create_source_page(*, action_path: str = "/actions/sources/create") -> Rende
eyebrow="Source creation",
title="Create source",
actions=actions,
source_count=source_count,
running_count=running_count,
content=source_form(mode="create", action_path=action_path),
)
@ -434,6 +458,8 @@ def edit_source_page(
slug: str,
source: Mapping[str, object],
action_path: str,
source_count: int = 0,
running_count: int = 0,
) -> Renderable:
actions = (
muted_action_link(href="/sources", label="Back to sources"),
@ -444,5 +470,7 @@ def edit_source_page(
eyebrow="Source editing",
title="Edit source",
actions=actions,
source_count=source_count,
running_count=running_count,
content=source_form(mode="edit", action_path=action_path, source=source),
)