From 624dc5d7f031b62c61f82f80a183e7b6c2bcee5d Mon Sep 17 00:00:00 2001 From: Mikhail Date: Sun, 17 May 2026 11:08:32 +0300 Subject: [PATCH] Split HTML5 setup renderer --- services/api-server/src/api_server/html5.py | 307 ----------------- .../api-server/src/api_server/html5_setup.py | 314 ++++++++++++++++++ services/api-server/src/api_server/main.py | 12 +- 3 files changed, 321 insertions(+), 312 deletions(-) create mode 100644 services/api-server/src/api_server/html5_setup.py diff --git a/services/api-server/src/api_server/html5.py b/services/api-server/src/api_server/html5.py index e0fda12..eec336f 100644 --- a/services/api-server/src/api_server/html5.py +++ b/services/api-server/src/api_server/html5.py @@ -962,238 +962,6 @@ def render_html5_authoring_rollback_result(project_id: str, result: object | Non """ -def render_html5_project_setup(*, project_id: str, projects: Iterable[object], setup: object) -> str: - project_nav = "\n".join(_project_link(project, project_id) for project in projects) - name = _setup_name(setup) - sources = getattr(setup, "import_sources", []) or [] - source_cards = "".join(_import_source_card(source) for source in sources) - content = f""" -
- {_topbar(project_id, project_nav)} -
- -
- {render_html5_settings_panel(project_id, setup)} - {render_html5_setup_actions(project_id, setup)} - {render_html5_setup_summary(project_id, setup)} -
-
-
- """ - return _page(f"SFERA HTML5 setup - {project_id}", content) - - -def render_html5_settings_panel(project_id: str, setup: object, saved: bool = False) -> str: - settings = getattr(setup, "settings", None) - name = str(getattr(settings, "name", "") or "") - platform_version = str(getattr(settings, "platform_version", "") or "") - compatibility_mode = str(getattr(settings, "compatibility_mode", "") or "") - notice = 'Сохранено' if saved else "" - return f""" -
-
Базовые настройки {notice}
-
- - - - -
-
- """ - - -def render_html5_setup_actions(project_id: str, setup: object) -> str: - sources = getattr(setup, "import_sources", []) or [] - current_source = _enum_text(getattr(setup, "current_source", None) or "") - source_options = "".join(_source_option(source, current_source) for source in sources) - if not source_options: - source_options = f'' - return f""" -
-
- - - -
-
- -
-
- -
-
- -
-
- -
-
- {render_html5_import_check(project_id)} - {render_html5_import_job(project_id)} - """ - - -def render_html5_import_check(project_id: str, check: object | None = None) -> str: - if check is None: - return f""" -
-
Проверка импорта
-

Запустите server-side preflight перед импортом проекта {escape(project_id)}.

-
- """ - status = str(getattr(check, "status", "UNKNOWN")) - source = _enum_text(getattr(check, "source", "")) - ready = bool(getattr(check, "ready", False)) - checks = getattr(check, "checks", []) or [] - items = "".join(_preflight_item(item) for item in checks) - return f""" -
-
Проверка импорта
-
- {escape(status)} - {escape(source)} - {'ready' if ready else 'needs attention'} -
-
{items or '

Проверки не вернули результатов

'}
-
- """ - - -def render_html5_import_job(project_id: str, job: object | None = None) -> str: - if job is None: - return f""" -
-
Фоновый импорт
-

Фоновая задача импорта для проекта {escape(project_id)} еще не запускалась.

-
- """ - job_id = str(getattr(job, "job_id", "")) - status = _enum_text(getattr(job, "status", "unknown")) - payload = getattr(job, "payload", {}) or {} - message = str(payload.get("message") or "") - source = str(payload.get("source") or "") - stage = str(payload.get("stage") or "") - logs = payload.get("logs") if isinstance(payload.get("logs"), list) else [] - logs_html = "".join(f"
  • {escape(str(item))}
  • " for item in logs[-6:]) - return f""" -
    -
    Фоновый импорт
    -
    - {escape(status)} - {escape(source)} - {escape(stage or job_id)} -
    -

    {escape(message or "Ожидание обновления статуса")}

    - -
    - """ - - -def render_html5_setup_summary(project_id: str, setup: object) -> str: - status = _enum_text(getattr(setup, "status", "unknown")) - message = str(getattr(setup, "message", "")) - current_source = _enum_text(getattr(setup, "current_source", None) or "не выбран") - last_import = getattr(setup, "last_import", None) - history = getattr(setup, "import_history", []) or [] - return f""" -
    -
    -
    -

    Server-rendered status

    -

    {escape(status)}

    -
    - {escape(current_source)} -
    -

    {escape(message)}

    -
    - {_metric("Объекты", _import_value(last_import, "object_count"))} - {_metric("Модули", _import_value(last_import, "module_count"))} - {_metric("Формы", _import_value(last_import, "form_count"))} - {_metric("Роли", _import_value(last_import, "role_count"))} -
    -
    Последняя загрузка
    - {_last_import_block(last_import)} -
    История
    -
    - {''.join(_history_item(item) for item in history[:6]) or '

    История импорта пока пустая

    '} -
    -
    - """ - - def render_html5_status(project_id: str, snapshot: SirSnapshot) -> str: return ( f'project: {escape(project_id)}' @@ -1374,91 +1142,16 @@ def _topbar(project_id: str, project_nav: str) -> str: """ -def _setup_name(setup: object) -> str: - settings = getattr(setup, "settings", None) - return str(getattr(settings, "name", None) or getattr(setup, "project_id", "SFERA Project")) - - def _enum_text(value: object) -> str: if value is None: return "" return str(value.value if hasattr(value, "value") else value) -def _import_value(import_summary: object | None, field: str) -> int | str: - if import_summary is None: - return "0" - return getattr(import_summary, field, 0) - - def _metric(label: str, value: object) -> str: return f"
    {escape(label)}
    {escape(str(value))}
    " -def _last_import_block(import_summary: object | None) -> str: - if import_summary is None: - return '

    Загрузка еще не выполнялась

    ' - source = _enum_text(getattr(import_summary, "source", "")) - status = str(getattr(import_summary, "status", "")) - source_path = str(getattr(import_summary, "source_path", "") or "source path unavailable") - runtime = str(getattr(import_summary, "runtime_mode", "") or "runtime unavailable") - return f""" -
    - {escape(status)} - {escape(source)} · {escape(runtime)} - {escape(source_path)} -
    - """ - - -def _history_item(item: object) -> str: - source = _enum_text(getattr(item, "source", "")) - status = str(getattr(item, "status", "")) - objects = getattr(item, "object_count", 0) - modules = getattr(item, "module_count", 0) - return f""" -
    - {escape(status)} - {escape(source)} - {escape(str(objects))} objects · {escape(str(modules))} modules -
    - """ - - -def _import_source_card(source: object) -> str: - kind = _enum_text(getattr(source, "kind", "")) - title = str(getattr(source, "title", kind)) - description = str(getattr(source, "description", "")) - readiness = str(getattr(source, "readiness", "")) - return f""" -
    - {escape(title)} - {escape(kind)} - {escape(readiness or description)} -
    - """ - - -def _source_option(source: object, current_source: str) -> str: - kind = _enum_text(getattr(source, "kind", "")) - title = str(getattr(source, "title", kind)) - selected = " selected" if kind == current_source else "" - return f'' - - -def _preflight_item(item: object) -> str: - title = str(getattr(item, "title", "Check")) - status = str(getattr(item, "status", "UNKNOWN")) - message = str(getattr(item, "message", "")) - return f""" -
    - {escape(title)} - {escape(status)} - {escape(message)} -
    - """ - - def _review_summary(findings: list[dict]) -> str: severities = Counter(str(item.get("severity") or item.get("level") or "INFO") for item in findings) titles = Counter(str(item.get("title") or item.get("code") or "Finding") for item in findings) diff --git a/services/api-server/src/api_server/html5_setup.py b/services/api-server/src/api_server/html5_setup.py new file mode 100644 index 0000000..32673de --- /dev/null +++ b/services/api-server/src/api_server/html5_setup.py @@ -0,0 +1,314 @@ +from __future__ import annotations + +from html import escape +from typing import Iterable +from urllib.parse import quote + +from api_server.html5 import _enum_text, _metric, _page, _project_link, _topbar + + +def render_html5_project_setup(*, project_id: str, projects: Iterable[object], setup: object) -> str: + project_nav = "\n".join(_project_link(project, project_id) for project in projects) + name = _setup_name(setup) + sources = getattr(setup, "import_sources", []) or [] + source_cards = "".join(_import_source_card(source) for source in sources) + content = f""" +
    + {_topbar(project_id, project_nav)} +
    + +
    + {render_html5_settings_panel(project_id, setup)} + {render_html5_setup_actions(project_id, setup)} + {render_html5_setup_summary(project_id, setup)} +
    +
    +
    + """ + return _page(f"SFERA HTML5 setup - {project_id}", content) + + +def render_html5_settings_panel(project_id: str, setup: object, saved: bool = False) -> str: + settings = getattr(setup, "settings", None) + name = str(getattr(settings, "name", "") or "") + platform_version = str(getattr(settings, "platform_version", "") or "") + compatibility_mode = str(getattr(settings, "compatibility_mode", "") or "") + notice = 'Сохранено' if saved else "" + return f""" +
    +
    Базовые настройки {notice}
    +
    + + + + +
    +
    + """ + + +def render_html5_setup_actions(project_id: str, setup: object) -> str: + sources = getattr(setup, "import_sources", []) or [] + current_source = _enum_text(getattr(setup, "current_source", None) or "") + source_options = "".join(_source_option(source, current_source) for source in sources) + if not source_options: + source_options = f'' + return f""" +
    +
    + + + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + {render_html5_import_check(project_id)} + {render_html5_import_job(project_id)} + """ + + +def render_html5_import_check(project_id: str, check: object | None = None) -> str: + if check is None: + return f""" +
    +
    Проверка импорта
    +

    Запустите server-side preflight перед импортом проекта {escape(project_id)}.

    +
    + """ + status = str(getattr(check, "status", "UNKNOWN")) + source = _enum_text(getattr(check, "source", "")) + ready = bool(getattr(check, "ready", False)) + checks = getattr(check, "checks", []) or [] + items = "".join(_preflight_item(item) for item in checks) + return f""" +
    +
    Проверка импорта
    +
    + {escape(status)} + {escape(source)} + {'ready' if ready else 'needs attention'} +
    +
    {items or '

    Проверки не вернули результатов

    '}
    +
    + """ + + +def render_html5_import_job(project_id: str, job: object | None = None) -> str: + if job is None: + return f""" +
    +
    Фоновый импорт
    +

    Фоновая задача импорта для проекта {escape(project_id)} еще не запускалась.

    +
    + """ + job_id = str(getattr(job, "job_id", "")) + status = _enum_text(getattr(job, "status", "unknown")) + payload = getattr(job, "payload", {}) or {} + message = str(payload.get("message") or "") + source = str(payload.get("source") or "") + stage = str(payload.get("stage") or "") + logs = payload.get("logs") if isinstance(payload.get("logs"), list) else [] + logs_html = "".join(f"
  • {escape(str(item))}
  • " for item in logs[-6:]) + return f""" +
    +
    Фоновый импорт
    +
    + {escape(status)} + {escape(source)} + {escape(stage or job_id)} +
    +

    {escape(message or "Ожидание обновления статуса")}

    + +
    + """ + + +def render_html5_setup_summary(project_id: str, setup: object) -> str: + status = _enum_text(getattr(setup, "status", "unknown")) + message = str(getattr(setup, "message", "")) + current_source = _enum_text(getattr(setup, "current_source", None) or "не выбран") + last_import = getattr(setup, "last_import", None) + history = getattr(setup, "import_history", []) or [] + return f""" +
    +
    +
    +

    Server-rendered status

    +

    {escape(status)}

    +
    + {escape(current_source)} +
    +

    {escape(message)}

    +
    + {_metric("Объекты", _import_value(last_import, "object_count"))} + {_metric("Модули", _import_value(last_import, "module_count"))} + {_metric("Формы", _import_value(last_import, "form_count"))} + {_metric("Роли", _import_value(last_import, "role_count"))} +
    +
    Последняя загрузка
    + {_last_import_block(last_import)} +
    История
    +
    + {''.join(_history_item(item) for item in history[:6]) or '

    История импорта пока пустая

    '} +
    +
    + """ + + +def _setup_name(setup: object) -> str: + settings = getattr(setup, "settings", None) + return str(getattr(settings, "name", None) or getattr(setup, "project_id", "SFERA Project")) + + +def _import_value(import_summary: object | None, field: str) -> int | str: + if import_summary is None: + return "0" + return getattr(import_summary, field, 0) + + +def _last_import_block(import_summary: object | None) -> str: + if import_summary is None: + return '

    Загрузка еще не выполнялась

    ' + source = _enum_text(getattr(import_summary, "source", "")) + status = str(getattr(import_summary, "status", "")) + source_path = str(getattr(import_summary, "source_path", "") or "source path unavailable") + runtime = str(getattr(import_summary, "runtime_mode", "") or "runtime unavailable") + return f""" +
    + {escape(status)} + {escape(source)} · {escape(runtime)} + {escape(source_path)} +
    + """ + + +def _history_item(item: object) -> str: + source = _enum_text(getattr(item, "source", "")) + status = str(getattr(item, "status", "")) + objects = getattr(item, "object_count", 0) + modules = getattr(item, "module_count", 0) + return f""" +
    + {escape(status)} + {escape(source)} + {escape(str(objects))} objects · {escape(str(modules))} modules +
    + """ + + +def _import_source_card(source: object) -> str: + kind = _enum_text(getattr(source, "kind", "")) + title = str(getattr(source, "title", kind)) + description = str(getattr(source, "description", "")) + readiness = str(getattr(source, "readiness", "")) + return f""" +
    + {escape(title)} + {escape(kind)} + {escape(readiness or description)} +
    + """ + + +def _source_option(source: object, current_source: str) -> str: + kind = _enum_text(getattr(source, "kind", "")) + title = str(getattr(source, "title", kind)) + selected = " selected" if kind == current_source else "" + return f'' + + +def _preflight_item(item: object) -> str: + title = str(getattr(item, "title", "Check")) + status = str(getattr(item, "status", "UNKNOWN")) + message = str(getattr(item, "message", "")) + return f""" +
    + {escape(title)} + {escape(status)} + {escape(message)} +
    + """ diff --git a/services/api-server/src/api_server/main.py b/services/api-server/src/api_server/main.py index e05b574..8db6242 100644 --- a/services/api-server/src/api_server/main.py +++ b/services/api-server/src/api_server/main.py @@ -51,15 +51,10 @@ from api_server.html5 import ( render_html5_metadata_preview_result, render_html5_object_context, render_html5_object_report, - render_html5_project_setup, render_html5_project_rows, render_html5_project_report, render_html5_review, render_html5_symbol_detail, - render_html5_import_check, - render_html5_import_job, - render_html5_settings_panel, - render_html5_setup_summary, render_html5_source, render_html5_status, render_html5_symbols, @@ -70,6 +65,13 @@ from api_server.html5_operations import ( render_html5_operation_summary, render_html5_operations, ) +from api_server.html5_setup import ( + render_html5_import_check, + render_html5_import_job, + render_html5_project_setup, + render_html5_settings_panel, + render_html5_setup_summary, +) from impact_engine import object_impact, routine_impact from incremental_indexer import rebuild_changed_file from integration_topology import IntegrationKind, build_integration_topology