trace-agent-ui 0.2.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (21) hide show
  1. {trace_agent_ui-0.2.0/src/trace_agent_ui.egg-info → trace_agent_ui-0.3.0}/PKG-INFO +3 -1
  2. {trace_agent_ui-0.2.0 → trace_agent_ui-0.3.0}/pyproject.toml +6 -1
  3. trace_agent_ui-0.3.0/src/trace_agent_ui/app.py +111 -0
  4. trace_agent_ui-0.2.0/src/trace_agent_ui/static/assets/index-CUMFgaU0.js → trace_agent_ui-0.3.0/src/trace_agent_ui/static/assets/index-CVfISqiI.js +1 -1
  5. {trace_agent_ui-0.2.0 → trace_agent_ui-0.3.0}/src/trace_agent_ui/static/index.html +1 -1
  6. {trace_agent_ui-0.2.0 → trace_agent_ui-0.3.0/src/trace_agent_ui.egg-info}/PKG-INFO +3 -1
  7. {trace_agent_ui-0.2.0 → trace_agent_ui-0.3.0}/src/trace_agent_ui.egg-info/SOURCES.txt +3 -2
  8. {trace_agent_ui-0.2.0 → trace_agent_ui-0.3.0}/src/trace_agent_ui.egg-info/requires.txt +3 -0
  9. trace_agent_ui-0.3.0/tests/test_app.py +143 -0
  10. trace_agent_ui-0.2.0/src/trace_agent_ui/app.py +0 -77
  11. {trace_agent_ui-0.2.0 → trace_agent_ui-0.3.0}/MANIFEST.in +0 -0
  12. {trace_agent_ui-0.2.0 → trace_agent_ui-0.3.0}/README.md +0 -0
  13. {trace_agent_ui-0.2.0 → trace_agent_ui-0.3.0}/setup.cfg +0 -0
  14. {trace_agent_ui-0.2.0 → trace_agent_ui-0.3.0}/src/trace_agent_ui/__init__.py +0 -0
  15. {trace_agent_ui-0.2.0 → trace_agent_ui-0.3.0}/src/trace_agent_ui/static/assets/index-hExWyjb_.css +0 -0
  16. {trace_agent_ui-0.2.0 → trace_agent_ui-0.3.0}/src/trace_agent_ui/static/favicon.svg +0 -0
  17. {trace_agent_ui-0.2.0 → trace_agent_ui-0.3.0}/src/trace_agent_ui/static/icon.png +0 -0
  18. {trace_agent_ui-0.2.0 → trace_agent_ui-0.3.0}/src/trace_agent_ui/static/icons.svg +0 -0
  19. {trace_agent_ui-0.2.0 → trace_agent_ui-0.3.0}/src/trace_agent_ui.egg-info/dependency_links.txt +0 -0
  20. {trace_agent_ui-0.2.0 → trace_agent_ui-0.3.0}/src/trace_agent_ui.egg-info/entry_points.txt +0 -0
  21. {trace_agent_ui-0.2.0 → trace_agent_ui-0.3.0}/src/trace_agent_ui.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trace-agent-ui
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: TraceAgent UI static server.
5
5
  Author-email: Enrique Javier Villar Cea <villarcea.enriquejavier@gmail.com>, Javier Rodriguez Castellano <javirodriguezcastellano@gmail.com>
6
6
  License: MIT
@@ -21,6 +21,8 @@ Description-Content-Type: text/markdown
21
21
  Requires-Dist: fastapi<1.0.0,>=0.115.0
22
22
  Requires-Dist: httpx<1.0.0,>=0.27.0
23
23
  Requires-Dist: uvicorn[standard]<1.0.0,>=0.30.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest<9.0.0,>=8.3.2; extra == "dev"
24
26
 
25
27
  # TraceAgent UI
26
28
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "trace-agent-ui"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "TraceAgent UI static server."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -36,6 +36,11 @@ Homepage = "https://github.com/LixusSoftware/TraceAgent"
36
36
  Repository = "https://github.com/LixusSoftware/TraceAgent"
37
37
  Issues = "https://github.com/LixusSoftware/TraceAgent/issues"
38
38
 
39
+ [project.optional-dependencies]
40
+ dev = [
41
+ "pytest>=8.3.2,<9.0.0",
42
+ ]
43
+
39
44
  [project.scripts]
40
45
  trace-agent-ui = "trace_agent_ui.app:main"
41
46
 
@@ -0,0 +1,111 @@
1
+ """TraceAgent UI static file server.
2
+
3
+ Serves the built React SPA from package data and proxies /api/* requests
4
+ to the TraceAgent backend server.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ from pathlib import Path
11
+
12
+ import httpx
13
+ from fastapi import FastAPI, Request
14
+ from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
15
+ from starlette.background import BackgroundTask
16
+
17
+ # Headers that describe one connection, not the message: they must not be copied across the proxy.
18
+ _HOP_BY_HOP = {
19
+ "host",
20
+ "content-length",
21
+ "connection",
22
+ "keep-alive",
23
+ "transfer-encoding",
24
+ "upgrade",
25
+ "te",
26
+ "trailers",
27
+ "proxy-authenticate",
28
+ "proxy-authorization",
29
+ }
30
+
31
+
32
+ def create_app(
33
+ static_dir: str | None = None,
34
+ api_base_url: str | None = None,
35
+ transport: httpx.AsyncBaseTransport | None = None,
36
+ ) -> FastAPI:
37
+ app = FastAPI(title="TraceAgent UI")
38
+
39
+ if static_dir is None:
40
+ import importlib.resources as pkg_resources
41
+
42
+ static_dir = str(pkg_resources.files("trace_agent_ui") / "static")
43
+ static_root = Path(static_dir).resolve()
44
+
45
+ api_base = (api_base_url or os.getenv("TRACE_AGENT_SERVER_URL", "http://127.0.0.1:8000")).rstrip("/")
46
+
47
+ # The API proxy has to be registered before the catch-all below: routes match in order, so
48
+ # anything registered earlier (or a static mount at "/") would answer /api/* with a 404.
49
+ @app.api_route(
50
+ "/api/{path:path}",
51
+ methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
52
+ response_model=None,
53
+ )
54
+ async def proxy_to_backend(request: Request, path: str) -> StreamingResponse | JSONResponse:
55
+ """Proxy API requests to the TraceAgent backend server, streaming the response (SSE included)."""
56
+ headers = {k: v for k, v in request.headers.items() if k.lower() not in _HOP_BY_HOP}
57
+ # No read timeout: the live event stream stays open as long as the run does.
58
+ client = httpx.AsyncClient(transport=transport, timeout=httpx.Timeout(30.0, read=None))
59
+ upstream_request = client.build_request(
60
+ request.method,
61
+ f"{api_base}/api/{path}",
62
+ headers=headers,
63
+ params=request.query_params,
64
+ content=await request.body(),
65
+ )
66
+ try:
67
+ upstream = await client.send(upstream_request, stream=True)
68
+ except httpx.RequestError as exc:
69
+ await client.aclose()
70
+ return JSONResponse(
71
+ {"detail": f"The TraceAgent server at {api_base} is not reachable ({type(exc).__name__})."},
72
+ status_code=502,
73
+ )
74
+
75
+ async def close() -> None:
76
+ await upstream.aclose()
77
+ await client.aclose()
78
+
79
+ return StreamingResponse(
80
+ upstream.aiter_raw(),
81
+ status_code=upstream.status_code,
82
+ headers={k: v for k, v in upstream.headers.items() if k.lower() not in _HOP_BY_HOP},
83
+ background=BackgroundTask(close),
84
+ )
85
+
86
+ @app.get("/health")
87
+ async def health() -> dict[str, str]:
88
+ return {"status": "ok"}
89
+
90
+ @app.get("/{full_path:path}")
91
+ async def serve_spa(full_path: str) -> FileResponse:
92
+ """Serve a built asset if it exists, and index.html for any other path (SPA fallback)."""
93
+ candidate = (static_root / full_path).resolve()
94
+ if full_path and candidate.is_file() and static_root in candidate.parents:
95
+ return FileResponse(candidate)
96
+ return FileResponse(static_root / "index.html")
97
+
98
+ return app
99
+
100
+
101
+ def main() -> None:
102
+ import uvicorn
103
+
104
+ port = int(os.getenv("TRACE_AGENT_UI_PORT", "8080"))
105
+ host = os.getenv("TRACE_AGENT_UI_HOST", "0.0.0.0")
106
+ app = create_app()
107
+ uvicorn.run(app, host=host, port=port)
108
+
109
+
110
+ if __name__ == "__main__":
111
+ main()