Split HTML5 setup renderer
This commit is contained in:
@@ -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"""
|
||||
<main
|
||||
class="workspace setup-workspace"
|
||||
data-html5-page="setup"
|
||||
data-project-id="{escape(project_id)}"
|
||||
hx-ext="sse"
|
||||
sse-connect="/html5/projects/{quote(project_id)}/setup/events"
|
||||
>
|
||||
{_topbar(project_id, project_nav)}
|
||||
<section class="setup-layout">
|
||||
<aside class="panel">
|
||||
<div class="panel-title">Проект</div>
|
||||
<div class="setup-card">
|
||||
<p class="eyebrow">HTML5 setup</p>
|
||||
<h1>{escape(name)}</h1>
|
||||
<p class="muted">{escape(project_id)}</p>
|
||||
<div class="setup-actions">
|
||||
<a class="button" href="/html5/projects/{quote(project_id)}/editor">Открыть IDE</a>
|
||||
<a class="button" href="/project-settings?project={quote(project_id)}">Legacy setup</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-title">Источники</div>
|
||||
<div class="source-list">{source_cards or '<p class="muted padded">Источники импорта не настроены</p>'}</div>
|
||||
</aside>
|
||||
<section class="panel setup-main">
|
||||
{render_html5_settings_panel(project_id, setup)}
|
||||
{render_html5_setup_actions(project_id, setup)}
|
||||
{render_html5_setup_summary(project_id, setup)}
|
||||
</section>
|
||||
</section>
|
||||
</main>
|
||||
"""
|
||||
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 = '<span class="saved">Сохранено</span>' if saved else ""
|
||||
return f"""
|
||||
<div class="settings-panel" data-html5-settings-panel>
|
||||
<div class="panel-title flush">Базовые настройки {notice}</div>
|
||||
<form
|
||||
class="settings-form"
|
||||
method="post"
|
||||
action="/html5/projects/{quote(project_id)}/setup/settings"
|
||||
hx-post="/html5/projects/{quote(project_id)}/setup/settings"
|
||||
hx-target="[data-html5-settings-panel]"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<label>Название<input name="name" value="{escape(name)}" /></label>
|
||||
<label>Платформа<input name="platform_version" value="{escape(platform_version)}" placeholder="8.3.24" /></label>
|
||||
<label>Совместимость<input name="compatibility_mode" value="{escape(compatibility_mode)}" placeholder="8.3.20" /></label>
|
||||
<button type="submit">Сохранить настройки</button>
|
||||
</form>
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
||||
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'<option value="{escape(current_source or "XML_DUMP")}">{escape(current_source or "XML_DUMP")}</option>'
|
||||
return f"""
|
||||
<div class="setup-actions-panel" data-html5-setup-actions>
|
||||
<form
|
||||
class="inline-form"
|
||||
method="post"
|
||||
action="/html5/projects/{quote(project_id)}/setup/source"
|
||||
hx-post="/html5/projects/{quote(project_id)}/setup/source"
|
||||
hx-target="[data-html5-setup-summary]"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<label>Источник</label>
|
||||
<select name="source">{source_options}</select>
|
||||
<button type="submit">Сохранить</button>
|
||||
</form>
|
||||
<form
|
||||
class="inline-form"
|
||||
method="post"
|
||||
action="/html5/projects/{quote(project_id)}/setup/check"
|
||||
hx-post="/html5/projects/{quote(project_id)}/setup/check"
|
||||
hx-target="[data-html5-import-check]"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<button type="submit">Проверить</button>
|
||||
</form>
|
||||
<form
|
||||
class="inline-form"
|
||||
method="post"
|
||||
action="/html5/projects/{quote(project_id)}/setup/import-job"
|
||||
hx-post="/html5/projects/{quote(project_id)}/setup/import-job"
|
||||
hx-target="[data-html5-import-job]"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<button type="submit">Импорт в фоне</button>
|
||||
</form>
|
||||
<form
|
||||
class="inline-form"
|
||||
method="post"
|
||||
action="/html5/projects/{quote(project_id)}/setup/import"
|
||||
hx-post="/html5/projects/{quote(project_id)}/setup/import"
|
||||
hx-target="[data-html5-setup-summary]"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<button type="submit">Запустить импорт</button>
|
||||
</form>
|
||||
<form
|
||||
class="inline-form"
|
||||
method="post"
|
||||
action="/html5/projects/{quote(project_id)}/setup/reindex"
|
||||
hx-post="/html5/projects/{quote(project_id)}/setup/reindex"
|
||||
hx-target="[data-html5-setup-summary]"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<button type="submit">Переиндексировать</button>
|
||||
</form>
|
||||
</div>
|
||||
{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"""
|
||||
<div class="import-check" data-html5-import-check>
|
||||
<div class="panel-title flush">Проверка импорта</div>
|
||||
<p class="muted padded">Запустите server-side preflight перед импортом проекта {escape(project_id)}.</p>
|
||||
</div>
|
||||
"""
|
||||
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"""
|
||||
<div class="import-check" data-html5-import-check>
|
||||
<div class="panel-title flush">Проверка импорта</div>
|
||||
<div class="check-head">
|
||||
<strong>{escape(status)}</strong>
|
||||
<span>{escape(source)}</span>
|
||||
<small>{'ready' if ready else 'needs attention'}</small>
|
||||
</div>
|
||||
<div class="check-list">{items or '<p class="muted padded">Проверки не вернули результатов</p>'}</div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
||||
def render_html5_import_job(project_id: str, job: object | None = None) -> str:
|
||||
if job is None:
|
||||
return f"""
|
||||
<div class="import-job" data-html5-import-job sse-swap="setup-import-job" hx-swap="outerHTML">
|
||||
<div class="panel-title flush">Фоновый импорт</div>
|
||||
<p class="muted padded">Фоновая задача импорта для проекта {escape(project_id)} еще не запускалась.</p>
|
||||
</div>
|
||||
"""
|
||||
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"<li>{escape(str(item))}</li>" for item in logs[-6:])
|
||||
return f"""
|
||||
<div
|
||||
class="import-job"
|
||||
data-html5-import-job
|
||||
hx-get="/html5/projects/{quote(project_id)}/setup/jobs/{quote(job_id)}"
|
||||
sse-swap="setup-import-job"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<div class="panel-title flush">Фоновый импорт</div>
|
||||
<div class="check-head">
|
||||
<strong>{escape(status)}</strong>
|
||||
<span>{escape(source)}</span>
|
||||
<small>{escape(stage or job_id)}</small>
|
||||
</div>
|
||||
<p class="muted padded">{escape(message or "Ожидание обновления статуса")}</p>
|
||||
<ul class="job-log">{logs_html or '<li>Лог пока пустой</li>'}</ul>
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
||||
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"""
|
||||
<div
|
||||
class="setup-summary"
|
||||
data-html5-setup-summary
|
||||
hx-get="/html5/projects/{quote(project_id)}/setup/summary"
|
||||
sse-swap="setup-summary"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<p class="eyebrow">Server-rendered status</p>
|
||||
<h2>{escape(status)}</h2>
|
||||
</div>
|
||||
<span class="status-pill">{escape(current_source)}</span>
|
||||
</div>
|
||||
<p class="lead compact-lead">{escape(message)}</p>
|
||||
<dl class="setup-metrics">
|
||||
{_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"))}
|
||||
</dl>
|
||||
<div class="panel-title flush">Последняя загрузка</div>
|
||||
{_last_import_block(last_import)}
|
||||
<div class="panel-title flush">История</div>
|
||||
<div class="history-list">
|
||||
{''.join(_history_item(item) for item in history[:6]) or '<p class="muted padded">История импорта пока пустая</p>'}
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
||||
def render_html5_status(project_id: str, snapshot: SirSnapshot) -> str:
|
||||
return (
|
||||
f'<span>project: {escape(project_id)}</span>'
|
||||
@@ -1374,91 +1142,16 @@ def _topbar(project_id: str, project_nav: str) -> str:
|
||||
</header>"""
|
||||
|
||||
|
||||
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"<div><dt>{escape(label)}</dt><dd>{escape(str(value))}</dd></div>"
|
||||
|
||||
|
||||
def _last_import_block(import_summary: object | None) -> str:
|
||||
if import_summary is None:
|
||||
return '<p class="muted padded">Загрузка еще не выполнялась</p>'
|
||||
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"""
|
||||
<div class="setup-detail" data-html5-last-import>
|
||||
<strong>{escape(status)}</strong>
|
||||
<span>{escape(source)} · {escape(runtime)}</span>
|
||||
<small>{escape(source_path)}</small>
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
||||
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"""
|
||||
<article class="history-item" data-html5-import-history>
|
||||
<strong>{escape(status)}</strong>
|
||||
<span>{escape(source)}</span>
|
||||
<small>{escape(str(objects))} objects · {escape(str(modules))} modules</small>
|
||||
</article>
|
||||
"""
|
||||
|
||||
|
||||
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"""
|
||||
<article class="source-card" data-html5-import-source="{escape(kind)}">
|
||||
<strong>{escape(title)}</strong>
|
||||
<span>{escape(kind)}</span>
|
||||
<small>{escape(readiness or description)}</small>
|
||||
</article>
|
||||
"""
|
||||
|
||||
|
||||
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'<option value="{escape(kind)}"{selected}>{escape(title)} · {escape(kind)}</option>'
|
||||
|
||||
|
||||
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"""
|
||||
<article class="check-item" data-html5-preflight-check="{escape(status)}">
|
||||
<strong>{escape(title)}</strong>
|
||||
<span>{escape(status)}</span>
|
||||
<small>{escape(message)}</small>
|
||||
</article>
|
||||
"""
|
||||
|
||||
|
||||
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)
|
||||
|
||||
@@ -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"""
|
||||
<main
|
||||
class="workspace setup-workspace"
|
||||
data-html5-page="setup"
|
||||
data-project-id="{escape(project_id)}"
|
||||
hx-ext="sse"
|
||||
sse-connect="/html5/projects/{quote(project_id)}/setup/events"
|
||||
>
|
||||
{_topbar(project_id, project_nav)}
|
||||
<section class="setup-layout">
|
||||
<aside class="panel">
|
||||
<div class="panel-title">Проект</div>
|
||||
<div class="setup-card">
|
||||
<p class="eyebrow">HTML5 setup</p>
|
||||
<h1>{escape(name)}</h1>
|
||||
<p class="muted">{escape(project_id)}</p>
|
||||
<div class="setup-actions">
|
||||
<a class="button" href="/html5/projects/{quote(project_id)}/editor">Открыть IDE</a>
|
||||
<a class="button" href="/project-settings?project={quote(project_id)}">Legacy setup</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-title">Источники</div>
|
||||
<div class="source-list">{source_cards or '<p class="muted padded">Источники импорта не настроены</p>'}</div>
|
||||
</aside>
|
||||
<section class="panel setup-main">
|
||||
{render_html5_settings_panel(project_id, setup)}
|
||||
{render_html5_setup_actions(project_id, setup)}
|
||||
{render_html5_setup_summary(project_id, setup)}
|
||||
</section>
|
||||
</section>
|
||||
</main>
|
||||
"""
|
||||
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 = '<span class="saved">Сохранено</span>' if saved else ""
|
||||
return f"""
|
||||
<div class="settings-panel" data-html5-settings-panel>
|
||||
<div class="panel-title flush">Базовые настройки {notice}</div>
|
||||
<form
|
||||
class="settings-form"
|
||||
method="post"
|
||||
action="/html5/projects/{quote(project_id)}/setup/settings"
|
||||
hx-post="/html5/projects/{quote(project_id)}/setup/settings"
|
||||
hx-target="[data-html5-settings-panel]"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<label>Название<input name="name" value="{escape(name)}" /></label>
|
||||
<label>Платформа<input name="platform_version" value="{escape(platform_version)}" placeholder="8.3.24" /></label>
|
||||
<label>Совместимость<input name="compatibility_mode" value="{escape(compatibility_mode)}" placeholder="8.3.20" /></label>
|
||||
<button type="submit">Сохранить настройки</button>
|
||||
</form>
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
||||
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'<option value="{escape(current_source or "XML_DUMP")}">{escape(current_source or "XML_DUMP")}</option>'
|
||||
return f"""
|
||||
<div class="setup-actions-panel" data-html5-setup-actions>
|
||||
<form
|
||||
class="inline-form"
|
||||
method="post"
|
||||
action="/html5/projects/{quote(project_id)}/setup/source"
|
||||
hx-post="/html5/projects/{quote(project_id)}/setup/source"
|
||||
hx-target="[data-html5-setup-summary]"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<label>Источник</label>
|
||||
<select name="source">{source_options}</select>
|
||||
<button type="submit">Сохранить</button>
|
||||
</form>
|
||||
<form
|
||||
class="inline-form"
|
||||
method="post"
|
||||
action="/html5/projects/{quote(project_id)}/setup/check"
|
||||
hx-post="/html5/projects/{quote(project_id)}/setup/check"
|
||||
hx-target="[data-html5-import-check]"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<button type="submit">Проверить</button>
|
||||
</form>
|
||||
<form
|
||||
class="inline-form"
|
||||
method="post"
|
||||
action="/html5/projects/{quote(project_id)}/setup/import-job"
|
||||
hx-post="/html5/projects/{quote(project_id)}/setup/import-job"
|
||||
hx-target="[data-html5-import-job]"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<button type="submit">Импорт в фоне</button>
|
||||
</form>
|
||||
<form
|
||||
class="inline-form"
|
||||
method="post"
|
||||
action="/html5/projects/{quote(project_id)}/setup/import"
|
||||
hx-post="/html5/projects/{quote(project_id)}/setup/import"
|
||||
hx-target="[data-html5-setup-summary]"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<button type="submit">Запустить импорт</button>
|
||||
</form>
|
||||
<form
|
||||
class="inline-form"
|
||||
method="post"
|
||||
action="/html5/projects/{quote(project_id)}/setup/reindex"
|
||||
hx-post="/html5/projects/{quote(project_id)}/setup/reindex"
|
||||
hx-target="[data-html5-setup-summary]"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<button type="submit">Переиндексировать</button>
|
||||
</form>
|
||||
</div>
|
||||
{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"""
|
||||
<div class="import-check" data-html5-import-check>
|
||||
<div class="panel-title flush">Проверка импорта</div>
|
||||
<p class="muted padded">Запустите server-side preflight перед импортом проекта {escape(project_id)}.</p>
|
||||
</div>
|
||||
"""
|
||||
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"""
|
||||
<div class="import-check" data-html5-import-check>
|
||||
<div class="panel-title flush">Проверка импорта</div>
|
||||
<div class="check-head">
|
||||
<strong>{escape(status)}</strong>
|
||||
<span>{escape(source)}</span>
|
||||
<small>{'ready' if ready else 'needs attention'}</small>
|
||||
</div>
|
||||
<div class="check-list">{items or '<p class="muted padded">Проверки не вернули результатов</p>'}</div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
||||
def render_html5_import_job(project_id: str, job: object | None = None) -> str:
|
||||
if job is None:
|
||||
return f"""
|
||||
<div class="import-job" data-html5-import-job sse-swap="setup-import-job" hx-swap="outerHTML">
|
||||
<div class="panel-title flush">Фоновый импорт</div>
|
||||
<p class="muted padded">Фоновая задача импорта для проекта {escape(project_id)} еще не запускалась.</p>
|
||||
</div>
|
||||
"""
|
||||
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"<li>{escape(str(item))}</li>" for item in logs[-6:])
|
||||
return f"""
|
||||
<div
|
||||
class="import-job"
|
||||
data-html5-import-job
|
||||
hx-get="/html5/projects/{quote(project_id)}/setup/jobs/{quote(job_id)}"
|
||||
sse-swap="setup-import-job"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<div class="panel-title flush">Фоновый импорт</div>
|
||||
<div class="check-head">
|
||||
<strong>{escape(status)}</strong>
|
||||
<span>{escape(source)}</span>
|
||||
<small>{escape(stage or job_id)}</small>
|
||||
</div>
|
||||
<p class="muted padded">{escape(message or "Ожидание обновления статуса")}</p>
|
||||
<ul class="job-log">{logs_html or '<li>Лог пока пустой</li>'}</ul>
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
||||
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"""
|
||||
<div
|
||||
class="setup-summary"
|
||||
data-html5-setup-summary
|
||||
hx-get="/html5/projects/{quote(project_id)}/setup/summary"
|
||||
sse-swap="setup-summary"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<p class="eyebrow">Server-rendered status</p>
|
||||
<h2>{escape(status)}</h2>
|
||||
</div>
|
||||
<span class="status-pill">{escape(current_source)}</span>
|
||||
</div>
|
||||
<p class="lead compact-lead">{escape(message)}</p>
|
||||
<dl class="setup-metrics">
|
||||
{_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"))}
|
||||
</dl>
|
||||
<div class="panel-title flush">Последняя загрузка</div>
|
||||
{_last_import_block(last_import)}
|
||||
<div class="panel-title flush">История</div>
|
||||
<div class="history-list">
|
||||
{''.join(_history_item(item) for item in history[:6]) or '<p class="muted padded">История импорта пока пустая</p>'}
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
||||
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 '<p class="muted padded">Загрузка еще не выполнялась</p>'
|
||||
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"""
|
||||
<div class="setup-detail" data-html5-last-import>
|
||||
<strong>{escape(status)}</strong>
|
||||
<span>{escape(source)} · {escape(runtime)}</span>
|
||||
<small>{escape(source_path)}</small>
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
||||
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"""
|
||||
<article class="history-item" data-html5-import-history>
|
||||
<strong>{escape(status)}</strong>
|
||||
<span>{escape(source)}</span>
|
||||
<small>{escape(str(objects))} objects · {escape(str(modules))} modules</small>
|
||||
</article>
|
||||
"""
|
||||
|
||||
|
||||
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"""
|
||||
<article class="source-card" data-html5-import-source="{escape(kind)}">
|
||||
<strong>{escape(title)}</strong>
|
||||
<span>{escape(kind)}</span>
|
||||
<small>{escape(readiness or description)}</small>
|
||||
</article>
|
||||
"""
|
||||
|
||||
|
||||
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'<option value="{escape(kind)}"{selected}>{escape(title)} · {escape(kind)}</option>'
|
||||
|
||||
|
||||
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"""
|
||||
<article class="check-item" data-html5-preflight-check="{escape(status)}">
|
||||
<strong>{escape(title)}</strong>
|
||||
<span>{escape(status)}</span>
|
||||
<small>{escape(message)}</small>
|
||||
</article>
|
||||
"""
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user