From 53e983af4e82d690d6351636e7677813c521b165 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Sun, 17 May 2026 11:13:46 +0300 Subject: [PATCH] Split HTML5 authoring renderer --- services/api-server/src/api_server/html5.py | 597 +----------------- .../src/api_server/html5_authoring.py | 596 +++++++++++++++++ services/api-server/src/api_server/main.py | 18 +- 3 files changed, 611 insertions(+), 600 deletions(-) create mode 100644 services/api-server/src/api_server/html5_authoring.py diff --git a/services/api-server/src/api_server/html5.py b/services/api-server/src/api_server/html5.py index eec336f..90f7136 100644 --- a/services/api-server/src/api_server/html5.py +++ b/services/api-server/src/api_server/html5.py @@ -5,6 +5,11 @@ from html import escape from typing import Iterable from urllib.parse import quote +from api_server.html5_authoring import ( + render_html5_authoring_changes, + render_html5_authoring_preview, + render_html5_metadata_authoring, +) from sir import NodeKind, SirSnapshot _HTML5_OBJECT_CONTEXT_KINDS = { @@ -580,388 +585,6 @@ def render_html5_object_context( """ -def render_html5_authoring_changes(project_id: str, changes: Iterable[object] | None) -> str: - if changes is None: - return f""" -
-
Authoring
-

Сервер загружает историю рабочих изменений.

-
- """ - change_list = list(changes) - if not change_list: - body = '

Изменений пока нет

' - else: - body = "".join(_authoring_change_item(change) for change in change_list[:12]) - return f""" -
-
Authoring · {len(change_list)}
- {_authoring_changes_summary(change_list)} - {_authoring_recent_change(change_list)} -
{body}
- {render_html5_authoring_change_detail(project_id, None)} -
- """ - - -def render_html5_authoring_preview(project_id: str, preview: object | None, error: str | None = None) -> str: - if preview is None and error is None: - return f""" -
-
Authoring preview
-
- - - - - - - -
- {render_html5_authoring_preview_result(project_id)} -
- - - - - - - - -
- {render_html5_authoring_diff_result(project_id)} -
- """ - return render_html5_authoring_preview_result(project_id, preview, error) - - -def render_html5_authoring_preview_result(project_id: str, preview: object | None = None, error: str | None = None) -> str: - if preview is None and error is None: - return '
' - if error: - return f""" -
-
Preview result
-

{escape(error)}

-
- """ - allowed = bool(getattr(preview, "allowed", False)) - insert_text = str(getattr(preview, "insert_text", "")) - checks = getattr(preview, "checks", []) or [] - diff = getattr(preview, "semantic_diff", []) or [] - context = getattr(preview, "context", None) - object_node = getattr(context, "object", None) - routine_node = getattr(context, "routine", None) - object_name = getattr(object_node, "qualified_name", None) or getattr(object_node, "name", None) or "object unavailable" - routine_name = getattr(routine_node, "name", None) or "routine unavailable" - check_rows = "".join(_authoring_check_item(check) for check in checks[:8]) - diff_rows = "".join(_authoring_diff_item(line) for line in diff[:8]) or '

Diff пустой

' - return f""" -
-
Preview result · {'allowed' if allowed else 'blocked'}
-
- {escape(str(object_name))} - {escape(str(routine_name))} - {escape(insert_text[:180] or "insert text unavailable")} -
- {_authoring_result_summary("allowed" if allowed else "blocked", diff, checks)} -
{check_rows}
-
{diff_rows}
-
- """ - - -def render_html5_authoring_diff_result( - project_id: str, - preview: object | None = None, - error: str | None = None, - request_payload: dict | None = None, -) -> str: - if preview is None and error is None: - return '
' - if error: - return f""" -
-
Diff preview
-

{escape(error)}

-
- """ - changed = bool(getattr(preview, "changed", False)) - added = getattr(preview, "added_lines", 0) - removed = getattr(preview, "removed_lines", 0) - target = getattr(preview, "target", None) - target_name = getattr(target, "qualified_name", None) or getattr(target, "name", None) or "target unavailable" - checks = getattr(preview, "checks", []) or [] - diff = getattr(preview, "semantic_diff", []) or [] - version_preview = getattr(preview, "version_preview", None) - next_version_id = str(getattr(version_preview, "next_version_id", "")) - check_rows = "".join(_authoring_check_item(check) for check in checks[:8]) - diff_rows = "".join(_authoring_diff_item(line) for line in diff[:12]) or '

Diff пустой

' - apply_form = ( - _authoring_apply_change_set_form(project_id, request_payload or {}, next_version_id) - if changed and next_version_id - else "" - ) - return f""" -
-
Diff preview · {'changed' if changed else 'unchanged'}
-
- {escape(str(target_name))} - +{escape(str(added))} / -{escape(str(removed))} - {escape(next_version_id or "version preview unavailable")} -
- {_authoring_result_summary("changed" if changed else "unchanged", diff, checks)} -
{check_rows}
-
{diff_rows}
- {apply_form} -
- """ - - -def render_html5_authoring_apply_result(project_id: str, result: object | None = None, error: str | None = None) -> str: - if result is None and error is None: - return '
' - if error: - return f""" -
-
Apply change-set
-

{escape(error)}

-
- """ - status = str(getattr(result, "status", "UNKNOWN")) - change_id = str(getattr(result, "change_id", "")) - version = getattr(result, "version", None) - version_id = str(getattr(version, "version_id", "")) - return f""" -
-
Apply change-set
-
- {escape(status)} - {escape(change_id)} - {escape(version_id)} -
- {_authoring_apply_summary("change-set", status, change_id, version_id)} -

Change-set применен в workspace для проекта {escape(project_id)}.

-
- """ - - -def render_html5_metadata_authoring(project_id: str) -> str: - return f""" -
-
Metadata draft
-
- - - - - - - - - - - -
- {render_html5_metadata_preview_result(project_id)} -
- """ - - -def render_html5_metadata_preview_result( - project_id: str, - preview: object | None = None, - error: str | None = None, - request_payload: dict | None = None, -) -> str: - if preview is None and error is None: - return '
' - if error: - return f""" -
-
Metadata preview
-

{escape(error)}

-
- """ - changed = bool(getattr(preview, "changed", False)) - added = getattr(preview, "added_lines", 0) - target = getattr(preview, "target", None) - target_name = getattr(target, "qualified_name", None) or getattr(target, "name", None) or "target unavailable" - checks = getattr(preview, "checks", []) or [] - diff = getattr(preview, "semantic_diff", []) or [] - version_preview = getattr(preview, "version_preview", None) - next_version_id = str(getattr(version_preview, "next_version_id", "")) - check_rows = "".join(_authoring_check_item(check) for check in checks[:8]) - diff_rows = "".join(_authoring_diff_item(line) for line in diff[:12]) or '

Diff пустой

' - apply_form = ( - _metadata_apply_form(project_id, request_payload or {}, next_version_id) - if changed and next_version_id - else "" - ) - return f""" -
-
Metadata preview · {'changed' if changed else 'unchanged'}
-
- {escape(str(target_name))} - +{escape(str(added))} / -0 - {escape(next_version_id or "version preview unavailable")} -
- {_authoring_result_summary("changed" if changed else "unchanged", diff, checks)} -
{check_rows}
-
{diff_rows}
- {apply_form} -
- """ - - -def render_html5_metadata_apply_result(project_id: str, result: object | None = None, error: str | None = None) -> str: - if result is None and error is None: - return '
' - if error: - return f""" -
-
Metadata apply
-

{escape(error)}

-
- """ - status = str(getattr(result, "status", "UNKNOWN")) - change_id = str(getattr(result, "change_id", "")) - version = getattr(result, "version", None) - version_id = str(getattr(version, "version_id", "")) - return f""" -
-
Metadata apply
-
- {escape(status)} - {escape(change_id)} - {escape(version_id)} -
- {_authoring_apply_summary("metadata", status, change_id, version_id)} -

Metadata draft применен в workspace для проекта {escape(project_id)}.

-
- """ - - -def render_html5_authoring_change_detail(project_id: str, preview: object | None) -> str: - if preview is None: - return f""" -
-
Rollback preview
-

Выберите изменение, чтобы сервер рассчитал rollback diff для проекта {escape(project_id)}.

-
- """ - change_id = str(getattr(preview, "change_id", "")) - original_version_id = str(getattr(preview, "original_version_id", "")) - rollback_version_id = str(getattr(preview, "rollback_version_id", "")) - target = getattr(preview, "target", None) - target_name = getattr(target, "qualified_name", None) or getattr(target, "name", None) or "target unavailable" - diff = getattr(preview, "semantic_diff", []) or [] - checks = getattr(preview, "checks", []) or [] - apply_available = bool(getattr(preview, "apply_available", False)) - diff_rows = "".join(_authoring_diff_item(line) for line in diff[:12]) or '

Diff пустой

' - check_rows = "".join(_authoring_check_item(check) for check in checks[:8]) - apply_form = _authoring_rollback_form(project_id, change_id, rollback_version_id) if apply_available else "" - return f""" -
-
Rollback preview · {'ready' if apply_available else 'blocked'}
-
- {escape(str(target_name))} - {escape(original_version_id)} -> {escape(rollback_version_id)} - {escape(change_id)} -
- {_authoring_detail_summary(diff, checks, apply_available)} -
{check_rows}
-
{diff_rows}
- {apply_form} -
- """ - - -def render_html5_authoring_rollback_result(project_id: str, result: object | None = None, error: str | None = None) -> str: - if result is None and error is None: - return '
' - if error: - return f""" -
-
Rollback apply
-

{escape(error)}

-
- """ - status = str(getattr(result, "status", "UNKNOWN")) - change_id = str(getattr(result, "change_id", "")) - rollback_change_id = str(getattr(result, "rollback_change_id", "")) - version = getattr(result, "version", None) - version_id = str(getattr(version, "version_id", "")) - return f""" -
-
Rollback apply
-
- {escape(status)} - {escape(rollback_change_id)} - {escape(version_id)} -
- {_authoring_apply_summary("rollback", status, rollback_change_id or change_id, version_id)} -

Rollback применен в workspace для проекта {escape(project_id)}.

-
- """ - - def render_html5_status(project_id: str, snapshot: SirSnapshot) -> str: return ( f'project: {escape(project_id)}' @@ -1297,113 +920,6 @@ def _symbol_reference_item(project_id: str, reference: object) -> str: """ -def _authoring_change_item(change: object) -> str: - change_id = str(getattr(change, "change_id", "")) - status = str(getattr(change, "status", "")) - target = getattr(change, "target", None) - target_name = getattr(target, "qualified_name", None) or getattr(target, "name", None) or "target unavailable" - approved_by = str(getattr(change, "approved_by", "") or "not approved") - task_id = str(getattr(change, "task_id", "") or "no task") - added = getattr(change, "added_lines", 0) - removed = getattr(change, "removed_lines", 0) - production = "production" if bool(getattr(change, "production_applied", False)) else "workspace" - project_id = str(getattr(change, "project_id", "")) - detail_attrs = ( - f'hx-get="/html5/projects/{quote(project_id)}/authoring/changes/{quote(change_id, safe="")}" ' - 'hx-target="[data-html5-authoring-detail]" hx-swap="outerHTML"' - if change_id and project_id - else "" - ) - return f""" -
- {escape(str(target_name))} - {escape(status)} · +{escape(str(added))} / -{escape(str(removed))} · {escape(production)} - {escape(task_id)} · {escape(approved_by)} · {escape(change_id)} -
- """ - - -def _authoring_changes_summary(changes: Iterable[object]) -> str: - change_list = list(changes) - statuses = Counter(str(getattr(change, "status", "") or "UNKNOWN") for change in change_list) - production = sum(1 for change in change_list if bool(getattr(change, "production_applied", False))) - workspace = len(change_list) - production - added = sum(int(getattr(change, "added_lines", 0) or 0) for change in change_list) - removed = sum(int(getattr(change, "removed_lines", 0) or 0) for change in change_list) - status_text = ", ".join(f"{name}: {count}" for name, count in sorted(statuses.items())) or "no changes" - return f""" -

- {escape(str(len(change_list)))} changes · {escape(status_text)} · {escape(str(workspace))} workspace · {escape(str(production))} production · +{escape(str(added))} / -{escape(str(removed))} -

- """ - - -def _authoring_recent_change(changes: Iterable[object]) -> str: - change_list = list(changes) - if not change_list: - return "" - latest = change_list[0] - change_id = str(getattr(latest, "change_id", "")) - status = str(getattr(latest, "status", "") or "UNKNOWN") - target = getattr(latest, "target", None) - target_name = getattr(target, "qualified_name", None) or getattr(target, "name", None) or "target unavailable" - version = getattr(latest, "version", None) - version_id = str(getattr(latest, "version_id", "") or getattr(version, "version_id", "") or "version unavailable") - approved_by = str(getattr(latest, "approved_by", "") or "not approved") - return f""" -
- {escape(status)} · {escape(str(target_name))} - {escape(version_id)} - {escape(approved_by)} · {escape(change_id)} -
- """ - - -def _authoring_result_summary(state: str, diff: Iterable[object], checks: Iterable[object]) -> str: - diff_list = list(diff) - check_list = list(checks) - diff_kinds = Counter(str(getattr(line, "kind", "") or "CHANGE") for line in diff_list) - check_statuses = Counter(str(getattr(check, "status", "") or "UNKNOWN") for check in check_list) - added = sum(1 for line in diff_list if str(getattr(line, "kind", "")) == "ADD") - removed = sum(1 for line in diff_list if str(getattr(line, "kind", "")) == "REMOVE") - diff_text = ", ".join(f"{name}: {count}" for name, count in sorted(diff_kinds.items())) or "empty diff" - check_text = ", ".join(f"{name}: {count}" for name, count in sorted(check_statuses.items())) or "no checks" - return f""" -

- {escape(state)} · {escape(str(len(diff_list)))} diff lines · +{escape(str(added))} / -{escape(str(removed))} · {escape(diff_text)} · {escape(check_text)} -

- """ - - -def _authoring_detail_summary(diff: Iterable[object], checks: Iterable[object], apply_available: bool) -> str: - diff_list = list(diff) - check_list = list(checks) - diff_kinds = Counter(str(getattr(line, "kind", "") or "CHANGE") for line in diff_list) - check_statuses = Counter(str(getattr(check, "status", "") or "UNKNOWN") for check in check_list) - added = sum(1 for line in diff_list if str(getattr(line, "kind", "")) == "ADD") - removed = sum(1 for line in diff_list if str(getattr(line, "kind", "")) == "REMOVE") - diff_text = ", ".join(f"{name}: {count}" for name, count in sorted(diff_kinds.items())) or "empty diff" - check_text = ", ".join(f"{name}: {count}" for name, count in sorted(check_statuses.items())) or "no checks" - state = "rollback ready" if apply_available else "rollback blocked" - return f""" -

- {escape(state)} · {escape(str(len(diff_list)))} diff lines · +{escape(str(added))} / -{escape(str(removed))} · {escape(diff_text)} · {escape(check_text)} -

- """ - - -def _authoring_apply_summary(kind: str, status: str, change_id: str, version_id: str) -> str: - return f""" -

- {escape(kind)} · {escape(status or "UNKNOWN")} · {escape(change_id or "change unavailable")} · {escape(version_id or "version unavailable")} -

- """ - - def _named_node_item(label: str, node: object) -> str: name = getattr(node, "qualified_name", None) or getattr(node, "name", "") kind = getattr(node, "kind", label) @@ -1759,109 +1275,6 @@ def _flowchart_node_item(project_id: str, node: object, depth: int) -> str: """ -def _authoring_diff_item(line: object) -> str: - kind = str(getattr(line, "kind", "")) - text = str(getattr(line, "text", "")) - return f""" -
- {escape(kind)} - {escape(text)} -
- """ - - -def _authoring_check_item(check: object) -> str: - name = str(getattr(check, "name", "check")) - status = str(getattr(check, "status", "UNKNOWN")) - message = str(getattr(check, "message", "")) - return f""" -
- {escape(name)} - {escape(status)} - {escape(message)} -
- """ - - -def _authoring_rollback_form(project_id: str, change_id: str, rollback_version_id: str) -> str: - return f""" -
- - - - - - -
- {render_html5_authoring_rollback_result(project_id)} - """ - - -def _authoring_apply_change_set_form(project_id: str, payload: dict, next_version_id: str) -> str: - return f""" -
- - - - - - - - - - - -
- {render_html5_authoring_apply_result(project_id)} - """ - - -def _metadata_apply_form(project_id: str, payload: dict, next_version_id: str) -> str: - return f""" -
- - - - - - - - - - - - - - -
- {render_html5_metadata_apply_result(project_id)} - """ - - def _node_source_text(node: object | None) -> str: if node is None: return "// Модуль не найден в snapshot.\n// HTML5 IDE показывает серверный fallback без клиентского JS." diff --git a/services/api-server/src/api_server/html5_authoring.py b/services/api-server/src/api_server/html5_authoring.py new file mode 100644 index 0000000..d71ccea --- /dev/null +++ b/services/api-server/src/api_server/html5_authoring.py @@ -0,0 +1,596 @@ +from __future__ import annotations + +from collections import Counter +from html import escape +from typing import Iterable +from urllib.parse import quote + + +def render_html5_authoring_changes(project_id: str, changes: Iterable[object] | None) -> str: + if changes is None: + return f""" +
+
Authoring
+

Сервер загружает историю рабочих изменений.

+
+ """ + change_list = list(changes) + if not change_list: + body = '

Изменений пока нет

' + else: + body = "".join(_authoring_change_item(change) for change in change_list[:12]) + return f""" +
+
Authoring · {len(change_list)}
+ {_authoring_changes_summary(change_list)} + {_authoring_recent_change(change_list)} +
{body}
+ {render_html5_authoring_change_detail(project_id, None)} +
+ """ + + +def render_html5_authoring_preview(project_id: str, preview: object | None, error: str | None = None) -> str: + if preview is None and error is None: + return f""" +
+
Authoring preview
+
+ + + + + + + +
+ {render_html5_authoring_preview_result(project_id)} +
+ + + + + + + + +
+ {render_html5_authoring_diff_result(project_id)} +
+ """ + return render_html5_authoring_preview_result(project_id, preview, error) + + +def render_html5_authoring_preview_result(project_id: str, preview: object | None = None, error: str | None = None) -> str: + if preview is None and error is None: + return '
' + if error: + return f""" +
+
Preview result
+

{escape(error)}

+
+ """ + allowed = bool(getattr(preview, "allowed", False)) + insert_text = str(getattr(preview, "insert_text", "")) + checks = getattr(preview, "checks", []) or [] + diff = getattr(preview, "semantic_diff", []) or [] + context = getattr(preview, "context", None) + object_node = getattr(context, "object", None) + routine_node = getattr(context, "routine", None) + object_name = getattr(object_node, "qualified_name", None) or getattr(object_node, "name", None) or "object unavailable" + routine_name = getattr(routine_node, "name", None) or "routine unavailable" + check_rows = "".join(_authoring_check_item(check) for check in checks[:8]) + diff_rows = "".join(_authoring_diff_item(line) for line in diff[:8]) or '

Diff пустой

' + return f""" +
+
Preview result · {'allowed' if allowed else 'blocked'}
+
+ {escape(str(object_name))} + {escape(str(routine_name))} + {escape(insert_text[:180] or "insert text unavailable")} +
+ {_authoring_result_summary("allowed" if allowed else "blocked", diff, checks)} +
{check_rows}
+
{diff_rows}
+
+ """ + + +def render_html5_authoring_diff_result( + project_id: str, + preview: object | None = None, + error: str | None = None, + request_payload: dict | None = None, +) -> str: + if preview is None and error is None: + return '
' + if error: + return f""" +
+
Diff preview
+

{escape(error)}

+
+ """ + changed = bool(getattr(preview, "changed", False)) + added = getattr(preview, "added_lines", 0) + removed = getattr(preview, "removed_lines", 0) + target = getattr(preview, "target", None) + target_name = getattr(target, "qualified_name", None) or getattr(target, "name", None) or "target unavailable" + checks = getattr(preview, "checks", []) or [] + diff = getattr(preview, "semantic_diff", []) or [] + version_preview = getattr(preview, "version_preview", None) + next_version_id = str(getattr(version_preview, "next_version_id", "")) + check_rows = "".join(_authoring_check_item(check) for check in checks[:8]) + diff_rows = "".join(_authoring_diff_item(line) for line in diff[:12]) or '

Diff пустой

' + apply_form = ( + _authoring_apply_change_set_form(project_id, request_payload or {}, next_version_id) + if changed and next_version_id + else "" + ) + return f""" +
+
Diff preview · {'changed' if changed else 'unchanged'}
+
+ {escape(str(target_name))} + +{escape(str(added))} / -{escape(str(removed))} + {escape(next_version_id or "version preview unavailable")} +
+ {_authoring_result_summary("changed" if changed else "unchanged", diff, checks)} +
{check_rows}
+
{diff_rows}
+ {apply_form} +
+ """ + + +def render_html5_authoring_apply_result(project_id: str, result: object | None = None, error: str | None = None) -> str: + if result is None and error is None: + return '
' + if error: + return f""" +
+
Apply change-set
+

{escape(error)}

+
+ """ + status = str(getattr(result, "status", "UNKNOWN")) + change_id = str(getattr(result, "change_id", "")) + version = getattr(result, "version", None) + version_id = str(getattr(version, "version_id", "")) + return f""" +
+
Apply change-set
+
+ {escape(status)} + {escape(change_id)} + {escape(version_id)} +
+ {_authoring_apply_summary("change-set", status, change_id, version_id)} +

Change-set применен в workspace для проекта {escape(project_id)}.

+
+ """ + + +def render_html5_metadata_authoring(project_id: str) -> str: + return f""" +
+
Metadata draft
+
+ + + + + + + + + + + +
+ {render_html5_metadata_preview_result(project_id)} +
+ """ + + +def render_html5_metadata_preview_result( + project_id: str, + preview: object | None = None, + error: str | None = None, + request_payload: dict | None = None, +) -> str: + if preview is None and error is None: + return '
' + if error: + return f""" +
+
Metadata preview
+

{escape(error)}

+
+ """ + changed = bool(getattr(preview, "changed", False)) + added = getattr(preview, "added_lines", 0) + target = getattr(preview, "target", None) + target_name = getattr(target, "qualified_name", None) or getattr(target, "name", None) or "target unavailable" + checks = getattr(preview, "checks", []) or [] + diff = getattr(preview, "semantic_diff", []) or [] + version_preview = getattr(preview, "version_preview", None) + next_version_id = str(getattr(version_preview, "next_version_id", "")) + check_rows = "".join(_authoring_check_item(check) for check in checks[:8]) + diff_rows = "".join(_authoring_diff_item(line) for line in diff[:12]) or '

Diff пустой

' + apply_form = ( + _metadata_apply_form(project_id, request_payload or {}, next_version_id) + if changed and next_version_id + else "" + ) + return f""" +
+
Metadata preview · {'changed' if changed else 'unchanged'}
+
+ {escape(str(target_name))} + +{escape(str(added))} / -0 + {escape(next_version_id or "version preview unavailable")} +
+ {_authoring_result_summary("changed" if changed else "unchanged", diff, checks)} +
{check_rows}
+
{diff_rows}
+ {apply_form} +
+ """ + + +def render_html5_metadata_apply_result(project_id: str, result: object | None = None, error: str | None = None) -> str: + if result is None and error is None: + return '
' + if error: + return f""" +
+
Metadata apply
+

{escape(error)}

+
+ """ + status = str(getattr(result, "status", "UNKNOWN")) + change_id = str(getattr(result, "change_id", "")) + version = getattr(result, "version", None) + version_id = str(getattr(version, "version_id", "")) + return f""" +
+
Metadata apply
+
+ {escape(status)} + {escape(change_id)} + {escape(version_id)} +
+ {_authoring_apply_summary("metadata", status, change_id, version_id)} +

Metadata draft применен в workspace для проекта {escape(project_id)}.

+
+ """ + + +def render_html5_authoring_change_detail(project_id: str, preview: object | None) -> str: + if preview is None: + return f""" +
+
Rollback preview
+

Выберите изменение, чтобы сервер рассчитал rollback diff для проекта {escape(project_id)}.

+
+ """ + change_id = str(getattr(preview, "change_id", "")) + original_version_id = str(getattr(preview, "original_version_id", "")) + rollback_version_id = str(getattr(preview, "rollback_version_id", "")) + target = getattr(preview, "target", None) + target_name = getattr(target, "qualified_name", None) or getattr(target, "name", None) or "target unavailable" + diff = getattr(preview, "semantic_diff", []) or [] + checks = getattr(preview, "checks", []) or [] + apply_available = bool(getattr(preview, "apply_available", False)) + diff_rows = "".join(_authoring_diff_item(line) for line in diff[:12]) or '

Diff пустой

' + check_rows = "".join(_authoring_check_item(check) for check in checks[:8]) + apply_form = _authoring_rollback_form(project_id, change_id, rollback_version_id) if apply_available else "" + return f""" +
+
Rollback preview · {'ready' if apply_available else 'blocked'}
+
+ {escape(str(target_name))} + {escape(original_version_id)} -> {escape(rollback_version_id)} + {escape(change_id)} +
+ {_authoring_detail_summary(diff, checks, apply_available)} +
{check_rows}
+
{diff_rows}
+ {apply_form} +
+ """ + + +def render_html5_authoring_rollback_result(project_id: str, result: object | None = None, error: str | None = None) -> str: + if result is None and error is None: + return '
' + if error: + return f""" +
+
Rollback apply
+

{escape(error)}

+
+ """ + status = str(getattr(result, "status", "UNKNOWN")) + change_id = str(getattr(result, "change_id", "")) + rollback_change_id = str(getattr(result, "rollback_change_id", "")) + version = getattr(result, "version", None) + version_id = str(getattr(version, "version_id", "")) + return f""" +
+
Rollback apply
+
+ {escape(status)} + {escape(rollback_change_id)} + {escape(version_id)} +
+ {_authoring_apply_summary("rollback", status, rollback_change_id or change_id, version_id)} +

Rollback применен в workspace для проекта {escape(project_id)}.

+
+ """ + +def _authoring_change_item(change: object) -> str: + change_id = str(getattr(change, "change_id", "")) + status = str(getattr(change, "status", "")) + target = getattr(change, "target", None) + target_name = getattr(target, "qualified_name", None) or getattr(target, "name", None) or "target unavailable" + approved_by = str(getattr(change, "approved_by", "") or "not approved") + task_id = str(getattr(change, "task_id", "") or "no task") + added = getattr(change, "added_lines", 0) + removed = getattr(change, "removed_lines", 0) + production = "production" if bool(getattr(change, "production_applied", False)) else "workspace" + project_id = str(getattr(change, "project_id", "")) + detail_attrs = ( + f'hx-get="/html5/projects/{quote(project_id)}/authoring/changes/{quote(change_id, safe="")}" ' + 'hx-target="[data-html5-authoring-detail]" hx-swap="outerHTML"' + if change_id and project_id + else "" + ) + return f""" +
+ {escape(str(target_name))} + {escape(status)} · +{escape(str(added))} / -{escape(str(removed))} · {escape(production)} + {escape(task_id)} · {escape(approved_by)} · {escape(change_id)} +
+ """ + + +def _authoring_changes_summary(changes: Iterable[object]) -> str: + change_list = list(changes) + statuses = Counter(str(getattr(change, "status", "") or "UNKNOWN") for change in change_list) + production = sum(1 for change in change_list if bool(getattr(change, "production_applied", False))) + workspace = len(change_list) - production + added = sum(int(getattr(change, "added_lines", 0) or 0) for change in change_list) + removed = sum(int(getattr(change, "removed_lines", 0) or 0) for change in change_list) + status_text = ", ".join(f"{name}: {count}" for name, count in sorted(statuses.items())) or "no changes" + return f""" +

+ {escape(str(len(change_list)))} changes · {escape(status_text)} · {escape(str(workspace))} workspace · {escape(str(production))} production · +{escape(str(added))} / -{escape(str(removed))} +

+ """ + + +def _authoring_recent_change(changes: Iterable[object]) -> str: + change_list = list(changes) + if not change_list: + return "" + latest = change_list[0] + change_id = str(getattr(latest, "change_id", "")) + status = str(getattr(latest, "status", "") or "UNKNOWN") + target = getattr(latest, "target", None) + target_name = getattr(target, "qualified_name", None) or getattr(target, "name", None) or "target unavailable" + version = getattr(latest, "version", None) + version_id = str(getattr(latest, "version_id", "") or getattr(version, "version_id", "") or "version unavailable") + approved_by = str(getattr(latest, "approved_by", "") or "not approved") + return f""" +
+ {escape(status)} · {escape(str(target_name))} + {escape(version_id)} + {escape(approved_by)} · {escape(change_id)} +
+ """ + + +def _authoring_result_summary(state: str, diff: Iterable[object], checks: Iterable[object]) -> str: + diff_list = list(diff) + check_list = list(checks) + diff_kinds = Counter(str(getattr(line, "kind", "") or "CHANGE") for line in diff_list) + check_statuses = Counter(str(getattr(check, "status", "") or "UNKNOWN") for check in check_list) + added = sum(1 for line in diff_list if str(getattr(line, "kind", "")) == "ADD") + removed = sum(1 for line in diff_list if str(getattr(line, "kind", "")) == "REMOVE") + diff_text = ", ".join(f"{name}: {count}" for name, count in sorted(diff_kinds.items())) or "empty diff" + check_text = ", ".join(f"{name}: {count}" for name, count in sorted(check_statuses.items())) or "no checks" + return f""" +

+ {escape(state)} · {escape(str(len(diff_list)))} diff lines · +{escape(str(added))} / -{escape(str(removed))} · {escape(diff_text)} · {escape(check_text)} +

+ """ + + +def _authoring_detail_summary(diff: Iterable[object], checks: Iterable[object], apply_available: bool) -> str: + diff_list = list(diff) + check_list = list(checks) + diff_kinds = Counter(str(getattr(line, "kind", "") or "CHANGE") for line in diff_list) + check_statuses = Counter(str(getattr(check, "status", "") or "UNKNOWN") for check in check_list) + added = sum(1 for line in diff_list if str(getattr(line, "kind", "")) == "ADD") + removed = sum(1 for line in diff_list if str(getattr(line, "kind", "")) == "REMOVE") + diff_text = ", ".join(f"{name}: {count}" for name, count in sorted(diff_kinds.items())) or "empty diff" + check_text = ", ".join(f"{name}: {count}" for name, count in sorted(check_statuses.items())) or "no checks" + state = "rollback ready" if apply_available else "rollback blocked" + return f""" +

+ {escape(state)} · {escape(str(len(diff_list)))} diff lines · +{escape(str(added))} / -{escape(str(removed))} · {escape(diff_text)} · {escape(check_text)} +

+ """ + + +def _authoring_apply_summary(kind: str, status: str, change_id: str, version_id: str) -> str: + return f""" +

+ {escape(kind)} · {escape(status or "UNKNOWN")} · {escape(change_id or "change unavailable")} · {escape(version_id or "version unavailable")} +

+ """ + +def _authoring_diff_item(line: object) -> str: + kind = str(getattr(line, "kind", "")) + text = str(getattr(line, "text", "")) + return f""" +
+ {escape(kind)} + {escape(text)} +
+ """ + + +def _authoring_check_item(check: object) -> str: + name = str(getattr(check, "name", "check")) + status = str(getattr(check, "status", "UNKNOWN")) + message = str(getattr(check, "message", "")) + return f""" +
+ {escape(name)} + {escape(status)} + {escape(message)} +
+ """ + + +def _authoring_rollback_form(project_id: str, change_id: str, rollback_version_id: str) -> str: + return f""" +
+ + + + + + +
+ {render_html5_authoring_rollback_result(project_id)} + """ + + +def _authoring_apply_change_set_form(project_id: str, payload: dict, next_version_id: str) -> str: + return f""" +
+ + + + + + + + + + + +
+ {render_html5_authoring_apply_result(project_id)} + """ + + +def _metadata_apply_form(project_id: str, payload: dict, next_version_id: str) -> str: + return f""" +
+ + + + + + + + + + + + + + +
+ {render_html5_metadata_apply_result(project_id)} + """ diff --git a/services/api-server/src/api_server/main.py b/services/api-server/src/api_server/main.py index 8db6242..3638438 100644 --- a/services/api-server/src/api_server/main.py +++ b/services/api-server/src/api_server/main.py @@ -38,17 +38,9 @@ from neo4j import AsyncGraphDatabase from pydantic import BaseModel, Field from api_server.html5 import ( - render_html5_authoring_apply_result, - render_html5_authoring_changes, - render_html5_authoring_change_detail, - render_html5_authoring_diff_result, - 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, render_html5_object_context, render_html5_object_report, render_html5_project_rows, @@ -59,6 +51,16 @@ from api_server.html5 import ( render_html5_status, render_html5_symbols, ) +from api_server.html5_authoring import ( + render_html5_authoring_apply_result, + render_html5_authoring_changes, + render_html5_authoring_change_detail, + render_html5_authoring_diff_result, + render_html5_authoring_preview_result, + render_html5_authoring_rollback_result, + render_html5_metadata_apply_result, + render_html5_metadata_preview_result, +) from api_server.html5_operations import ( render_html5_operation_detail, render_html5_operation_rows,