Create access profiles from HTML5 workspace
This commit is contained in:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user