from __future__ import annotations from collections.abc import Awaitable, Callable from typing import Any, cast from datastar_py import ServerSentEventGenerator as SSE from datastar_py.quart import DatastarResponse, read_signals from peewee import IntegrityError from quart import Quart, Response from repub.jobs import clear_completed_executions from repub.model import ( create_source, delete_job_source, delete_source, load_job_enabled, save_setting, source_slug_exists, update_source, ) from repub.web.app import ( RUNS_TAB_STATE_KEY, _read_optional_signals, _read_tab_id, get_job_runtime, get_tab_state_store, run_job_now_response, trigger_refresh, validate_settings_form, validate_source_form, ) RouteGuard = Callable[[Callable[..., Awaitable[Any]]], Callable[..., Awaitable[Any]]] def register_admin_actions(app: Quart, *, admin_required: RouteGuard) -> None: @app.post("/admin/actions/sources/create") @admin_required async def admin_create_source_action() -> DatastarResponse: signals = cast(dict[str, object], await read_signals()) source, error = validate_source_form( signals, slug_exists=source_slug_exists, ) if error is not None: return DatastarResponse( SSE.patch_signals({"_formError": error, "_formSuccess": ""}) ) assert source is not None try: create_source(**source) except IntegrityError: return DatastarResponse( SSE.patch_signals( {"_formError": "Slug must be unique.", "_formSuccess": ""} ) ) get_job_runtime(app).sync_jobs() trigger_refresh(app) return DatastarResponse(SSE.redirect("/admin/sources")) @app.post("/admin/actions/sources//edit") @admin_required async def admin_edit_source_action(slug: str) -> DatastarResponse: signals = cast(dict[str, object], await read_signals()) source, error = validate_source_form( signals, slug_exists=lambda candidate: candidate != slug and source_slug_exists(candidate), immutable_slug=slug, ) if error is not None: return DatastarResponse( SSE.patch_signals({"_formError": error, "_formSuccess": ""}) ) assert source is not None if update_source(slug, **source) is None: return DatastarResponse( SSE.patch_signals( {"_formError": "Source does not exist.", "_formSuccess": ""} ) ) get_job_runtime(app).sync_jobs() trigger_refresh(app) return DatastarResponse(SSE.redirect("/admin/sources")) @app.post("/admin/actions/sources//delete") @admin_required async def admin_delete_source_action(slug: str) -> Response: delete_source(slug) get_job_runtime(app).sync_jobs() trigger_refresh(app) return Response(status=204) @app.post("/admin/actions/settings") @admin_required async def admin_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"]) save_setting("feed_url", settings["feed_url"]) trigger_refresh(app) return DatastarResponse(SSE.redirect("/admin/settings")) @app.post("/admin/actions/runs/completed-page/") @admin_required async def admin_set_completed_runs_page_action(page: int) -> Response: signals = await _read_optional_signals() tab_id = _read_tab_id(signals) if tab_id is None: return Response(status=400) get_tab_state_store(app).update_page_state( tab_id, RUNS_TAB_STATE_KEY, lambda state: {**state, "completed_page": max(1, page)}, ) trigger_refresh(app, tab_id=tab_id) return Response(status=204) @app.post("/admin/actions/jobs//run-now") @admin_required async def admin_run_job_now_action(job_id: int) -> Response: return run_job_now_response(app, job_id) @app.post("/admin/actions/jobs//toggle-enabled") @admin_required async def admin_toggle_job_enabled_action(job_id: int) -> Response: enabled = load_job_enabled(job_id) if enabled is not None: get_job_runtime(app).set_job_enabled(job_id, enabled=not enabled) trigger_refresh(app) return Response(status=204) @app.post("/admin/actions/jobs//delete") @admin_required async def admin_delete_job_action(job_id: int) -> Response: delete_job_source(job_id) get_job_runtime(app).sync_jobs() trigger_refresh(app) return Response(status=204) @app.post("/admin/actions/executions//cancel") @admin_required async def admin_cancel_execution_action(execution_id: int) -> Response: get_job_runtime(app).request_execution_cancel(execution_id) trigger_refresh(app) return Response(status=204) @app.post("/admin/actions/queued-executions//cancel") @admin_required async def admin_cancel_queued_execution_action(execution_id: int) -> Response: get_job_runtime(app).cancel_queued_execution(execution_id) trigger_refresh(app) return Response(status=204) @app.post("/admin/actions/queued-executions//move-up") @admin_required async def admin_move_queued_execution_up_action(execution_id: int) -> Response: get_job_runtime(app).move_queued_execution(execution_id, direction="up") return Response(status=204) @app.post("/admin/actions/queued-executions//move-down") @admin_required async def admin_move_queued_execution_down_action(execution_id: int) -> Response: get_job_runtime(app).move_queued_execution(execution_id, direction="down") return Response(status=204) @app.post("/admin/actions/completed-executions/clear") @admin_required async def admin_clear_completed_executions_action() -> Response: clear_completed_executions(log_dir=app.config["REPUB_LOG_DIR"]) trigger_refresh(app) return Response(status=204)