web_admin.py 2.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. from __future__ import annotations
  2. import asyncio
  3. from pathlib import Path
  4. from aiohttp import web
  5. import wbb
  6. from wbb.admin.api import AdminApi, build_admin_application
  7. _runner: web.AppRunner | None = None
  8. def _resolve_admin_asset(relative: str) -> Path | None:
  9. root = Path(str(getattr(wbb, "ADMIN_WEB_STATIC_DIR", "admin-web/dist"))).resolve()
  10. candidate = (root / relative).resolve()
  11. if relative and candidate.is_file() and root in candidate.parents:
  12. return candidate
  13. index = root / "index.html"
  14. if index.is_file():
  15. return index
  16. return None
  17. async def _serve_spa(request: web.Request) -> web.StreamResponse:
  18. asset = _resolve_admin_asset(request.match_info.get("tail", ""))
  19. if asset is not None:
  20. return web.FileResponse(asset)
  21. return web.Response(
  22. text="Admin web assets are not built. Run `cd admin-web && npm run build`.",
  23. status=503,
  24. content_type="text/plain",
  25. )
  26. async def _redirect_admin(_: web.Request) -> web.StreamResponse:
  27. raise web.HTTPFound("/admin/")
  28. async def start_admin_web() -> None:
  29. global _runner
  30. if _runner is not None or not bool(getattr(wbb, "ADMIN_WEB_ENABLED", True)):
  31. return
  32. application = build_admin_application()
  33. api: AdminApi = application["admin_api"]
  34. await api.initialize()
  35. application.router.add_get("/admin", _redirect_admin)
  36. application.router.add_get("/admin/{tail:.*}", _serve_spa)
  37. _runner = web.AppRunner(application, access_log=None)
  38. await _runner.setup()
  39. host = str(getattr(wbb, "ADMIN_WEB_HOST", "0.0.0.0"))
  40. port = int(getattr(wbb, "ADMIN_WEB_PORT", 8088))
  41. site = web.TCPSite(_runner, host=host, port=port)
  42. await site.start()
  43. wbb.log.info(f"Admin web listening on http://{host}:{port}/admin")
  44. async def stop_admin_web() -> None:
  45. global _runner
  46. if _runner is not None:
  47. await _runner.cleanup()
  48. _runner = None
  49. def schedule_admin_web() -> None:
  50. try:
  51. loop = asyncio.get_running_loop()
  52. except RuntimeError:
  53. return
  54. loop.create_task(start_admin_web(), name="admin-web")