149 lines
6.2 KiB
Python
149 lines
6.2 KiB
Python
import html
|
||
import unicodedata
|
||
|
||
from ..config import PORTAL_PREFIX
|
||
|
||
APP_NAME = "CSBot Services Portal"
|
||
|
||
# Sekce viditelné pouze pro tento e-mail (Migration Readiness, Runtime Management, Environment).
|
||
SUPER_ADMIN_EMAIL = "jiri.uhlir59@gmail.com"
|
||
|
||
# Admin menu: (popisek, odkaz, FA ikona, super_only). super_only = jen pro SUPER_ADMIN_EMAIL.
|
||
ADMIN_MENU_ITEMS = [
|
||
("Alerting", "/portal/alerting/rules", "fa-bell", False),
|
||
("Audit", "/portal/audit", "fa-clipboard-list", False),
|
||
("Environment", "/portal/admin/environment", "fa-sliders", True),
|
||
("Incidenty", "/portal/incidents", "fa-triangle-exclamation", False),
|
||
("Nasazení", "/portal/deployments", "fa-rocket", False),
|
||
("Plánované skripty", "/portal/scheduled-scripts", "fa-calendar-days", False),
|
||
("Služby", "/portal/apps", "fa-server", False),
|
||
("Úlohy", "/portal/jobs", "fa-list-check", False),
|
||
("Users", "/portal/admin/users", "fa-users", False),
|
||
("Workery", "/portal/workers", "fa-gears", False),
|
||
("Zálohy", "/portal/backups", "fa-box-archive", False),
|
||
]
|
||
|
||
|
||
def _alpha_key(label: str) -> str:
|
||
"""Řadicí klíč bez diakritiky (Plánované < Přehled < Služby < Úlohy ...)."""
|
||
normalized = unicodedata.normalize("NFKD", label)
|
||
return "".join(ch for ch in normalized if not unicodedata.combining(ch)).lower()
|
||
|
||
|
||
def page(title: str, body: str, user=None) -> str:
|
||
nav = ""
|
||
user_panel = ""
|
||
|
||
if user:
|
||
username = html.escape(user.get("username", ""))
|
||
display_name = html.escape(user.get("display_name") or user.get("username", ""))
|
||
role = (user.get("role") or "").lower()
|
||
email = (user.get("email") or "").strip().lower()
|
||
can_operate = role in ("admin", "developer") # provozní sekce: jen dev nebo admin
|
||
is_super = email == SUPER_ADMIN_EMAIL
|
||
|
||
# Guest nemá přístup nikam – nezobrazujeme mu žádné odkazy, jen odhlášení.
|
||
is_guest = role == "guest"
|
||
|
||
# Hlavní navigace: Služby vidí každý přihlášený uživatel (kromě guesta); Úlohy, Plánované
|
||
# skripty, Alerting a Pro vývojáře jen developer nebo admin.
|
||
nav_links = ""
|
||
if not is_guest:
|
||
nav_links = '<a class="nav-link" href="/portal/apps"><i class="fa-solid fa-server" aria-hidden="true"></i> Služby</a>'
|
||
if can_operate:
|
||
nav_links += (
|
||
'<a class="nav-link" href="/portal/jobs"><i class="fa-solid fa-list-check" aria-hidden="true"></i> Úlohy</a>'
|
||
'<a class="nav-link" href="/portal/logs"><i class="fa-solid fa-file-lines" aria-hidden="true"></i> Logy</a>'
|
||
'<a class="nav-link" href="/portal/scheduled-scripts"><i class="fa-solid fa-calendar-days" aria-hidden="true"></i> Plánované skripty</a>'
|
||
'<a class="nav-link" href="/portal/alerting/rules"><i class="fa-solid fa-bell" aria-hidden="true"></i> Alerting</a>'
|
||
'<a class="nav-link" href="/portal/developers"><i class="fa-solid fa-code" aria-hidden="true"></i> Pro vývojáře</a>'
|
||
)
|
||
|
||
admin_menu = ""
|
||
if role == "admin":
|
||
visible_items = [item for item in ADMIN_MENU_ITEMS if not item[3] or is_super]
|
||
visible_items.sort(key=lambda item: _alpha_key(item[0]))
|
||
admin_links = "".join(
|
||
f'<a href="{href}"><i class="fa-solid {icon}" aria-hidden="true"></i> {html.escape(label)}</a>'
|
||
for label, href, icon, _super_only in visible_items
|
||
)
|
||
admin_menu = f"""
|
||
<details class="nav-menu">
|
||
<summary class="nav-menu-button"><i class="fa-solid fa-screwdriver-wrench" aria-hidden="true"></i> Admin</summary>
|
||
<div class="nav-menu-panel">
|
||
{admin_links}
|
||
</div>
|
||
</details>
|
||
"""
|
||
nav = f"""
|
||
<button type="button" class="nav-toggle" aria-label="Otevřít menu" onclick="togglePortalNav()"><i class="fa-solid fa-bars" aria-hidden="true"></i></button>
|
||
<div class="nav-wrap" id="portal-nav">
|
||
<nav>
|
||
{nav_links}
|
||
{admin_menu}
|
||
<div class="user-menu">
|
||
<span class="user-name">{display_name}</span>
|
||
<span class="muted">@{username}</span>
|
||
<form method="post" action="/portal/logout">
|
||
<button type="submit" class="btn-secondary"><i class="fa-solid fa-right-from-bracket" aria-hidden="true"></i> Odhlásit</button>
|
||
</form>
|
||
</div>
|
||
</nav>
|
||
</div>
|
||
"""
|
||
user_panel = ""
|
||
|
||
return f"""
|
||
<html>
|
||
<head>
|
||
<title>{APP_NAME} - {html.escape(title)}</title>
|
||
<link rel="stylesheet" href="{PORTAL_PREFIX}/static/vendor/fontawesome/css/fontawesome.min.css">
|
||
<link rel="stylesheet" href="{PORTAL_PREFIX}/static/vendor/fontawesome/css/solid.min.css">
|
||
<link rel="stylesheet" href="{PORTAL_PREFIX}/static/styles.css">
|
||
<script src="{PORTAL_PREFIX}/static/portal.js"></script>
|
||
</head>
|
||
<body>
|
||
<header>
|
||
<a class="brand" href="/portal/apps" aria-label="{APP_NAME}">
|
||
<img src="{PORTAL_PREFIX}/static/csbot-logo.svg" alt="CSBOT">
|
||
<span>{APP_NAME}</span>
|
||
</a>
|
||
{nav}
|
||
{user_panel}
|
||
</header>
|
||
<main>
|
||
{body}
|
||
</main>
|
||
</body>
|
||
</html>
|
||
"""
|
||
|
||
|
||
def render_result(title, back_url, sections, extra_link=None, extra_label=None, user=None):
|
||
rendered_sections = ""
|
||
|
||
for section_title, content in sections:
|
||
rendered_sections += f"""
|
||
<h2>{html.escape(section_title)}</h2>
|
||
<pre>{html.escape(content)}</pre>
|
||
"""
|
||
|
||
extra = ""
|
||
if extra_link and extra_label:
|
||
extra = f'<p><a class="btn" href="{extra_link}">{html.escape(extra_label)}</a></p>'
|
||
|
||
return page(
|
||
title,
|
||
f"""
|
||
<div class="card">
|
||
<h2>{html.escape(title)}</h2>
|
||
{extra}
|
||
<p><a href="{back_url}">← Zpět</a></p>
|
||
</div>
|
||
<div class="card">
|
||
{rendered_sections}
|
||
</div>
|
||
""",
|
||
user=user,
|
||
)
|