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 "Ожидание обновления статуса")}
-
{logs_html 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 "Ожидание обновления статуса")}
+
{logs_html 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