separeate pages
This commit is contained in:
parent
3fc999a69b
commit
9e826fcee8
9 changed files with 1376 additions and 924 deletions
145
repub/web.py
145
repub/web.py
|
|
@ -2,23 +2,28 @@ from __future__ import annotations
|
|||
|
||||
import asyncio
|
||||
import hashlib
|
||||
from collections.abc import AsyncGenerator
|
||||
from contextlib import suppress
|
||||
from collections.abc import AsyncGenerator, Awaitable, Callable
|
||||
from typing import cast
|
||||
|
||||
import htpy as h
|
||||
from datastar_py import ServerSentEventGenerator as SSE
|
||||
from datastar_py.quart import DatastarResponse, read_signals
|
||||
from datastar_py.quart import DatastarResponse
|
||||
from datastar_py.sse import DatastarEvent
|
||||
from htpy import Renderable
|
||||
from quart import Quart, Response, request, url_for
|
||||
|
||||
from repub.datastar import RefreshBroker, render_stream
|
||||
from repub.pages import admin_component, shim_page
|
||||
from repub.pages import (
|
||||
create_source_page,
|
||||
dashboard_page,
|
||||
execution_logs_page,
|
||||
runs_page,
|
||||
shim_page,
|
||||
sources_page,
|
||||
)
|
||||
|
||||
REFRESH_BROKER_KEY = "repub.refresh_broker"
|
||||
ACTIVE_JOBS_KEY = "repub.demo_active_jobs"
|
||||
REFRESH_TASK_KEY = "repub.demo_refresh_task"
|
||||
|
||||
RenderFunction = Callable[[], Awaitable[Renderable]]
|
||||
|
||||
|
||||
def _render_shim_page(*, stylesheet_href: str, datastar_src: str) -> tuple[str, str]:
|
||||
|
|
@ -31,30 +36,19 @@ def _render_shim_page(*, stylesheet_href: str, datastar_src: str) -> tuple[str,
|
|||
return body, etag
|
||||
|
||||
|
||||
def create_app(*, enable_demo_refresh: bool = True) -> Quart:
|
||||
def create_app() -> Quart:
|
||||
app = Quart(__name__)
|
||||
app.extensions[REFRESH_BROKER_KEY] = RefreshBroker()
|
||||
app.extensions[ACTIVE_JOBS_KEY] = 12
|
||||
|
||||
if enable_demo_refresh:
|
||||
|
||||
@app.before_serving
|
||||
async def start_demo_refresh() -> None:
|
||||
app.extensions[REFRESH_TASK_KEY] = asyncio.create_task(
|
||||
_demo_refresh_loop(app)
|
||||
)
|
||||
|
||||
@app.after_serving
|
||||
async def stop_demo_refresh() -> None:
|
||||
task = cast(asyncio.Task[None] | None, app.extensions.get(REFRESH_TASK_KEY))
|
||||
if task is None:
|
||||
return
|
||||
task.cancel()
|
||||
with suppress(asyncio.CancelledError):
|
||||
await task
|
||||
|
||||
@app.get("/")
|
||||
async def index() -> Response:
|
||||
@app.get("/sources")
|
||||
@app.get("/sources/create")
|
||||
@app.get("/runs")
|
||||
@app.get("/job/<int:job_id>/execution/<int:execution_id>/logs")
|
||||
async def page_shim(
|
||||
job_id: int | None = None, execution_id: int | None = None
|
||||
) -> Response:
|
||||
del job_id, execution_id
|
||||
body, etag = _render_shim_page(
|
||||
stylesheet_href=url_for("static", filename="app.css"),
|
||||
datastar_src=url_for("static", filename="datastar@1.0.0-RC.8.js"),
|
||||
|
|
@ -69,24 +63,27 @@ def create_app(*, enable_demo_refresh: bool = True) -> Quart:
|
|||
return response
|
||||
|
||||
@app.post("/")
|
||||
async def index_patch() -> DatastarResponse:
|
||||
queue = get_refresh_broker(app).subscribe()
|
||||
stream = render_stream(
|
||||
queue,
|
||||
render=lambda: render_dashboard(app),
|
||||
last_event_id=request.headers.get("last-event-id"),
|
||||
)
|
||||
return DatastarResponse(_unsubscribe_on_close(queue, stream, app))
|
||||
async def dashboard_patch() -> DatastarResponse:
|
||||
return _page_patch_response(app, render_dashboard)
|
||||
|
||||
@app.post("/demo/decrement")
|
||||
async def demo_decrement() -> DatastarResponse:
|
||||
amount, error = _validated_decrement_amount(await read_signals())
|
||||
if error is not None:
|
||||
return DatastarResponse(SSE.patch_signals({"decrementError": error}))
|
||||
@app.post("/sources")
|
||||
async def sources_patch() -> DatastarResponse:
|
||||
return _page_patch_response(app, render_sources)
|
||||
|
||||
set_active_jobs(app, max(0, get_active_jobs(app) - amount))
|
||||
trigger_refresh(app)
|
||||
return DatastarResponse(SSE.patch_signals({"decrementError": ""}))
|
||||
@app.post("/sources/create")
|
||||
async def create_source_patch() -> DatastarResponse:
|
||||
return _page_patch_response(app, render_create_source)
|
||||
|
||||
@app.post("/runs")
|
||||
async def runs_patch() -> DatastarResponse:
|
||||
return _page_patch_response(app, render_runs)
|
||||
|
||||
@app.post("/job/<int:job_id>/execution/<int:execution_id>/logs")
|
||||
async def logs_patch(job_id: int, execution_id: int) -> DatastarResponse:
|
||||
async def render() -> Renderable:
|
||||
return await render_execution_logs(job_id=job_id, execution_id=execution_id)
|
||||
|
||||
return _page_patch_response(app, render)
|
||||
|
||||
return app
|
||||
|
||||
|
|
@ -99,8 +96,34 @@ def trigger_refresh(app: Quart, event: object = "refresh-event") -> None:
|
|||
get_refresh_broker(app).publish(event)
|
||||
|
||||
|
||||
async def render_dashboard(app: Quart) -> Renderable:
|
||||
return admin_component(active_jobs=str(get_active_jobs(app)))
|
||||
async def render_dashboard() -> Renderable:
|
||||
return dashboard_page()
|
||||
|
||||
|
||||
async def render_sources() -> Renderable:
|
||||
return sources_page()
|
||||
|
||||
|
||||
async def render_create_source() -> Renderable:
|
||||
return create_source_page()
|
||||
|
||||
|
||||
async def render_runs() -> Renderable:
|
||||
return runs_page()
|
||||
|
||||
|
||||
async def render_execution_logs(*, job_id: int, execution_id: int) -> Renderable:
|
||||
return execution_logs_page(job_id=job_id, execution_id=execution_id)
|
||||
|
||||
|
||||
def _page_patch_response(app: Quart, render: RenderFunction) -> DatastarResponse:
|
||||
queue = get_refresh_broker(app).subscribe()
|
||||
stream = render_stream(
|
||||
queue,
|
||||
render=render,
|
||||
last_event_id=request.headers.get("last-event-id"),
|
||||
)
|
||||
return DatastarResponse(_unsubscribe_on_close(queue, stream, app))
|
||||
|
||||
|
||||
async def _unsubscribe_on_close(
|
||||
|
|
@ -111,35 +134,3 @@ async def _unsubscribe_on_close(
|
|||
yield event
|
||||
finally:
|
||||
get_refresh_broker(app).unsubscribe(cast(asyncio.Queue[object], queue))
|
||||
|
||||
|
||||
def get_active_jobs(app: Quart) -> int:
|
||||
return cast(int, app.extensions[ACTIVE_JOBS_KEY])
|
||||
|
||||
|
||||
def set_active_jobs(app: Quart, value: int) -> None:
|
||||
app.extensions[ACTIVE_JOBS_KEY] = value
|
||||
|
||||
|
||||
async def _demo_refresh_loop(app: Quart) -> None:
|
||||
while True:
|
||||
await asyncio.sleep(1)
|
||||
set_active_jobs(app, get_active_jobs(app) + 1)
|
||||
trigger_refresh(app)
|
||||
|
||||
|
||||
def _validated_decrement_amount(
|
||||
signals: dict[str, object] | None,
|
||||
) -> tuple[int, str | None]:
|
||||
raw_amount = (
|
||||
"" if signals is None else str(signals.get("decrementAmount", "")).strip()
|
||||
)
|
||||
try:
|
||||
amount = int(raw_amount)
|
||||
except ValueError:
|
||||
return 0, "Decrement amount must be an odd integer."
|
||||
|
||||
if amount < 1 or amount % 2 == 0:
|
||||
return 0, "Decrement amount must be an odd integer."
|
||||
|
||||
return amount, None
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue