Create access profiles from HTML5 workspace
CI / python (push) Has been cancelled
CI / rust (push) Has been cancelled

This commit is contained in:
2026-05-21 19:35:04 +03:00
parent 2f7db03001
commit 87236606d1
5 changed files with 206 additions and 2 deletions
@@ -1,6 +1,7 @@
from __future__ import annotations
from html import escape
import json
from typing import Iterable
from urllib.parse import quote
@@ -63,6 +64,7 @@ def render_html5_access_page(
{render_html5_access_profile(project_id=project_id, profile=selected)}
<div data-html5-access-plan>{plan_html}</div>
<div data-html5-access-result>{dry_run_html}</div>
{render_html5_access_profile_builder(project_id=project_id)}
</section>
<aside class="panel access-side" data-html5-access-side>
<div class="panel-title">Группы доступа</div>
@@ -76,6 +78,85 @@ def render_html5_access_page(
return _page(f"SFERA Access - {project_id}", content)
def render_html5_access_profile_builder(*, project_id: str) -> str:
return f"""
<section class="access-builder" data-html5-access-builder>
<div class="panel-title">Новый профиль доступа</div>
<form class="access-builder-form">
<label>
<span>Имя профиля</span>
<input name="name" placeholder="ПрофильHTTP" />
</label>
<label>
<span>Объекты 1С</span>
<textarea name="target_objects" placeholder="HTTPСервис.ПубличныйAPI"></textarea>
</label>
<label>
<span>Права</span>
<input name="permissions" value="read" />
</label>
<label>
<span>Пользователь-источник</span>
<input name="source_user" placeholder="ivanov" />
</label>
<div class="access-builder-actions">
<button
type="submit"
hx-post="/html5/projects/{quote(project_id)}/access/profile-preview"
hx-target="[data-html5-access-builder-result]"
hx-swap="innerHTML"
>Предпросмотр</button>
<button
type="submit"
class="primary"
hx-post="/html5/projects/{quote(project_id)}/access/profiles"
hx-target="[data-html5-access-builder-result]"
hx-swap="innerHTML"
>Сохранить черновик</button>
</div>
</form>
<div data-html5-access-builder-result>
<p class="muted padded">Профиль будет построен сервером по ролям, правам и объектам 1С.</p>
</div>
</section>
"""
def render_html5_access_profile_preview(*, draft: object) -> str:
roles = list(getattr(draft, "roles", []) or [])
missing = list(getattr(draft, "missing_objects", []) or [])
warnings = list(getattr(draft, "warnings", []) or [])
proposed = dict(getattr(draft, "proposed_profile", {}) or {})
return f"""
<section class="access-builder-result" data-html5-access-builder-result-content>
<div class="access-plan-head">
<span class="status-pill">предпросмотр</span>
<strong>{escape(str(proposed.get("qualified_name") or getattr(draft, "name", "")))}</strong>
</div>
<div class="access-role-grid">{''.join(_role_card(role) for role in roles) or '<p class="muted padded">Роли не найдены</p>'}</div>
{_notice_list("Недостающие объекты", missing)}
{_notice_list("Предупреждения", warnings)}
</section>
"""
def render_html5_access_profile_apply_result(*, project_id: str, response: object, plan: object | None = None) -> str:
profile = getattr(response, "profile", {}) or {}
profile_name = str(profile.get("name") or profile.get("qualified_name") or "")
message = str(getattr(response, "message", ""))
return f"""
<section class="access-builder-result" data-html5-access-builder-result-content>
<div class="access-plan-head">
<span class="status-pill">сохранено</span>
<strong>{escape(profile_name)}</strong>
<a class="button" href="/html5/projects/{quote(project_id)}/access?profile={quote(profile_name)}">Открыть</a>
</div>
<p class="object-summary">{escape(message)}</p>
{render_html5_access_publish_plan(project_id=project_id, profile=_DictProfile(profile), plan=plan)}
</section>
"""
def render_html5_access_profile(*, project_id: str, profile: object | None) -> str:
if profile is None:
return """
@@ -248,6 +329,15 @@ def _check_card(check: object) -> str:
return f'<article class="access-card"><strong>{escape(code)} · {escape(status)}</strong><small>{escape(message)}</small></article>'
def _notice_list(title: str, values: list[object]) -> str:
if not values:
return ""
return f"""
<div class="panel-title">{escape(title)}</div>
<ul class="access-warnings">{''.join(f'<li>{escape(str(item))}</li>' for item in values)}</ul>
"""
def _assignment_count(profiles: list[object], groups: list[object], users: list[object]) -> int:
return (
sum(len(getattr(item, "roles", []) or []) for item in profiles)
@@ -259,5 +349,13 @@ def _assignment_count(profiles: list[object], groups: list[object], users: list[
def _short_json(payload: dict) -> str:
if not payload:
return "{}"
items = [f"{key}: {value}" for key, value in list(payload.items())[:12]]
return "\n".join(items)
return json.dumps({key: value for key, value in list(payload.items())[:12]}, ensure_ascii=False, indent=2, default=str)
class _DictProfile:
def __init__(self, payload: dict):
self.name = str(payload.get("name") or "")
self.qualified_name = str(payload.get("qualified_name") or self.name)
self.roles = payload.get("roles") or []
self.attributes = payload.get("attributes") or {}
self.source = payload.get("source") or "workspace"