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 "