Files
hockey_new/ui_builder/integration.py
2026-08-19 15:08:39 +03:00

145 lines
4.9 KiB
Python

from __future__ import annotations
import hashlib
import shutil
from pathlib import Path
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from .auth import EditorAuthManager, create_editor_auth_router
from .manager import UIBuilderManager
from .router import DataProvider, create_ui_builder_router
def _seed_config(source: Path, destination: Path) -> None:
if destination.exists():
return
if source.exists():
destination.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(source, destination)
def install_ui_builder(
app: FastAPI,
*,
settings_dir: Path,
data_providers: dict[str, DataProvider],
source_labels: dict[str, str] | None = None,
api_prefix: str = "/api/ui-builder",
assets_url: str = "/ui-builder-assets",
editor_url: str = "/editor",
runtime_url: str = "/",
settings_filename: str = "ui_builder.json",
draft_filename: str = "ui_builder_draft.json",
published_filename: str = "ui_builder_published.json",
custom_script_urls: list[str] | None = None,
custom_style_urls: list[str] | None = None,
) -> UIBuilderManager:
"""Install published runtime and PIN-protected editor."""
settings_dir = Path(settings_dir)
legacy = settings_dir / Path(settings_filename).name
draft_file = settings_dir / Path(draft_filename).name
published_file = settings_dir / Path(published_filename).name
_seed_config(legacy, draft_file)
_seed_config(legacy, published_file)
draft_manager = UIBuilderManager(settings_dir, filename=draft_file.name)
published_manager = UIBuilderManager(settings_dir, filename=published_file.name)
auth = EditorAuthManager(settings_dir)
assets_dir = Path(__file__).resolve().parent / "static"
app.mount(
assets_url,
StaticFiles(directory=assets_dir),
name="ui-builder-assets",
)
editor_api = f"{api_prefix.rstrip('/')}/editor"
runtime_api = f"{api_prefix.rstrip('/')}/runtime"
auth_api = f"{api_prefix.rstrip('/')}/auth"
app.include_router(
create_editor_auth_router(
auth,
prefix=auth_api,
editor_url=editor_url,
)
)
app.include_router(
create_ui_builder_router(
draft_manager,
data_providers,
source_labels=source_labels,
prefix=editor_api,
access_dependency=auth.require_editor,
publish_manager=published_manager,
)
)
app.include_router(
create_ui_builder_router(
published_manager,
data_providers,
source_labels=source_labels,
prefix=runtime_api,
read_only=True,
)
)
template = (assets_dir / "index.html").read_text(encoding="utf-8")
asset_digest = hashlib.sha256()
for asset_name in ("app.js", "styles.css"):
asset_path = assets_dir / asset_name
if asset_path.exists():
asset_digest.update(asset_path.read_bytes())
asset_version = asset_digest.hexdigest()[:12]
custom_script_urls = custom_script_urls or []
custom_style_urls = custom_style_urls or []
def render_page(mode: str) -> HTMLResponse:
page_api = editor_api if mode == "editor" else runtime_api
html = (
template.replace("__UI_BUILDER_ASSETS__", assets_url.rstrip("/"))
.replace("__UI_BUILDER_ASSET_VERSION__", asset_version)
.replace("__UI_BUILDER_API__", page_api)
.replace("__UI_BUILDER_AUTH_API__", auth_api)
.replace("__UI_BUILDER_EDITOR__", editor_url)
.replace("__UI_BUILDER_RUNTIME__", runtime_url)
.replace("__UI_BUILDER_MODE__", mode)
.replace("__UI_BUILDER_EDITOR_HOTKEY__", "Ctrl+Shift+E")
.replace(
"__UI_BUILDER_CUSTOM_STYLES__",
"\n".join(
f'<link rel="stylesheet" href="{url}">'
for url in custom_style_urls
),
)
.replace(
"__UI_BUILDER_CUSTOM_SCRIPTS__",
"\n".join(
f'<script src="{url}"></script>'
for url in custom_script_urls
),
)
)
return HTMLResponse(html)
@app.get(editor_url, include_in_schema=False)
async def ui_builder_editor(request: Request):
if not auth.is_request_authenticated(request):
return RedirectResponse(runtime_url, status_code=302)
return render_page("editor")
@app.get(runtime_url, include_in_schema=False)
async def ui_builder_runtime() -> HTMLResponse:
return render_page("runtime")
app.state.ui_builder_manager = draft_manager
app.state.ui_builder_draft_manager = draft_manager
app.state.ui_builder_published_manager = published_manager
app.state.ui_builder_auth = auth
return draft_manager