bifroest/app/hugin/routes.py
2026-10-27 08:34:17 +01:00

159 lines
8.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Hugin: Host und Übersicht (Plattform-Admin, /admin/hugin); Key, Pakete wählen, abgleichen und
übernehmen (Workspace-Admin, Reiter „Hugin“ der Einstellungen)."""
from typing import Annotated
from fastapi import APIRouter, Form, Request
from fastapi.concurrency import run_in_threadpool
from fastapi.responses import HTMLResponse, Response
from sqlalchemy.orm import Session
from app import web
from app.core.config import Settings
from app.core.errors import Invalid
from app.hugin import service as hugin
from app.hugin.client import HuginError
from app.identity.admin import require as require_platform_admin
from app.workspaces import service as workspaces
from app.workspaces.models import Workspace
Text = Annotated[str, Form()]
def tab_context(session: Session, settings: Settings, workspace: Workspace) -> dict:
"""Für den Reiter „Hugin“ der Workspace-Einstellungen; ohne Aufruf an Hugin."""
access = hugin.access(session, workspace.id)
return {"host": hugin.host(session), "access": access,
"findings": hugin.describe(access.findings or {}) if access else [],
"subscriptions": hugin.subscriptions(session, workspace.id), "locks": hugin.locks(session, workspace.id),
"kind_names": hugin.KIND_NAMES}
def router() -> APIRouter:
r = APIRouter()
def back(workspace_id: int) -> Response:
return web.redirect(f"/workspaces/{workspace_id}/settings?tab=hugin&saved=1")
def hugin_page(request: Request, principal, workspace_id: int, template: str, work, status: int = 200):
"""Seiten mit Aufruf an Hugin (Katalog, Abgleich); Fehler von Hugin erscheinen auf der Seite."""
settings, vault = web.settings(request), web.vault(request)
with web.db(request).session() as session:
workspace = workspaces.require_admin(session, principal, workspace_id).workspace
context = {"workspace": workspace, "current_workspace": workspace, "error": "",
"kind_names": hugin.KIND_NAMES, **tab_context(session, settings, workspace)}
try:
context.update(work(session, settings, vault))
except HuginError as e:
context["error"], status = str(e), 502
return web.render(request, template, context, status)
def admin_page(request: Request, principal, error: str = "", notice: str = "", status: int = 200,
url: str = "") -> HTMLResponse:
require_platform_admin(principal)
with web.db(request).session() as session:
instance = hugin.host(session)
context = {"host": instance, "workspaces": hugin.overview(session), "error": error, "notice": notice,
"url": url or (instance.url if instance else web.settings(request).hugin_url)}
return web.render(request, "admin_hugin.html", context, status)
@r.get("/admin/hugin")
def admin_overview(request: Request, principal: web.User) -> HTMLResponse:
return admin_page(request, principal)
def check_and_show(request: Request, principal, notice: str) -> HTMLResponse:
with web.db(request).session() as session:
row = hugin.check_host(session, principal, web.settings(request))
ok, message = row.status == "ok", row.status_message
if ok:
return admin_page(request, principal, notice=f"{notice} Hugin ist erreichbar.".strip())
return admin_page(request, principal, error=f"{notice} {message}".strip())
@r.post("/admin/hugin")
def save_host(request: Request, principal: web.Checked, url: Text) -> HTMLResponse:
try:
with web.db(request).session() as session:
hugin.save_host(session, principal, web.settings(request), url)
except Invalid as e:
return admin_page(request, principal, error=str(e), status=400, url=url)
return check_and_show(request, principal, "Gespeichert.")
@r.post("/admin/hugin/check")
async def check_host(request: Request, principal: web.Checked) -> HTMLResponse:
return await run_in_threadpool(check_and_show, request, principal, "")
@r.post("/workspaces/{workspace_id}/hugin/access")
async def save_access(request: Request, principal: web.Checked, workspace_id: int, key: Text) -> Response:
def work():
with web.db(request).session() as session:
hugin.save_access(session, principal, web.settings(request), web.vault(request), workspace_id, key)
await run_in_threadpool(work)
return back(workspace_id)
@r.post("/workspaces/{workspace_id}/hugin/access/test")
async def test_access(request: Request, principal: web.Checked, workspace_id: int) -> Response:
def work():
with web.db(request).session() as session:
hugin.test_access(session, principal, web.settings(request), web.vault(request), workspace_id)
await run_in_threadpool(work)
return back(workspace_id)
@r.post("/workspaces/{workspace_id}/hugin/access/remove")
def remove_access(request: Request, principal: web.Checked, workspace_id: int) -> Response:
with web.db(request).session() as session:
hugin.remove_access(session, principal, workspace_id)
return back(workspace_id)
@r.get("/workspaces/{workspace_id}/hugin/catalog")
async def catalog(request: Request, principal: web.User, workspace_id: int, q: str = "") -> HTMLResponse:
def work(session, settings, vault):
packages = hugin.catalog(session, settings, vault, principal, workspace_id)
if q:
packages = [p for p in packages if q.lower() in (p["id"] + " " + p.get("description", "")).lower()]
return {"packages": packages, "q": q,
"chosen": {s.package_id for s in hugin.subscriptions(session, workspace_id)}}
return await run_in_threadpool(hugin_page, request, principal, workspace_id, "hugin_catalog.html", work)
@r.post("/workspaces/{workspace_id}/hugin/check")
async def check(request: Request, principal: web.Checked, workspace_id: int) -> HTMLResponse:
def work(session, settings, vault):
return {"plan": hugin.plan(session, settings, vault, principal, workspace_id)}
return await run_in_threadpool(hugin_page, request, principal, workspace_id, "hugin_plan.html", work)
@r.post("/workspaces/{workspace_id}/hugin/apply")
async def apply(request: Request, principal: web.Checked, workspace_id: int) -> Response:
def work(session, settings, vault):
return {"plan": hugin.apply(session, settings, vault, principal, workspace_id), "applied": True}
page = await run_in_threadpool(hugin_page, request, principal, workspace_id, "hugin_plan.html", work)
return page if page.status_code != 200 else back(workspace_id)
@r.get("/workspaces/{workspace_id}/hugin/import")
async def import_page(request: Request, principal: web.User, workspace_id: int) -> HTMLResponse:
proposals = await run_in_threadpool(hugin.repo_proposals, web.db(request), web.settings(request),
web.vault(request), principal, workspace_id)
def work(session, settings, vault):
chosen = {s.package_id: s.spec for s in hugin.subscriptions(session, workspace_id)}
return {"proposals": proposals, "chosen": chosen}
return await run_in_threadpool(hugin_page, request, principal, workspace_id, "hugin_import.html", work)
@r.post("/workspaces/{workspace_id}/hugin/import")
def import_packages(request: Request, principal: web.Checked, workspace_id: int,
package: Annotated[list[str], Form()] = []) -> Response: # noqa: B006 – FastAPI-Formular
with web.db(request).session() as session:
hugin.add_many(session, principal, workspace_id, package)
return back(workspace_id)
@r.post("/workspaces/{workspace_id}/hugin/packages")
def add(request: Request, principal: web.Checked, workspace_id: int, package: Text) -> Response:
with web.db(request).session() as session:
hugin.add(session, principal, workspace_id, package)
return back(workspace_id)
@r.post("/workspaces/{workspace_id}/hugin/packages/{package_id}/remove")
def remove(request: Request, principal: web.Checked, workspace_id: int, package_id: str) -> Response:
with web.db(request).session() as session:
hugin.remove(session, principal, workspace_id, package_id)
return back(workspace_id)
return r