Source code for harp_apps.dashboard.controllers

import asyncio
import os

from asgi_middleware_static_file import ASGIMiddlewareStaticFile
from asgiref.typing import ASGISendCallable
from http_router import NotFoundError, Router
from httpx import AsyncClient

from harp import get_logger
from harp.controllers import RoutingController
from harp.errors import ConfigurationError
from harp.http import AlreadyHandledHttpResponse, HttpRequest, HttpResponse
from harp_apps import dashboard
from harp_apps.proxy.controllers import HttpProxyController
from harp_apps.proxy.settings.remote import Remote
from harp_apps.storage.types import IStorage

from ..settings import DashboardSettings
from ..settings.auth import BasicAuthSettings

logger = get_logger(__name__)


[docs] class DashboardController(RoutingController): name = "ui" storage: IStorage settings: DashboardSettings http_client: AsyncClient _ui_static_middleware = None _ui_devserver_proxy_controller = None #: Static directory to look for pre-built assets. static_build_path = os.path.realpath(os.path.join(os.path.dirname(os.path.abspath(dashboard.__file__)), "web"))
[docs] def __init__( self, storage: IStorage, settings: DashboardSettings, http_client: AsyncClient, router: Router = None, ): super().__init__(router=router, handle_errors=False) # context for usage in handlers self.http_client = http_client self.storage = storage self.settings = settings # create users if they don't exist if isinstance(self.settings.auth, BasicAuthSettings): asyncio.create_task(self.storage.create_users_once_ready(self.settings.auth.users)) if self.settings.enable_ui: self._initialize_ui()
def _initialize_ui(self): # controllers for delegating requests if self.settings.devserver.enabled and self.settings.devserver.port: self._ui_devserver_proxy_controller = self._create_ui_devserver_proxy_controller( port=self.settings.devserver.port ) # if no devserver is configured, we may need to serve static files if not self._ui_devserver_proxy_controller: if os.path.exists(self.static_build_path): self._ui_static_middleware = ASGIMiddlewareStaticFile(None, "", [self.static_build_path]) # if no devserver is configured and no static files are found, we can't serve the dashboard if not self._ui_static_middleware and not self._ui_devserver_proxy_controller: raise ConfigurationError( "Dashboard controller could not initiate because it got neither compiled assets nor a devserver " "configuration. If you're using an editable install, make sure to run `make build-frontend` in the " "target working copy, or start a frontend development server." ) def __repr__(self): features = { "api": True, "devserver": bool(self._ui_devserver_proxy_controller), "static": bool(self._ui_static_middleware), } return f"{type(self).__name__}({'+'.join(f for f in features if features[f])})" def _create_ui_devserver_proxy_controller(self, *, port): return HttpProxyController( Remote.from_settings_dict( { "endpoints": [{"url": f"http://localhost:{port}/"}], "liveness": {"type": "ignore"}, } ), http_client=self.http_client, logging=False, name="dashboard-devserver", ) async def __call__(self, request: HttpRequest, asgi_send: ASGISendCallable, *, transaction_id=None) -> HttpResponse: request.extensions.setdefault("user", None) if self.settings.auth: current_auth = request.basic_auth if current_auth: request.extensions["user"] = self.settings.auth.check(current_auth[0], current_auth[1]) if not request.extensions["user"]: return HttpResponse( b"Unauthorized", status=401, headers={"WWW-Authenticate": 'Basic realm="Harp Dashboard"'}, content_type="text/plain", ) # Is this a prebuilt static asset? Can we serve it using our middleware? This wil need refactoring, as it ties # the controller implementation to the underlying webserver protocol implementation, but for now, it works. if ( self._ui_static_middleware and not request.path.startswith("/api/") and hasattr(request._impl, "asgi_receive") ): # XXX todo fix await self._ui_static_middleware( { "type": "http", "path": request.path if "." in request.path else "/index.html", "method": request.method, }, request._impl.asgi_receive, asgi_send, ) return AlreadyHandledHttpResponse() try: return await super().__call__(request) except NotFoundError: if self._ui_devserver_proxy_controller: return await self._ui_devserver_proxy_controller(request) return HttpResponse("Not found.", status=404, content_type="text/plain")