Add settings and live sidebar counts
This commit is contained in:
parent
2a99edeec3
commit
a809bde16c
16 changed files with 696 additions and 51 deletions
92
repub/web.py
92
repub/web.py
|
|
@ -28,8 +28,10 @@ from repub.model import (
|
|||
delete_job_source,
|
||||
delete_source,
|
||||
initialize_database,
|
||||
load_settings_form,
|
||||
load_source_form,
|
||||
load_sources,
|
||||
save_setting,
|
||||
source_slug_exists,
|
||||
update_source,
|
||||
)
|
||||
|
|
@ -39,6 +41,7 @@ from repub.pages import (
|
|||
edit_source_page,
|
||||
execution_logs_page,
|
||||
runs_page,
|
||||
settings_page,
|
||||
shim_page,
|
||||
sources_page,
|
||||
)
|
||||
|
|
@ -59,6 +62,8 @@ class SourceFormData(TypedDict):
|
|||
notes: str
|
||||
spider_arguments: str
|
||||
enabled: bool
|
||||
convert_images: bool
|
||||
convert_video: bool
|
||||
cron_minute: str
|
||||
cron_hour: str
|
||||
cron_day_of_month: str
|
||||
|
|
@ -77,6 +82,10 @@ class SourceFormData(TypedDict):
|
|||
include_content: bool
|
||||
|
||||
|
||||
class SettingsFormData(TypedDict):
|
||||
max_concurrent_jobs: int
|
||||
|
||||
|
||||
DEFAULT_PANGEA_CONTENT_FORMAT = "MOBILE_3"
|
||||
DEFAULT_PANGEA_CONTENT_TYPE = "articles"
|
||||
DEFAULT_PANGEA_MAX_ARTICLES = "10"
|
||||
|
|
@ -123,6 +132,7 @@ def create_app(*, dev_mode: bool = False) -> Quart:
|
|||
@app.get("/sources/create")
|
||||
@app.get("/sources/<string:slug>/edit")
|
||||
@app.get("/runs")
|
||||
@app.get("/settings")
|
||||
@app.get("/job/<int:job_id>/execution/<int:execution_id>/logs")
|
||||
async def page_shim(
|
||||
slug: str | None = None,
|
||||
|
|
@ -158,7 +168,11 @@ def create_app(*, dev_mode: bool = False) -> Quart:
|
|||
|
||||
@app.post("/sources/<string:slug>/edit")
|
||||
async def edit_source_patch(slug: str) -> DatastarResponse:
|
||||
return _page_patch_response(app, lambda: render_edit_source(slug))
|
||||
return _page_patch_response(app, lambda: render_edit_source(slug, app))
|
||||
|
||||
@app.post("/settings")
|
||||
async def settings_patch() -> DatastarResponse:
|
||||
return _page_patch_response(app, lambda: render_settings(app))
|
||||
|
||||
@app.post("/actions/sources/create")
|
||||
async def create_source_action() -> DatastarResponse:
|
||||
|
|
@ -217,6 +231,20 @@ def create_app(*, dev_mode: bool = False) -> Quart:
|
|||
trigger_refresh(app)
|
||||
return Response(status=204)
|
||||
|
||||
@app.post("/actions/settings")
|
||||
async def update_settings_action() -> DatastarResponse:
|
||||
signals = cast(dict[str, object], await read_signals())
|
||||
settings, error = validate_settings_form(signals)
|
||||
if error is not None:
|
||||
return DatastarResponse(
|
||||
SSE.patch_signals({"_formError": error, "_formSuccess": ""})
|
||||
)
|
||||
|
||||
assert settings is not None
|
||||
save_setting("max_concurrent_jobs", settings["max_concurrent_jobs"])
|
||||
trigger_refresh(app)
|
||||
return DatastarResponse(SSE.redirect("/settings"))
|
||||
|
||||
@app.post("/runs")
|
||||
async def runs_patch() -> DatastarResponse:
|
||||
return _page_patch_response(app, lambda: render_runs(app))
|
||||
|
|
@ -300,16 +328,30 @@ async def render_dashboard(app: Quart | None = None) -> Renderable:
|
|||
|
||||
|
||||
async def render_sources(app: Quart | None = None) -> Renderable:
|
||||
sources = None if app is None else load_sources()
|
||||
return sources_page(sources=sources)
|
||||
if app is None:
|
||||
return sources_page()
|
||||
|
||||
sources = load_sources()
|
||||
return sources_page(
|
||||
sources=sources,
|
||||
running_count=len(
|
||||
load_runs_view(log_dir=app.config["REPUB_LOG_DIR"])["running"]
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def render_create_source(app: Quart | None = None) -> Renderable:
|
||||
del app
|
||||
return create_source_page()
|
||||
if app is None:
|
||||
return create_source_page()
|
||||
|
||||
sidebar_counts = _load_sidebar_counts(app)
|
||||
return create_source_page(
|
||||
source_count=sidebar_counts["source_count"],
|
||||
running_count=sidebar_counts["running_count"],
|
||||
)
|
||||
|
||||
|
||||
async def render_edit_source(slug: str) -> Renderable:
|
||||
async def render_edit_source(slug: str, app: Quart | None = None) -> Renderable:
|
||||
source = load_source_form(slug)
|
||||
if source is None:
|
||||
return sources_page(sources=())
|
||||
|
|
@ -317,6 +359,7 @@ async def render_edit_source(slug: str) -> Renderable:
|
|||
slug=slug,
|
||||
source=source,
|
||||
action_path=f"/actions/sources/{slug}/edit",
|
||||
**({} if app is None else _load_sidebar_counts(app)),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -329,6 +372,18 @@ async def render_runs(app: Quart | None = None) -> Renderable:
|
|||
running_executions=cast(tuple[dict[str, object], ...], view["running"]),
|
||||
upcoming_jobs=cast(tuple[dict[str, object], ...], view["upcoming"]),
|
||||
completed_executions=cast(tuple[dict[str, object], ...], view["completed"]),
|
||||
source_count=len(load_sources()),
|
||||
)
|
||||
|
||||
|
||||
async def render_settings(app: Quart | None = None) -> Renderable:
|
||||
if app is None:
|
||||
return settings_page(settings=load_settings_form())
|
||||
sidebar_counts = _load_sidebar_counts(app)
|
||||
return settings_page(
|
||||
settings=load_settings_form(),
|
||||
source_count=sidebar_counts["source_count"],
|
||||
running_count=sidebar_counts["running_count"],
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -377,6 +432,15 @@ async def _unsubscribe_on_close(
|
|||
get_refresh_broker(app).unsubscribe(cast(asyncio.Queue[object], queue))
|
||||
|
||||
|
||||
def _load_sidebar_counts(app: Quart) -> dict[str, int]:
|
||||
return {
|
||||
"source_count": len(load_sources()),
|
||||
"running_count": len(
|
||||
load_runs_view(log_dir=app.config["REPUB_LOG_DIR"])["running"]
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def validate_source_form(
|
||||
signals: dict[str, object] | None,
|
||||
*,
|
||||
|
|
@ -469,6 +533,8 @@ def validate_source_form(
|
|||
"max_articles": _parse_int(max_articles),
|
||||
"oldest_article": _parse_int(oldest_article),
|
||||
"enabled": enabled,
|
||||
"convert_images": _read_bool(signals, "convertImages", default=True),
|
||||
"convert_video": _read_bool(signals, "convertVideo", default=True),
|
||||
"only_newest": _read_bool(signals, "onlyNewest", default=True),
|
||||
"include_authors": _read_bool(signals, "includeAuthors", default=True),
|
||||
"exclude_media": _read_bool(signals, "excludeMedia", default=False),
|
||||
|
|
@ -482,6 +548,20 @@ def validate_source_form(
|
|||
return source, None
|
||||
|
||||
|
||||
def validate_settings_form(
|
||||
signals: dict[str, object] | None,
|
||||
) -> tuple[SettingsFormData | None, str | None]:
|
||||
if signals is None:
|
||||
return None, "Missing form data."
|
||||
|
||||
max_concurrent_jobs = _parse_int(_read_string(signals, "maxConcurrentJobs"))
|
||||
if max_concurrent_jobs is None:
|
||||
return None, "Max concurrent jobs must be an integer."
|
||||
if max_concurrent_jobs < 1:
|
||||
return None, "Max concurrent jobs must be at least 1."
|
||||
return {"max_concurrent_jobs": max_concurrent_jobs}, None
|
||||
|
||||
|
||||
def _read_string(signals: dict[str, object], key: str, *, strip: bool = True) -> str:
|
||||
value = str(signals.get(key, ""))
|
||||
return value.strip() if strip else value
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue