Одноколоночный трейс

This commit is contained in:
2026-04-30 10:15:07 +03:00
parent 72162dd050
commit 238c65c9c2
3 changed files with 36 additions and 36 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "plba" name = "plba"
version = "0.3.5" version = "0.3.6"
description = "Platform runtime for business applications" description = "Platform runtime for business applications"
readme = "README.md" readme = "README.md"
requires-python = ">=3.11" requires-python = ">=3.11"
+30 -32
View File
@@ -95,7 +95,7 @@ class TraceResponseRenderer:
def _render_html(self, trace_view: TraceLogView, request: TraceQueryRequest) -> HTMLResponse: def _render_html(self, trace_view: TraceLogView, request: TraceQueryRequest) -> HTMLResponse:
title = escape(f"Trace {trace_view.trace_id}") title = escape(f"Trace {trace_view.trace_id}")
header_links = self._related_trace_links(trace_view, request) header_links = self._related_trace_links(trace_view, request)
rows = self._step_rows(trace_view, request) sections = self._step_sections(trace_view, request)
html = f"""<!DOCTYPE html> html = f"""<!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@@ -166,26 +166,16 @@ class TraceResponseRenderer:
a:hover {{ a:hover {{
text-decoration: underline; text-decoration: underline;
}} }}
table {{ .step-section + .step-section {{
width: 100%; margin-top: 24px;
border-collapse: collapse; padding-top: 24px;
}}
th, td {{
vertical-align: top;
padding: 14px 12px;
border-top: 1px solid var(--line); border-top: 1px solid var(--line);
}} }}
th {{ .step-title {{
text-align: left; margin: 0 0 14px;
color: var(--muted); font-size: 22px;
font-size: 13px; line-height: 1.2;
text-transform: uppercase;
letter-spacing: 0.06em;
}}
td.step {{
width: 240px;
font-weight: 700; font-weight: 700;
white-space: nowrap;
}} }}
.log-entry + .log-entry {{ .log-entry + .log-entry {{
margin-top: 14px; margin-top: 14px;
@@ -222,6 +212,21 @@ class TraceResponseRenderer:
.level-DEBUG {{ .level-DEBUG {{
color: var(--debug); color: var(--debug);
}} }}
@media (max-width: 640px) {{
.page {{
padding: 14px;
}}
.card {{
padding: 16px 14px;
border-radius: 12px;
}}
h1 {{
font-size: 22px;
}}
.step-title {{
font-size: 20px;
}}
}}
</style> </style>
</head> </head>
<body> <body>
@@ -237,14 +242,7 @@ class TraceResponseRenderer:
</section> </section>
<section class="card"> <section class="card">
<h2>Logs By Step</h2> <h2>Logs By Step</h2>
<table> {sections}
<thead>
<tr><th>Step</th><th>Log</th></tr>
</thead>
<tbody>
{rows}
</tbody>
</table>
</section> </section>
</div> </div>
</body> </body>
@@ -283,11 +281,11 @@ class TraceResponseRenderer:
) )
return f"/traces/{trace_id}?{params}" return f"/traces/{trace_id}?{params}"
def _step_rows(self, trace_view: TraceLogView, request: TraceQueryRequest) -> str: def _step_sections(self, trace_view: TraceLogView, request: TraceQueryRequest) -> str:
groups = self._step_groups(trace_view.records) groups = self._step_groups(trace_view.records)
if not groups: if not groups:
return "<tr><td class=\"step\">&nbsp;</td><td>No log records for selected filters.</td></tr>" return "<section class=\"step-section\"><h3 class=\"step-title\">No step</h3><div>No log records for selected filters.</div></section>"
return "".join(self._step_row(group, request.include_attrs_json) for group in groups) return "".join(self._step_section(group, request.include_attrs_json) for group in groups)
def _step_groups(self, records: tuple[TraceLogRecord, ...]) -> tuple[TraceStepGroup, ...]: def _step_groups(self, records: tuple[TraceLogRecord, ...]) -> tuple[TraceStepGroup, ...]:
grouped: dict[str, list[TraceLogRecord]] = {} grouped: dict[str, list[TraceLogRecord]] = {}
@@ -300,10 +298,10 @@ class TraceResponseRenderer:
grouped[step].append(record) grouped[step].append(record)
return tuple(TraceStepGroup(step=step, records=tuple(grouped[step])) for step in order) return tuple(TraceStepGroup(step=step, records=tuple(grouped[step])) for step in order)
def _step_row(self, group: TraceStepGroup, include_attrs_json: bool) -> str: def _step_section(self, group: TraceStepGroup, include_attrs_json: bool) -> str:
step_label = escape(group.step) if group.step else "&nbsp;" step_label = escape(group.step) if group.step else "No step"
entries = "".join(self._log_entry(record, include_attrs_json) for record in group.records) entries = "".join(self._log_entry(record, include_attrs_json) for record in group.records)
return f"<tr><td class=\"step\">{step_label}</td><td>{entries}</td></tr>" return f"<section class=\"step-section\"><h3 class=\"step-title\">{step_label}</h3>{entries}</section>"
def _log_entry(self, record: TraceLogRecord, include_attrs_json: bool) -> str: def _log_entry(self, record: TraceLogRecord, include_attrs_json: bool) -> str:
level = escape(record.level) level = escape(record.level)
+5 -3
View File
@@ -201,11 +201,13 @@ def test_trace_endpoint_returns_html_page_with_related_links() -> None:
assert response.status_code == 200 assert response.status_code == 200
assert response.headers["content-type"].startswith("text/html") assert response.headers["content-type"].startswith("text/html")
assert "<th>Step</th><th>Log</th>" in response.text assert "<h2>Logs By Step</h2>" in response.text
assert ">load_stocks<" in response.text assert '<h3 class="step-title">load_stocks</h3>' in response.text
assert ">filter_stocks<" in response.text assert '<h3 class="step-title">filter_stocks</h3>' in response.text
assert "loaded prices" in response.text assert "loaded prices" in response.text
assert "filtered suspicious ticker" in response.text assert "filtered suspicious ticker" in response.text
assert "2026-04-28T10:11:12+00:00 | INFO | ok" in response.text
assert "2026-04-28T10:11:12+00:00 | WARNING | degraded" in response.text
assert 'href="/traces/trace-1?format=html&amp;levels=ERROR%2CWARNING%2CINFO&amp;attrs_json=true"' in response.text assert 'href="/traces/trace-1?format=html&amp;levels=ERROR%2CWARNING%2CINFO&amp;attrs_json=true"' in response.text
assert 'href="/traces/parent-1?format=html&amp;levels=ERROR%2CWARNING%2CINFO&amp;attrs_json=true"' in response.text assert 'href="/traces/parent-1?format=html&amp;levels=ERROR%2CWARNING%2CINFO&amp;attrs_json=true"' in response.text
assert 'href="/traces/child-1?format=html&amp;levels=ERROR%2CWARNING%2CINFO&amp;attrs_json=true"' in response.text assert 'href="/traces/child-1?format=html&amp;levels=ERROR%2CWARNING%2CINFO&amp;attrs_json=true"' in response.text