diff --git a/services/api-server/src/api_server/html5.py b/services/api-server/src/api_server/html5.py index c7f5c3e..df803fd 100644 --- a/services/api-server/src/api_server/html5.py +++ b/services/api-server/src/api_server/html5.py @@ -231,6 +231,7 @@ def render_html5_editor( {render_html5_symbols(snapshot, q, project_id)} {render_html5_symbol_detail(project_id, None)} + {render_html5_flowchart(project_id, None)} {render_html5_project_report(project_id, None)} {render_html5_review(project_id, None)} {render_html5_authoring_preview(project_id, None)} @@ -246,6 +247,48 @@ def render_html5_editor( return _page(f"SFERA HTML5 - {project_id}", content) +def render_html5_flowchart(project_id: str, flowchart: object | None) -> str: + if flowchart is None: + return f""" +
+
Карта связей
+

Сервер собирает граф проекта.

+
+ """ + nodes = getattr(flowchart, "nodes", []) or [] + edges = getattr(flowchart, "edges", []) or [] + mode = str(getattr(flowchart, "mode", "overview")) + body = "".join(_flowchart_edge_item(item, nodes) for item in edges[:10]) + if not body: + body = "".join(_flowchart_node_item(item) for item in nodes[:10]) + if not body: + body = '

Связи проекта не найдены

' + return f""" +
+
Карта связей · {escape(mode)}
+
+ {_metric("Nodes", len(nodes))} + {_metric("Edges", len(edges))} + {_metric("Total nodes", getattr(flowchart, "total_nodes", 0))} + {_metric("Total edges", getattr(flowchart, "total_edges", 0))} +
+
{body}
+
+ """ + + def render_html5_project_report(project_id: str, report: dict | None) -> str: if report is None: return f""" @@ -1527,6 +1570,19 @@ def _flowchart_edge_item(edge: object, nodes: Iterable[object]) -> str: """ +def _flowchart_node_item(node: object) -> str: + name = str(getattr(node, "qualified_name", "") or getattr(node, "label", "") or "node") + kind = str(getattr(node, "kind", "") or "NODE") + level = getattr(node, "level", 0) + count = getattr(node, "count", 1) + return f""" +
+ {escape(name)} + {escape(kind)} · level {escape(str(level))} · count {escape(str(count))} +
+ """ + + def _authoring_diff_item(line: object) -> str: kind = str(getattr(line, "kind", "")) text = str(getattr(line, "text", "")) diff --git a/services/api-server/src/api_server/main.py b/services/api-server/src/api_server/main.py index d9ecb95..7b3ecaf 100644 --- a/services/api-server/src/api_server/main.py +++ b/services/api-server/src/api_server/main.py @@ -43,6 +43,7 @@ from api_server.html5 import ( render_html5_authoring_preview_result, render_html5_authoring_rollback_result, render_html5_editor, + render_html5_flowchart, render_html5_index, render_html5_metadata_apply_result, render_html5_metadata_preview_result, @@ -1741,6 +1742,20 @@ async def html5_project_review(project_id: str) -> Response: ) +@app.get("/html5/projects/{project_id}/flowchart") +async def html5_project_flowchart( + project_id: str, + focus: str | None = None, + depth: int = 1, + limit: int = 80, +) -> Response: + flowchart = await project_flowchart(project_id, focus=focus, depth=depth, limit=limit) + return Response( + render_html5_flowchart(project_id, flowchart), + media_type="text/html; charset=utf-8", + ) + + @app.get("/html5/projects/{project_id}/objects/context/{object_name}") async def html5_project_object_context(project_id: str, object_name: str) -> Response: schema = await get_object_schema(project_id, object_name) diff --git a/services/api-server/tests/test_api.py b/services/api-server/tests/test_api.py index 8526598..2e765d9 100644 --- a/services/api-server/tests/test_api.py +++ b/services/api-server/tests/test_api.py @@ -108,6 +108,8 @@ def test_html5_server_rendered_project_editor(tmp_path: Path): assert "data-html5-editor" in editor.text assert "data-html5-symbol-results" in editor.text assert "data-html5-symbol-detail" in editor.text + assert "data-html5-flowchart" in editor.text + assert f'hx-get="/html5/projects/{project_id}/flowchart"' in editor.text assert "data-html5-project-report" in editor.text assert f'hx-get="/html5/projects/{project_id}/report"' in editor.text assert "data-html5-review" in editor.text @@ -335,6 +337,38 @@ def test_html5_object_context_fragment(tmp_path: Path): assert "