You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1838 lines
107 KiB
Python
1838 lines
107 KiB
Python
from html import escape
|
|
|
|
from admin_app.view.view_models import (
|
|
AdminBotMonitoringPageView,
|
|
AdminCollaboratorManagementPageView,
|
|
AdminLoginPageView,
|
|
AdminPanelHomeView,
|
|
AdminPanelMetric,
|
|
AdminPanelModuleCard,
|
|
AdminPanelNavigationItem,
|
|
AdminPanelQuickAction,
|
|
AdminPanelRoadmapItem,
|
|
AdminPanelSurfaceLink,
|
|
AdminRentalReportsPageView,
|
|
AdminSalesRevenueReportsPageView,
|
|
AdminSystemConfigurationPageView,
|
|
AdminToolIntakePageView,
|
|
AdminToolIntakeParameterTypeOption,
|
|
AdminToolReviewPageView,
|
|
AdminToolReviewWorkflowStep,
|
|
)
|
|
BOOTSTRAP_CSS_HREF = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
|
|
BOOTSTRAP_JS_HREF = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
|
|
_BADGE_CLASS_MAP = {
|
|
"success": "bg-success-subtle text-success-emphasis border border-success-subtle",
|
|
"warning": "bg-warning-subtle text-warning-emphasis border border-warning-subtle",
|
|
"info": "bg-info-subtle text-info-emphasis border border-info-subtle",
|
|
"primary": "bg-primary-subtle text-primary-emphasis border border-primary-subtle",
|
|
"secondary": "bg-secondary-subtle text-secondary-emphasis border border-secondary-subtle",
|
|
"dark": "bg-dark-subtle text-dark-emphasis border border-dark-subtle",
|
|
}
|
|
|
|
|
|
def render_panel_home(
|
|
view: AdminPanelHomeView,
|
|
*,
|
|
css_href: str,
|
|
js_href: str,
|
|
) -> str:
|
|
navigation_markup = _render_navigation(view.navigation)
|
|
quick_actions_markup = _render_quick_actions(view.quick_actions)
|
|
metrics_markup = _render_metrics(view.metrics)
|
|
modules_markup = _render_modules(view.modules)
|
|
surface_links_markup = _render_surface_links(view.surface_links)
|
|
roadmap_markup = _render_roadmap(view.roadmap)
|
|
|
|
panel_title = escape(view.panel_title)
|
|
app_name = escape(view.app_name)
|
|
panel_subtitle = escape(view.panel_subtitle)
|
|
environment = escape(view.environment)
|
|
version = escape(view.version)
|
|
api_prefix = escape(view.api_prefix)
|
|
service = escape(view.service)
|
|
release_label = escape(view.release_label)
|
|
|
|
return f"""<!DOCTYPE html>
|
|
<html lang="pt-BR" data-bs-theme="light">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>{panel_title}</title>
|
|
<meta name="description" content="{panel_subtitle}">
|
|
<link rel="stylesheet" href="{BOOTSTRAP_CSS_HREF}">
|
|
<link rel="stylesheet" href="{escape(css_href, quote=True)}">
|
|
</head>
|
|
<body class="admin-view-body">
|
|
<div class="container-xxl py-4 py-lg-5">
|
|
<div class="row g-4 align-items-start">
|
|
<aside class="col-12 col-xl-4 col-xxl-3">
|
|
<div class="card border-0 shadow-sm admin-shell-card admin-sidebar-sticky">
|
|
<div class="card-body p-4 p-lg-4">
|
|
<span class="badge rounded-pill text-bg-dark px-3 py-2">{release_label}</span>
|
|
<h1 class="display-6 fw-semibold mt-3 mb-2">{panel_title}</h1>
|
|
<p class="text-secondary mb-4">{panel_subtitle}</p>
|
|
|
|
<nav class="d-grid gap-2">
|
|
{navigation_markup}
|
|
</nav>
|
|
|
|
<div id="runtime" class="admin-runtime-block rounded-4 p-3 mt-4">
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-3">Runtime atual</p>
|
|
<div class="d-grid gap-3 small">
|
|
<div class="d-flex justify-content-between gap-3">
|
|
<span class="text-secondary">Aplicacao</span>
|
|
<strong class="text-end">{app_name}</strong>
|
|
</div>
|
|
<div class="d-flex justify-content-between gap-3">
|
|
<span class="text-secondary">Servico</span>
|
|
<strong class="text-end">{service}</strong>
|
|
</div>
|
|
<div class="d-flex justify-content-between gap-3">
|
|
<span class="text-secondary">Ambiente</span>
|
|
<strong class="text-end text-uppercase">{environment}</strong>
|
|
</div>
|
|
<div class="d-flex justify-content-between gap-3">
|
|
<span class="text-secondary">Versao</span>
|
|
<strong class="text-end">{version}</strong>
|
|
</div>
|
|
<div class="d-flex justify-content-between gap-3">
|
|
<span class="text-secondary">Prefixo API</span>
|
|
<strong class="text-end">{api_prefix}</strong>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
<section class="col-12 col-xl-8 col-xxl-9">
|
|
<div id="overview" class="card border-0 shadow-sm admin-hero-card overflow-hidden mb-4">
|
|
<div class="card-body p-4 p-lg-5">
|
|
<div class="d-flex flex-wrap gap-2 mb-3">
|
|
<span class="badge rounded-pill bg-white text-dark border">Area interna protegida</span>
|
|
<span class="badge rounded-pill bg-dark-subtle text-dark-emphasis border border-dark-subtle">Minimal Bootstrap</span>
|
|
</div>
|
|
<div class="row g-4 align-items-end">
|
|
<div class="col-lg-7">
|
|
<h2 class="display-5 fw-semibold mb-3">Dashboard do administrador</h2>
|
|
<p class="lead text-secondary mb-0">
|
|
A home protegida organiza o trabalho do time interno por fluxo, com foco no que realmente importa depois do login.
|
|
</p>
|
|
</div>
|
|
<div class="col-lg-5">
|
|
<div class="d-grid gap-2 admin-quick-actions">
|
|
{quick_actions_markup}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-3 mb-4">
|
|
{metrics_markup}
|
|
</div>
|
|
|
|
<div class="row g-4">
|
|
<div class="col-12 col-xxl-7">
|
|
<div id="modules" class="card border-0 shadow-sm admin-surface-card h-100">
|
|
<div class="card-body p-4">
|
|
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3 mb-4">
|
|
<div>
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Areas do sistema</p>
|
|
<h3 class="h2 fw-semibold mb-2">Onde o time interno opera</h3>
|
|
<p class="text-secondary mb-0">
|
|
A dashboard agora funciona como ponto de orientacao para entrar nas areas certas sem expor atalhos desnecessarios.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="row g-3">
|
|
{modules_markup}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-12 col-xxl-5">
|
|
<div id="surfaces" class="card border-0 shadow-sm admin-surface-card mb-4">
|
|
<div class="card-body p-4">
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Acessos disponiveis</p>
|
|
<h3 class="h3 fw-semibold mb-3">Entradas claras para as areas protegidas</h3>
|
|
<div class="list-group list-group-flush admin-link-list">
|
|
{surface_links_markup}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="roadmap" class="card border-0 shadow-sm admin-surface-card">
|
|
<div class="card-body p-4">
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Fluxo recomendado</p>
|
|
<h3 class="h3 fw-semibold mb-3">Como navegar no painel</h3>
|
|
<div class="vstack gap-3">
|
|
{roadmap_markup}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="{BOOTSTRAP_JS_HREF}" defer></script>
|
|
<script src="{escape(js_href, quote=True)}" defer></script>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
|
|
def render_login_page(
|
|
view: AdminLoginPageView,
|
|
*,
|
|
css_href: str,
|
|
js_href: str,
|
|
) -> str:
|
|
security_markup = _render_text_list(view.security_highlights)
|
|
notes_markup = _render_text_list(view.integration_notes)
|
|
|
|
return f"""<!DOCTYPE html>
|
|
<html lang="pt-BR" data-bs-theme="light">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>{escape(view.title)}</title>
|
|
<meta name="description" content="{escape(view.subtitle)}">
|
|
<link rel="stylesheet" href="{BOOTSTRAP_CSS_HREF}">
|
|
<link rel="stylesheet" href="{escape(css_href, quote=True)}">
|
|
</head>
|
|
<body class="admin-view-body admin-login-page">
|
|
<div class="container-xxl py-4 py-lg-5">
|
|
<div class="row justify-content-center">
|
|
<div class="col-12 col-xl-10">
|
|
<div class="row g-4 align-items-stretch">
|
|
<div class="col-12 col-lg-5">
|
|
<div class="card border-0 shadow-sm admin-login-card h-100">
|
|
<div class="card-body p-4 p-lg-5 d-flex flex-column justify-content-center">
|
|
<div class="mb-4">
|
|
<div class="d-flex flex-wrap gap-2 mb-3">
|
|
<span class="badge rounded-pill text-bg-dark">Acesso restrito</span>
|
|
<span class="badge rounded-pill bg-white text-dark border">{escape(view.app_name)}</span>
|
|
</div>
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Entrada do painel</p>
|
|
<h1 class="display-6 fw-semibold mb-3">{escape(view.title)}</h1>
|
|
<p class="text-secondary mb-0">{escape(view.subtitle)}</p>
|
|
</div>
|
|
|
|
<form class="admin-login-form" data-admin-login-form="true" data-auth-endpoint="{escape(view.auth_endpoint, quote=True)}" data-dashboard-href="{escape(view.dashboard_href, quote=True)}">
|
|
<div class="mb-3">
|
|
<label class="form-label fw-semibold" for="admin-login-email">Email administrativo</label>
|
|
<input class="form-control form-control-lg rounded-4" id="admin-login-email" name="email" type="email" placeholder="{escape(view.email_placeholder, quote=True)}" autocomplete="username" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label fw-semibold" for="admin-login-password">Senha</label>
|
|
<input class="form-control form-control-lg rounded-4" id="admin-login-password" name="password" type="password" placeholder="{escape(view.password_placeholder, quote=True)}" autocomplete="current-password" required>
|
|
</div>
|
|
<div class="small text-secondary rounded-4 admin-login-policy mb-4">
|
|
<strong>Politica atual:</strong> {escape(view.password_policy_label)}
|
|
</div>
|
|
<button class="btn btn-dark btn-lg rounded-pill w-100 d-inline-flex align-items-center justify-content-center gap-2" type="submit">
|
|
<span data-submit-label>Entrar no painel</span>
|
|
<span class="spinner-border spinner-border-sm d-none" data-submit-spinner aria-hidden="true"></span>
|
|
</button>
|
|
</form>
|
|
|
|
<div class="alert d-none mt-4 mb-0 rounded-4" id="admin-login-feedback" role="status"></div>
|
|
|
|
<p class="small text-secondary mt-4 mb-0">
|
|
O restante do sistema administrativo so fica disponivel depois da autenticacao do StaffAccount.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-12 col-lg-7">
|
|
<div class="card border-0 shadow-sm admin-login-info-card h-100">
|
|
<div class="card-body p-4 p-lg-5 d-flex flex-column gap-4">
|
|
<div>
|
|
<div class="d-flex flex-wrap gap-2 mb-3">
|
|
<span class="badge rounded-pill bg-body-tertiary text-secondary border">Ambiente {escape(view.environment)}</span>
|
|
<span class="badge rounded-pill bg-body-tertiary text-secondary border">Versao {escape(view.version)}</span>
|
|
</div>
|
|
<h2 class="display-6 fw-semibold mb-3">Depois do login, o painel organiza o fluxo por voce</h2>
|
|
<p class="lead text-secondary mb-0">
|
|
Primeiro vem a dashboard administrativa protegida. A partir dela, o time acessa revisao, governanca e acompanhamento do ambiente sem atalhos confusos antes da autenticacao.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="row g-3">
|
|
<div class="col-12 col-md-4">
|
|
<div class="admin-login-kpi rounded-4 p-3 h-100">
|
|
<p class="small text-uppercase fw-semibold text-secondary mb-2">Access token</p>
|
|
<div class="h4 fw-semibold mb-1">{escape(view.access_token_ttl_label)}</div>
|
|
<p class="small text-secondary mb-0">Janela curta para a sessao ativa.</p>
|
|
</div>
|
|
</div>
|
|
<div class="col-12 col-md-4">
|
|
<div class="admin-login-kpi rounded-4 p-3 h-100">
|
|
<p class="small text-uppercase fw-semibold text-secondary mb-2">Refresh token</p>
|
|
<div class="h4 fw-semibold mb-1">{escape(view.refresh_token_ttl_label)}</div>
|
|
<p class="small text-secondary mb-0">Continuidade controlada da sessao web.</p>
|
|
</div>
|
|
</div>
|
|
<div class="col-12 col-md-4">
|
|
<div class="admin-login-kpi rounded-4 p-3 h-100">
|
|
<p class="small text-uppercase fw-semibold text-secondary mb-2">Acesso</p>
|
|
<div class="h4 fw-semibold mb-1">Protegido</div>
|
|
<p class="small text-secondary mb-0">Liberado apenas apos autenticacao.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-4 mt-0">
|
|
<div class="col-12 col-xl-6">
|
|
<div class="admin-login-note rounded-4 p-4 h-100">
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">O que fica liberado</p>
|
|
<h3 class="h4 fw-semibold mb-3">Fluxo apos o login</h3>
|
|
<ul class="small text-secondary ps-3 mb-0">
|
|
{notes_markup}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div class="col-12 col-xl-6">
|
|
<div class="admin-login-note rounded-4 p-4 h-100">
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Base de seguranca</p>
|
|
<h3 class="h4 fw-semibold mb-3">Como a entrada esta protegida</h3>
|
|
<ul class="small text-secondary ps-3 mb-0">
|
|
{security_markup}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="{BOOTSTRAP_JS_HREF}" defer></script>
|
|
<script src="{escape(js_href, quote=True)}" defer></script>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
|
|
def _render_navigation(items: tuple[AdminPanelNavigationItem, ...]) -> str:
|
|
links: list[str] = []
|
|
for item in items:
|
|
badge_markup = ""
|
|
if item.badge:
|
|
badge_markup = (
|
|
f'<span class="badge rounded-pill bg-body-tertiary text-secondary border">{escape(item.badge)}</span>'
|
|
)
|
|
|
|
active_class = " active shadow-sm" if item.is_active else ""
|
|
links.append(
|
|
f"""
|
|
<a class="btn btn-link text-decoration-none text-start rounded-4 px-3 py-3 admin-nav-link{active_class}" href="{escape(item.href, quote=True)}">
|
|
<div class="d-flex justify-content-between align-items-start gap-3">
|
|
<div>
|
|
<div class="fw-semibold text-dark">{escape(item.label)}</div>
|
|
<div class="small text-secondary mt-1">{escape(item.description)}</div>
|
|
</div>
|
|
{badge_markup}
|
|
</div>
|
|
</a>
|
|
""".strip()
|
|
)
|
|
return "\n".join(links)
|
|
|
|
|
|
def _render_quick_actions(items: tuple[AdminPanelQuickAction, ...]) -> str:
|
|
return "\n".join(
|
|
f'<a class="btn {escape(item.button_class)} btn-lg rounded-pill px-4" href="{escape(item.href, quote=True)}">{escape(item.label)}</a>'
|
|
for item in items
|
|
)
|
|
|
|
|
|
def _render_metrics(items: tuple[AdminPanelMetric, ...]) -> str:
|
|
cards: list[str] = []
|
|
for item in items:
|
|
cards.append(
|
|
f"""
|
|
<div class="col-12 col-md-6 col-xxl-3">
|
|
<div class="card border-0 shadow-sm h-100 admin-metric-card">
|
|
<div class="card-body p-4">
|
|
<p class="small text-uppercase fw-semibold text-secondary mb-3">{escape(item.label)}</p>
|
|
<div class="display-6 fw-semibold mb-2">{escape(item.value)}</div>
|
|
<p class="text-secondary mb-0">{escape(item.description)}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
""".strip()
|
|
)
|
|
return "\n".join(cards)
|
|
|
|
|
|
def _render_modules(items: tuple[AdminPanelModuleCard, ...]) -> str:
|
|
cards: list[str] = []
|
|
for item in items:
|
|
highlights = "".join(
|
|
f'<li class="mb-2">{escape(highlight)}</li>'
|
|
for highlight in item.highlights
|
|
)
|
|
cta_markup = '<span class="small fw-semibold text-secondary">Em preparacao</span>'
|
|
if item.href and item.cta_label and item.is_available:
|
|
cta_markup = (
|
|
f'<a class="btn btn-sm btn-outline-dark rounded-pill px-3" '
|
|
f'href="{escape(item.href, quote=True)}">{escape(item.cta_label)}</a>'
|
|
)
|
|
|
|
badge_classes = _BADGE_CLASS_MAP.get(item.status_variant, _BADGE_CLASS_MAP["secondary"])
|
|
cards.append(
|
|
f"""
|
|
<div class="col-12 col-md-6">
|
|
<article class="card border-0 h-100 admin-module-card">
|
|
<div class="card-body p-4 d-flex flex-column gap-3">
|
|
<div class="d-flex flex-wrap justify-content-between gap-3 align-items-start">
|
|
<div>
|
|
<p class="small text-uppercase fw-semibold text-secondary mb-2">{escape(item.eyebrow)}</p>
|
|
<h4 class="h5 fw-semibold mb-0">{escape(item.title)}</h4>
|
|
</div>
|
|
<span class="badge rounded-pill {badge_classes}">{escape(item.status_label)}</span>
|
|
</div>
|
|
<p class="text-secondary mb-0">{escape(item.description)}</p>
|
|
<ul class="small text-secondary ps-3 mb-0">
|
|
{highlights}
|
|
</ul>
|
|
<div class="pt-1 mt-auto">
|
|
{cta_markup}
|
|
</div>
|
|
</div>
|
|
</article>
|
|
</div>
|
|
""".strip()
|
|
)
|
|
return "\n".join(cards)
|
|
|
|
|
|
def _render_surface_links(items: tuple[AdminPanelSurfaceLink, ...]) -> str:
|
|
cards: list[str] = []
|
|
for item in items:
|
|
cards.append(
|
|
f"""
|
|
<a class="list-group-item list-group-item-action border-0 rounded-4 px-0 admin-surface-link" href="{escape(item.href, quote=True)}">
|
|
<div class="d-flex justify-content-between align-items-start gap-3">
|
|
<div>
|
|
<div class="small text-uppercase fw-semibold text-secondary mb-2">{escape(item.method)}</div>
|
|
<div class="fw-semibold text-dark">{escape(item.label)}</div>
|
|
<div class="small text-secondary mt-1">{escape(item.description)}</div>
|
|
</div>
|
|
<span class="badge rounded-pill bg-body-tertiary text-secondary border">Abrir</span>
|
|
</div>
|
|
</a>
|
|
""".strip()
|
|
)
|
|
return "\n".join(cards)
|
|
|
|
|
|
def _render_roadmap(items: tuple[AdminPanelRoadmapItem, ...]) -> str:
|
|
cards: list[str] = []
|
|
for item in items:
|
|
cards.append(
|
|
f"""
|
|
<div class="admin-roadmap-item rounded-4 p-3">
|
|
<div class="d-flex justify-content-between align-items-start gap-3">
|
|
<div>
|
|
<div class="small text-uppercase fw-semibold text-secondary mb-2">Etapa {escape(item.step)}</div>
|
|
<div class="fw-semibold text-dark">{escape(item.title)}</div>
|
|
<div class="small text-secondary mt-1">{escape(item.description)}</div>
|
|
</div>
|
|
<span class="badge rounded-pill bg-body-tertiary text-secondary border">{escape(item.status_label)}</span>
|
|
</div>
|
|
</div>
|
|
""".strip()
|
|
)
|
|
return "\n".join(cards)
|
|
|
|
|
|
def _render_text_list(items: tuple[str, ...]) -> str:
|
|
return "\n".join(
|
|
f"<li class=\"mb-2\">{escape(item)}</li>"
|
|
for item in items
|
|
)
|
|
|
|
|
|
|
|
def render_tool_review_page(
|
|
view: AdminToolReviewPageView,
|
|
*,
|
|
css_href: str,
|
|
js_href: str,
|
|
) -> str:
|
|
workflow_markup = _render_tool_review_workflow(view.workflow)
|
|
review_notes_markup = _render_text_list(view.review_notes)
|
|
approval_notes_markup = _render_text_list(view.approval_notes)
|
|
activation_notes_markup = _render_text_list(view.activation_notes)
|
|
|
|
return f"""<!DOCTYPE html>
|
|
<html lang="pt-BR" data-bs-theme="light">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>{escape(view.title)}</title>
|
|
<meta name="description" content="{escape(view.subtitle)}">
|
|
<link rel="stylesheet" href="{BOOTSTRAP_CSS_HREF}">
|
|
<link rel="stylesheet" href="{escape(css_href, quote=True)}">
|
|
</head>
|
|
<body class="admin-view-body admin-tool-review-page">
|
|
<div class="container-xxl py-4 py-lg-5" data-admin-tool-review-board="true" data-overview-endpoint="{escape(view.overview_endpoint, quote=True)}" data-contracts-endpoint="{escape(view.contracts_endpoint, quote=True)}" data-review-queue-endpoint="{escape(view.review_queue_endpoint, quote=True)}" data-publications-endpoint="{escape(view.publications_endpoint, quote=True)}">
|
|
<div class="row g-4 align-items-start">
|
|
<aside class="col-12 col-xl-4 col-xxl-3">
|
|
<div class="card border-0 shadow-sm admin-shell-card admin-sidebar-sticky">
|
|
<div class="card-body p-4">
|
|
<div class="d-flex flex-wrap gap-2 mb-3">
|
|
<span class="badge rounded-pill text-bg-dark">Review hub</span>
|
|
<span class="badge rounded-pill bg-body-tertiary text-secondary border">Bootstrap UI</span>
|
|
</div>
|
|
<h1 class="display-6 fw-semibold mb-3">{escape(view.title)}</h1>
|
|
<p class="text-secondary mb-4">{escape(view.subtitle)}</p>
|
|
|
|
<div class="d-grid gap-2 mb-4">
|
|
<a class="btn btn-dark rounded-pill" href="{escape(view.dashboard_href, quote=True)}">Voltar ao dashboard</a>
|
|
<span class="badge rounded-pill bg-body-tertiary text-secondary border px-3 py-2">Acesso ja autenticado</span>
|
|
</div>
|
|
|
|
<div class="admin-runtime-block p-3 mb-3">
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-3">Resumo do workspace</p>
|
|
<div class="d-grid gap-3 small">
|
|
<div class="d-flex justify-content-between gap-3">
|
|
<span class="text-secondary">Aplicacao</span>
|
|
<strong class="text-end">{escape(view.app_name)}</strong>
|
|
</div>
|
|
<div class="d-flex justify-content-between gap-3">
|
|
<span class="text-secondary">Ambiente</span>
|
|
<strong class="text-end text-uppercase">{escape(view.environment)}</strong>
|
|
</div>
|
|
<div class="d-flex justify-content-between gap-3">
|
|
<span class="text-secondary">Versao</span>
|
|
<strong class="text-end">{escape(view.version)}</strong>
|
|
</div>
|
|
<div class="d-flex justify-content-between gap-3">
|
|
<span class="text-secondary">Atualizacao</span>
|
|
<strong class="text-end" data-tool-review-last-sync>Pendente</strong>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="admin-tool-review-note p-4 mb-3">
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Revisao humana</p>
|
|
<h2 class="h5 fw-semibold mb-3">O que observar na fila</h2>
|
|
<ul class="small text-secondary ps-3 mb-0">
|
|
{review_notes_markup}
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="admin-tool-review-note p-4">
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Ativacao controlada</p>
|
|
<h2 class="h5 fw-semibold mb-3">Como fechar a publicacao</h2>
|
|
<ul class="small text-secondary ps-3 mb-0">
|
|
{activation_notes_markup}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
<section class="col-12 col-xl-8 col-xxl-9">
|
|
<div class="card border-0 shadow-sm admin-hero-card overflow-hidden mb-4">
|
|
<div class="card-body p-4 p-lg-5">
|
|
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3">
|
|
<div>
|
|
<div class="d-flex flex-wrap gap-2 mb-3">
|
|
<span class="badge rounded-pill bg-white text-dark border">Governanca de tools</span>
|
|
<span class="badge rounded-pill bg-success-subtle text-success-emphasis border border-success-subtle">Revisao e ativacao</span>
|
|
</div>
|
|
<h2 class="display-5 fw-semibold mb-3">Fluxo governado de proposta, codigo e ativacao</h2>
|
|
<p class="lead text-secondary mb-0">
|
|
Esta tela conecta a sessao web do painel a cada etapa da proposta: triagem para gerar, iteracoes de codigo, decisao humana e catalogo ativo do produto.
|
|
</p>
|
|
</div>
|
|
<div class="d-grid gap-2 admin-quick-actions">
|
|
<button class="btn btn-dark btn-lg rounded-pill px-4" type="button" data-admin-tool-refresh>
|
|
<span data-tool-refresh-label>Atualizar leitura</span>
|
|
<span class="spinner-border spinner-border-sm d-none" data-tool-refresh-spinner aria-hidden="true"></span>
|
|
</button>
|
|
<a class="btn btn-outline-dark btn-lg rounded-pill px-4" href="{escape(view.dashboard_href, quote=True)}">Voltar ao overview</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="alert d-none rounded-4 mb-4" id="admin-tool-review-feedback" role="status"></div>
|
|
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-12 col-md-4">
|
|
<div class="card border-0 shadow-sm admin-metric-card h-100">
|
|
<div class="card-body p-4">
|
|
<p class="small text-uppercase fw-semibold text-secondary mb-3">Fila de revisao</p>
|
|
<div class="display-6 fw-semibold mb-2" data-tool-review-queue-count>0</div>
|
|
<p class="text-secondary mb-0">Propostas aguardando triagem, leitura tecnica ou aprovacao humana.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-12 col-md-4">
|
|
<div class="card border-0 shadow-sm admin-metric-card h-100">
|
|
<div class="card-body p-4">
|
|
<p class="small text-uppercase fw-semibold text-secondary mb-3">Publicacoes ativas</p>
|
|
<div class="display-6 fw-semibold mb-2" data-tool-review-publication-count>0</div>
|
|
<p class="text-secondary mb-0">Catalogo publicado e pronto para abastecer o runtime de produto.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-12 col-md-4">
|
|
<div class="card border-0 shadow-sm admin-metric-card h-100">
|
|
<div class="card-body p-4">
|
|
<p class="small text-uppercase fw-semibold text-secondary mb-3">Etapas do contrato</p>
|
|
<div class="display-6 fw-semibold mb-2" data-tool-review-lifecycle-count>{len(view.workflow)}</div>
|
|
<p class="text-secondary mb-0">Workflow compartilhado entre revisao, aprovacao e ativacao.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card border-0 shadow-sm admin-surface-card mb-4">
|
|
<div class="card-body p-4">
|
|
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3 mb-4">
|
|
<div>
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Pipeline visual</p>
|
|
<h3 class="h3 fw-semibold mb-2">Etapas que a tela acompanha</h3>
|
|
<p class="text-secondary mb-0">Os cards abaixo resumem o trajeto da proposta, da triagem para geracao e das iteracoes de codigo ate a ativacao no produto.</p>
|
|
</div>
|
|
</div>
|
|
<div class="row g-3">
|
|
{workflow_markup}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-4">
|
|
<div class="col-12 col-xxl-7">
|
|
<div class="card border-0 shadow-sm admin-surface-card h-100">
|
|
<div class="card-body p-4">
|
|
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3 mb-3">
|
|
<div>
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Fila atual</p>
|
|
<h3 class="h3 fw-semibold mb-2">Triagem e revisao por etapa</h3>
|
|
<p class="text-secondary mb-0">A fila abaixo mostra em que gate cada proposta esta e qual decisao humana ou tecnica vem a seguir.</p>
|
|
</div>
|
|
<span class="badge rounded-pill bg-body-tertiary text-secondary border" data-tool-review-queue-mode>Bootstrap</span>
|
|
</div>
|
|
<div class="admin-tool-review-grid" data-tool-review-queue-list>
|
|
<div class="admin-tool-empty-state rounded-4 p-4">
|
|
<h4 class="h5 fw-semibold mb-2">Nenhum item carregado ainda</h4>
|
|
<p class="text-secondary mb-0">Clique em atualizar leitura para sincronizar a fila de revisao do painel.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-12 col-xxl-5">
|
|
<div class="card border-0 shadow-sm admin-surface-card h-100">
|
|
<div class="card-body p-4 d-flex flex-column gap-4">
|
|
<div>
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Checklist de aprovacao</p>
|
|
<h3 class="h3 fw-semibold mb-2">Playbook para a decisao humana</h3>
|
|
<p class="text-secondary mb-0">Triagem, leitura do codigo, aprovacao e ativacao continuam controladas pelo papel administrativo e pelo contrato compartilhado.</p>
|
|
</div>
|
|
|
|
<div class="admin-tool-review-note p-4">
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Antes de aprovar</p>
|
|
<ul class="small text-secondary ps-3 mb-0">
|
|
{approval_notes_markup}
|
|
</ul>
|
|
</div>
|
|
|
|
<div>
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Lifecycle disponivel</p>
|
|
<div class="vstack gap-2" data-tool-contract-lifecycle>
|
|
<div class="small text-secondary">Aguardando leitura do contrato compartilhado...</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Tipos de parametro</p>
|
|
<div class="d-flex flex-wrap gap-2" data-tool-parameter-types>
|
|
<span class="badge rounded-pill bg-body-tertiary text-secondary border">Aguardando</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card border-0 shadow-sm admin-surface-card mt-4 mb-4">
|
|
<div class="card-body p-4">
|
|
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3 mb-3">
|
|
<div>
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Detalhe da versao</p>
|
|
<h3 class="h3 fw-semibold mb-2">Revisao humana antes da ativacao</h3>
|
|
<p class="text-secondary mb-0">Selecione uma proposta da fila para triar a geracao, inspecionar a iteracao atual do codigo e registrar a decisao da diretoria.</p>
|
|
</div>
|
|
<span class="badge rounded-pill bg-body-tertiary text-secondary border" data-tool-review-detail-status>Nenhum item</span>
|
|
</div>
|
|
<div class="row g-4">
|
|
<div class="col-12 col-xxl-5">
|
|
<div class="d-flex flex-column gap-3">
|
|
<div class="admin-tool-review-note p-4" data-tool-review-detail-summary>
|
|
<div class="fw-semibold mb-2" data-tool-review-detail-title>Selecione um item da fila</div>
|
|
<p class="text-secondary mb-0">O detalhe da versao aparece aqui junto com o resumo funcional e o gate humano atual.</p>
|
|
</div>
|
|
|
|
<div>
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Contexto e parametros</p>
|
|
<div class="vstack gap-2" data-tool-review-detail-meta>
|
|
<div class="admin-tool-inline-note rounded-4 p-3 small text-secondary">Nenhuma versao selecionada.</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Validacoes automaticas</p>
|
|
<div class="vstack gap-2" data-tool-review-validation-list>
|
|
<div class="admin-tool-inline-note rounded-4 p-3 small text-secondary">As validacoes da pipeline aparecem aqui.</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Historico da diretoria</p>
|
|
<div class="vstack gap-2" data-tool-review-history-list>
|
|
<div class="admin-tool-inline-note rounded-4 p-3 small text-secondary">Nenhuma decisao humana registrada ainda.</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Proximos passos</p>
|
|
<div class="vstack gap-2" data-tool-review-next-steps>
|
|
<div class="admin-tool-inline-note rounded-4 p-3 small text-secondary">Os proximos passos da versao aparecem aqui.</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-12 col-xxl-7">
|
|
<div class="d-flex flex-column gap-3 h-100">
|
|
<div>
|
|
<label class="form-label fw-semibold" for="admin-tool-review-generated-code">Codigo completo da iteracao atual</label>
|
|
<textarea class="form-control rounded-4 font-monospace" id="admin-tool-review-generated-code" rows="22" readonly data-tool-review-code>O codigo gerado pela pipeline aparecera aqui assim que uma versao for selecionada.</textarea>
|
|
<div class="form-text">Use este campo para revisar a implementacao completa da iteracao atual antes de solicitar ajustes, aprovar ou ativar a tool.</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="form-label fw-semibold" for="admin-tool-review-decision-notes">Parecer da diretoria</label>
|
|
<textarea class="form-control rounded-4" id="admin-tool-review-decision-notes" rows="5" placeholder="Registre o racional da triagem, da revisao, do pedido de ajustes ou da aprovacao humana." data-tool-review-decision-notes></textarea>
|
|
<div class="form-text" data-tool-review-decision-hint>As notas da decisao ficam persistidas na trilha administrativa da versao.</div>
|
|
</div>
|
|
|
|
<div class="form-check admin-tool-inline-note rounded-4 p-3">
|
|
<input class="form-check-input" type="checkbox" value="1" id="admin-tool-review-code-check" data-tool-review-reviewed-code>
|
|
<label class="form-check-label small text-secondary" for="admin-tool-review-code-check">
|
|
Confirmo que revisei o codigo completo gerado antes de validar esta versao.
|
|
</label>
|
|
</div>
|
|
|
|
<div class="d-flex flex-wrap gap-2" data-tool-review-actions>
|
|
<button class="btn btn-outline-primary rounded-pill" type="button" data-tool-review-action="authorize-generation" disabled>Autorizar geracao</button>
|
|
<button class="btn btn-outline-info rounded-pill" type="button" data-tool-review-action="run-pipeline" disabled>Executar pipeline</button>
|
|
<button class="btn btn-outline-dark rounded-pill" type="button" data-tool-review-action="review" disabled>Validar codigo</button>
|
|
<button class="btn btn-outline-warning rounded-pill" type="button" data-tool-review-action="request-changes" disabled>Solicitar ajustes e refatorar</button>
|
|
<button class="btn btn-dark rounded-pill" type="button" data-tool-review-action="approve" disabled>Aprovar para ativacao</button>
|
|
<button class="btn btn-success rounded-pill" type="button" data-tool-review-action="publish" disabled>Ativar no catalogo</button>
|
|
<button class="btn btn-outline-secondary rounded-pill" type="button" data-tool-review-action="close" disabled>Encerrar proposta</button>
|
|
<button class="btn btn-outline-danger rounded-pill" type="button" data-tool-review-action="deactivate" disabled>Desativar versao</button>
|
|
<button class="btn btn-warning rounded-pill" type="button" data-tool-review-action="rollback" disabled>Executar rollback</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card border-0 shadow-sm admin-surface-card mt-4">
|
|
<div class="card-body p-4">
|
|
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3 mb-3">
|
|
<div>
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Catalogo ativo</p>
|
|
<h3 class="h3 fw-semibold mb-2">Ativacao e superficie publicada</h3>
|
|
<p class="text-secondary mb-0">Quando a sessao tem permissao de publicacao, o painel tambem exibe o catalogo conhecido de tools ativas.</p>
|
|
</div>
|
|
<span class="badge rounded-pill bg-body-tertiary text-secondary border" data-tool-publication-source>Catalogo</span>
|
|
</div>
|
|
<div class="row g-3" data-tool-publication-list>
|
|
<div class="col-12">
|
|
<div class="admin-tool-empty-state rounded-4 p-4">
|
|
<h4 class="h5 fw-semibold mb-2">Catalogo ainda nao sincronizado</h4>
|
|
<p class="text-secondary mb-0">A leitura da ativacao aparece aqui assim que a sessao web carregar as publicacoes disponiveis.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="{BOOTSTRAP_JS_HREF}" defer></script>
|
|
<script src="{escape(js_href, quote=True)}" defer></script>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
|
|
def _render_tool_review_workflow(items: tuple[AdminToolReviewWorkflowStep, ...]) -> str:
|
|
cards: list[str] = []
|
|
for item in items:
|
|
badge_classes = _BADGE_CLASS_MAP.get(item.status_variant, _BADGE_CLASS_MAP["secondary"])
|
|
cards.append(
|
|
f"""
|
|
<div class="col-12 col-md-6 col-xxl-3">
|
|
<article class="admin-tool-workflow-card p-4 h-100">
|
|
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3 mb-3">
|
|
<div>
|
|
<p class="small text-uppercase fw-semibold text-secondary mb-2">{escape(item.eyebrow)}</p>
|
|
<h4 class="h5 fw-semibold mb-0">{escape(item.title)}</h4>
|
|
</div>
|
|
<span class="badge rounded-pill {badge_classes}">{escape(item.status_label)}</span>
|
|
</div>
|
|
<p class="text-secondary mb-0">{escape(item.description)}</p>
|
|
</article>
|
|
</div>
|
|
""".strip()
|
|
)
|
|
return "\n".join(cards)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def render_tool_intake_page(
|
|
view: AdminToolIntakePageView,
|
|
*,
|
|
css_href: str,
|
|
js_href: str,
|
|
) -> str:
|
|
naming_rules_markup = _render_text_list(view.naming_rules)
|
|
submission_notes_markup = _render_text_list(view.submission_notes)
|
|
approval_notes_markup = _render_text_list(view.approval_notes)
|
|
domain_cards_markup = "\n".join(
|
|
f'''<div class="col-12 col-md-6"><article class="admin-tool-inline-note rounded-4 p-3 h-100"><div class="fw-semibold mb-2">{escape(item.label)}</div><div class="small text-secondary">{escape(item.description)}</div></article></div>'''
|
|
for item in view.domain_options
|
|
)
|
|
parameter_type_badges_markup = "\n".join(
|
|
f'''<span class="badge rounded-pill bg-body-tertiary text-secondary border">{escape(item.label)}</span>'''
|
|
for item in view.parameter_type_options
|
|
)
|
|
domain_select_options = "\n".join(
|
|
f'''<option value="{escape(item.value, quote=True)}">{escape(item.label)}</option>'''
|
|
for item in view.domain_options
|
|
)
|
|
parameter_select_options = _render_tool_parameter_options(view.parameter_type_options)
|
|
|
|
return f'''<!DOCTYPE html>
|
|
<html lang="pt-BR" data-bs-theme="light">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>{escape(view.title)}</title>
|
|
<meta name="description" content="{escape(view.subtitle)}">
|
|
<link rel="stylesheet" href="{BOOTSTRAP_CSS_HREF}">
|
|
<link rel="stylesheet" href="{escape(css_href, quote=True)}">
|
|
</head>
|
|
<body class="admin-view-body admin-tool-intake-page">
|
|
<div class="container-xxl py-4 py-lg-5" data-admin-tool-intake="true" data-intake-endpoint="{escape(view.intake_endpoint, quote=True)}">
|
|
<div class="row g-4 align-items-start">
|
|
<aside class="col-12 col-xl-4 col-xxl-3">
|
|
<div class="card border-0 shadow-sm admin-shell-card admin-sidebar-sticky">
|
|
<div class="card-body p-4">
|
|
<div class="d-flex flex-wrap gap-2 mb-3">
|
|
<span class="badge rounded-pill text-bg-dark">Cadastro guiado</span>
|
|
<span class="badge rounded-pill bg-body-tertiary text-secondary border">Draft real</span>
|
|
</div>
|
|
<h1 class="display-6 fw-semibold mb-3">{escape(view.title)}</h1>
|
|
<p class="text-secondary mb-4">{escape(view.subtitle)}</p>
|
|
|
|
<div class="d-grid gap-2 mb-4 admin-quick-actions">
|
|
<a class="btn btn-dark rounded-pill" href="{escape(view.dashboard_href, quote=True)}">Voltar ao dashboard</a>
|
|
<a class="btn btn-outline-dark rounded-pill" href="{escape(view.review_href, quote=True)}">Abrir revisao</a>
|
|
</div>
|
|
|
|
<div class="admin-runtime-block p-3 mb-3">
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-3">Resumo do runtime</p>
|
|
<div class="d-grid gap-3 small">
|
|
<div class="d-flex justify-content-between gap-3">
|
|
<span class="text-secondary">Aplicacao</span>
|
|
<strong class="text-end">{escape(view.app_name)}</strong>
|
|
</div>
|
|
<div class="d-flex justify-content-between gap-3">
|
|
<span class="text-secondary">Ambiente</span>
|
|
<strong class="text-end text-uppercase">{escape(view.environment)}</strong>
|
|
</div>
|
|
<div class="d-flex justify-content-between gap-3">
|
|
<span class="text-secondary">Versao</span>
|
|
<strong class="text-end">{escape(view.version)}</strong>
|
|
</div>
|
|
<div class="d-flex justify-content-between gap-3">
|
|
<span class="text-secondary">Fluxo</span>
|
|
<strong class="text-end">Colaborador -> Diretor</strong>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="admin-tool-review-note p-4 mb-3">
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Regras de nomeacao</p>
|
|
<ul class="small text-secondary ps-3 mb-0">
|
|
{naming_rules_markup}
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="admin-tool-review-note p-4">
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Como a aprovacao funciona</p>
|
|
<ul class="small text-secondary ps-3 mb-0">
|
|
{approval_notes_markup}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
<section class="col-12 col-xl-8 col-xxl-9">
|
|
<div class="card border-0 shadow-sm admin-hero-card overflow-hidden mb-4">
|
|
<div class="card-body p-4 p-lg-5">
|
|
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3">
|
|
<div>
|
|
<div class="d-flex flex-wrap gap-2 mb-3">
|
|
<span class="badge rounded-pill bg-white text-dark border">Nova tool</span>
|
|
<span class="badge rounded-pill bg-info-subtle text-info-emphasis border border-info-subtle">Preview validado</span>
|
|
</div>
|
|
<h2 class="display-5 fw-semibold mb-3">Cadastrar uma nova tool com contexto operacional</h2>
|
|
<p class="lead text-secondary mb-0">
|
|
A tela abaixo transforma o cadastro em um pre-draft validado, pronto para seguir ao fluxo de revisao humana antes de qualquer publicacao no produto.
|
|
</p>
|
|
</div>
|
|
<div class="admin-tool-intake-chip-group">
|
|
{parameter_type_badges_markup}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="alert d-none rounded-4 mb-4" id="admin-tool-intake-feedback" role="status"></div>
|
|
|
|
<div class="row g-4">
|
|
<div class="col-12 col-xxl-7">
|
|
<div class="card border-0 shadow-sm admin-surface-card h-100">
|
|
<div class="card-body p-4 p-lg-5">
|
|
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3 mb-4">
|
|
<div>
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Formulario principal</p>
|
|
<h3 class="h3 fw-semibold mb-2">Preencher os dados da nova tool</h3>
|
|
<p class="text-secondary mb-0">O objetivo aqui e validar estrutura, objetivo operacional e parametros e salvar o draft administrativo antes da revisao.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<form data-admin-tool-intake-form="true" class="vstack gap-4">
|
|
<div class="row g-3">
|
|
<div class="col-12 col-lg-4">
|
|
<label class="form-label fw-semibold" for="tool-domain">Dominio</label>
|
|
<select class="form-select form-select-lg rounded-4 admin-tool-form-control" id="tool-domain" name="domain" required>
|
|
<option value="">Selecione</option>
|
|
{domain_select_options}
|
|
</select>
|
|
</div>
|
|
<div class="col-12 col-lg-4">
|
|
<label class="form-label fw-semibold" for="tool-name">Nome tecnico</label>
|
|
<input class="form-control form-control-lg rounded-4 admin-tool-form-control" id="tool-name" name="tool_name" type="text" placeholder="consultar_vendas_periodo" required>
|
|
</div>
|
|
<div class="col-12 col-lg-4">
|
|
<label class="form-label fw-semibold" for="tool-display-name">Nome de exibicao</label>
|
|
<input class="form-control form-control-lg rounded-4 admin-tool-form-control" id="tool-display-name" name="display_name" type="text" placeholder="Consultar vendas por periodo" required>
|
|
</div>
|
|
<div class="col-12">
|
|
<label class="form-label fw-semibold" for="tool-description">Descricao operacional</label>
|
|
<textarea class="form-control rounded-4 admin-tool-form-control" id="tool-description" name="description" rows="3" placeholder="Explique o que a tool faz e em que contexto o bot deve aciona-la." required></textarea>
|
|
</div>
|
|
<div class="col-12">
|
|
<label class="form-label fw-semibold" for="tool-business-goal">Objetivo de negocio</label>
|
|
<textarea class="form-control rounded-4 admin-tool-form-control" id="tool-business-goal" name="business_goal" rows="3" placeholder="Descreva o ganho operacional esperado com essa tool." required></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="admin-tool-form-pane rounded-4 p-4">
|
|
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3 mb-3">
|
|
<div>
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Parametros</p>
|
|
<h4 class="h5 fw-semibold mb-1">Estrutura de entrada da tool</h4>
|
|
<p class="text-secondary mb-0">Adicione somente os parametros realmente necessarios para a decisao do bot.</p>
|
|
</div>
|
|
<button class="btn btn-outline-dark rounded-pill" type="button" data-add-parameter-row>Adicionar parametro</button>
|
|
</div>
|
|
|
|
<div class="vstack gap-3" data-parameter-list>
|
|
<div class="admin-tool-parameter-row rounded-4 p-3" data-parameter-row>
|
|
<div class="row g-3 align-items-end">
|
|
<div class="col-12 col-lg-3">
|
|
<label class="form-label fw-semibold">Nome</label>
|
|
<input class="form-control rounded-4 admin-tool-form-control" name="parameter_name" type="text" placeholder="periodo_inicio">
|
|
</div>
|
|
<div class="col-12 col-lg-3">
|
|
<label class="form-label fw-semibold">Tipo</label>
|
|
<select class="form-select rounded-4 admin-tool-form-control" name="parameter_type">
|
|
{parameter_select_options}
|
|
</select>
|
|
</div>
|
|
<div class="col-12 col-lg-4">
|
|
<label class="form-label fw-semibold">Descricao</label>
|
|
<input class="form-control rounded-4 admin-tool-form-control" name="parameter_description" type="text" placeholder="Data inicial usada no filtro">
|
|
</div>
|
|
<div class="col-8 col-lg-1">
|
|
<div class="form-check form-switch pt-4">
|
|
<input class="form-check-input" type="checkbox" role="switch" name="parameter_required" checked>
|
|
<label class="form-check-label small text-secondary">Obrig.</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-4 col-lg-1 text-end">
|
|
<button class="btn btn-link text-danger text-decoration-none px-0" type="button" data-remove-parameter-row>Remover</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="d-flex flex-wrap gap-3">
|
|
<button class="btn btn-dark btn-lg rounded-pill px-4 d-inline-flex align-items-center gap-2" type="submit">
|
|
<span data-intake-submit-label>Criar proposta</span>
|
|
<span class="spinner-border spinner-border-sm d-none" data-intake-submit-spinner aria-hidden="true"></span>
|
|
</button>
|
|
<a class="btn btn-outline-secondary btn-lg rounded-pill px-4" href="{escape(view.review_href, quote=True)}">Ir para revisao</a>
|
|
</div>
|
|
</form>
|
|
|
|
<template id="admin-tool-parameter-row-template">
|
|
<div class="admin-tool-parameter-row rounded-4 p-3" data-parameter-row>
|
|
<div class="row g-3 align-items-end">
|
|
<div class="col-12 col-lg-3">
|
|
<label class="form-label fw-semibold">Nome</label>
|
|
<input class="form-control rounded-4 admin-tool-form-control" name="parameter_name" type="text" placeholder="novo_parametro">
|
|
</div>
|
|
<div class="col-12 col-lg-3">
|
|
<label class="form-label fw-semibold">Tipo</label>
|
|
<select class="form-select rounded-4 admin-tool-form-control" name="parameter_type">
|
|
{parameter_select_options}
|
|
</select>
|
|
</div>
|
|
<div class="col-12 col-lg-4">
|
|
<label class="form-label fw-semibold">Descricao</label>
|
|
<input class="form-control rounded-4 admin-tool-form-control" name="parameter_description" type="text" placeholder="Descricao do parametro">
|
|
</div>
|
|
<div class="col-8 col-lg-1">
|
|
<div class="form-check form-switch pt-4">
|
|
<input class="form-check-input" type="checkbox" role="switch" name="parameter_required" checked>
|
|
<label class="form-check-label small text-secondary">Obrig.</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-4 col-lg-1 text-end">
|
|
<button class="btn btn-link text-danger text-decoration-none px-0" type="button" data-remove-parameter-row>Remover</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-12 col-xxl-5">
|
|
<div class="card border-0 shadow-sm admin-surface-card h-100">
|
|
<div class="card-body p-4 p-lg-5 d-flex flex-column gap-4">
|
|
<div>
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Dominios sugeridos</p>
|
|
<div class="row g-3">{domain_cards_markup}</div>
|
|
</div>
|
|
|
|
<div class="admin-tool-review-note p-4">
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Orientacoes da fase atual</p>
|
|
<ul class="small text-secondary ps-3 mb-0">
|
|
{submission_notes_markup}
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="admin-tool-form-pane rounded-4 p-4">
|
|
<div class="d-flex justify-content-between align-items-start gap-3 mb-3">
|
|
<div>
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Preview do draft</p>
|
|
<h3 class="h4 fw-semibold mb-1">Resultado da proposta</h3>
|
|
</div>
|
|
<span class="badge rounded-pill bg-body-tertiary text-secondary border" data-tool-intake-storage-status>Aguardando</span>
|
|
</div>
|
|
<div data-tool-intake-preview>
|
|
<div class="admin-tool-empty-state rounded-4 p-4">
|
|
<h4 class="h5 fw-semibold mb-2">Nenhuma proposta criada ainda</h4>
|
|
<p class="text-secondary mb-0">Assim que o formulario for enviado, o resumo do draft aparece aqui com os proximos passos de triagem antes da geracao.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="{BOOTSTRAP_JS_HREF}" defer></script>
|
|
<script src="{escape(js_href, quote=True)}" defer></script>
|
|
</body>
|
|
</html>
|
|
'''
|
|
|
|
|
|
def _render_tool_parameter_options(items: tuple[AdminToolIntakeParameterTypeOption, ...]) -> str:
|
|
return "\n".join(
|
|
f'<option value="{escape(item.value, quote=True)}">{escape(item.label)}</option>'
|
|
for item in items
|
|
)
|
|
|
|
def render_collaborator_management_page(
|
|
view: AdminCollaboratorManagementPageView,
|
|
*,
|
|
css_href: str,
|
|
js_href: str,
|
|
) -> str:
|
|
onboarding_notes_markup = _render_text_list(view.onboarding_notes)
|
|
governance_notes_markup = _render_text_list(view.governance_notes)
|
|
|
|
return f'''<!DOCTYPE html>
|
|
<html lang="pt-BR" data-bs-theme="light">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>{escape(view.title)}</title>
|
|
<meta name="description" content="{escape(view.subtitle)}">
|
|
<link rel="stylesheet" href="{BOOTSTRAP_CSS_HREF}">
|
|
<link rel="stylesheet" href="{escape(css_href, quote=True)}">
|
|
</head>
|
|
<body class="admin-view-body admin-collaborator-page">
|
|
<div class="container-xxl py-4 py-lg-5" data-admin-collaborator-board="true" data-collaborator-collection-endpoint="{escape(view.collection_endpoint, quote=True)}">
|
|
<div class="row g-4 align-items-start">
|
|
<aside class="col-12 col-xl-4 col-xxl-3">
|
|
<div class="card border-0 shadow-sm admin-shell-card admin-sidebar-sticky">
|
|
<div class="card-body p-4">
|
|
<div class="d-flex flex-wrap gap-2 mb-3">
|
|
<span class="badge rounded-pill text-bg-dark">Diretor</span>
|
|
<span class="badge rounded-pill bg-body-tertiary text-secondary border">Equipe interna</span>
|
|
</div>
|
|
<h1 class="display-6 fw-semibold mb-3">{escape(view.title)}</h1>
|
|
<p class="text-secondary mb-4">{escape(view.subtitle)}</p>
|
|
|
|
<div class="d-grid gap-2 mb-4 admin-quick-actions">
|
|
<a class="btn btn-dark rounded-pill" href="{escape(view.dashboard_href, quote=True)}">Voltar ao dashboard</a>
|
|
<a class="btn btn-outline-dark rounded-pill" href="#collaborator-form-card">Cadastrar colaborador</a>
|
|
</div>
|
|
|
|
<div class="admin-runtime-block p-3 mb-3">
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-3">Contexto atual</p>
|
|
<div class="d-grid gap-3 small">
|
|
<div class="d-flex justify-content-between gap-3">
|
|
<span class="text-secondary">Aplicacao</span>
|
|
<strong class="text-end">{escape(view.app_name)}</strong>
|
|
</div>
|
|
<div class="d-flex justify-content-between gap-3">
|
|
<span class="text-secondary">Ambiente</span>
|
|
<strong class="text-end text-uppercase">{escape(view.environment)}</strong>
|
|
</div>
|
|
<div class="d-flex justify-content-between gap-3">
|
|
<span class="text-secondary">Versao</span>
|
|
<strong class="text-end">{escape(view.version)}</strong>
|
|
</div>
|
|
<div class="d-flex justify-content-between gap-3">
|
|
<span class="text-secondary">Papel exigido</span>
|
|
<strong class="text-end">Diretor</strong>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="admin-tool-review-note p-4 mb-3">
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Onboarding da equipe</p>
|
|
<ul class="small text-secondary ps-3 mb-0">
|
|
{onboarding_notes_markup}
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="admin-tool-review-note p-4">
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Governanca do acesso</p>
|
|
<ul class="small text-secondary ps-3 mb-0">
|
|
{governance_notes_markup}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
<section class="col-12 col-xl-8 col-xxl-9">
|
|
<div class="card border-0 shadow-sm admin-hero-card overflow-hidden mb-4">
|
|
<div class="card-body p-4 p-lg-5">
|
|
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3">
|
|
<div>
|
|
<div class="d-flex flex-wrap gap-2 mb-3">
|
|
<span class="badge rounded-pill bg-white text-dark border">Gestao de equipe</span>
|
|
<span class="badge rounded-pill bg-dark-subtle text-dark-emphasis border border-dark-subtle">Sessao protegida</span>
|
|
</div>
|
|
<h2 class="display-5 fw-semibold mb-3">Cadastro e status da equipe administrativa</h2>
|
|
<p class="lead text-secondary mb-0">
|
|
Esta area centraliza o onboarding de colaboradores e deixa o diretor com uma leitura simples de quem esta ativo no painel.
|
|
</p>
|
|
</div>
|
|
<div class="admin-collaborator-kpi rounded-4 p-3">
|
|
<p class="small text-uppercase fw-semibold text-secondary mb-2">Politica de senha</p>
|
|
<div class="fw-semibold">{escape(view.password_policy_label)}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="alert d-none rounded-4 mb-4" id="admin-collaborator-feedback" role="status"></div>
|
|
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-12 col-md-4">
|
|
<div class="card border-0 shadow-sm admin-metric-card h-100">
|
|
<div class="card-body p-4">
|
|
<p class="small text-uppercase fw-semibold text-secondary mb-3">Total de colaboradores</p>
|
|
<div class="display-6 fw-semibold mb-2" data-collaborator-total>0</div>
|
|
<p class="text-secondary mb-0">Contas administrativas de colaborador cadastradas no painel.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-12 col-md-4">
|
|
<div class="card border-0 shadow-sm admin-metric-card h-100">
|
|
<div class="card-body p-4">
|
|
<p class="small text-uppercase fw-semibold text-secondary mb-3">Ativos</p>
|
|
<div class="display-6 fw-semibold mb-2" data-collaborator-active-count>0</div>
|
|
<p class="text-secondary mb-0">Colaboradores que podem entrar normalmente no admin.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-12 col-md-4">
|
|
<div class="card border-0 shadow-sm admin-metric-card h-100">
|
|
<div class="card-body p-4">
|
|
<p class="small text-uppercase fw-semibold text-secondary mb-3">Inativos</p>
|
|
<div class="display-6 fw-semibold mb-2" data-collaborator-inactive-count>0</div>
|
|
<p class="text-secondary mb-0">Acessos pausados sem remover rastreabilidade da conta.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-4">
|
|
<div class="col-12 col-xxl-5">
|
|
<div id="collaborator-form-card" class="card border-0 shadow-sm admin-surface-card h-100">
|
|
<div class="card-body p-4 p-lg-5">
|
|
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3 mb-4">
|
|
<div>
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Novo acesso</p>
|
|
<h3 class="h3 fw-semibold mb-2">Cadastrar colaborador</h3>
|
|
<p class="text-secondary mb-0">Crie a conta inicial da equipe com nome, email e senha provisoria ja validada pela politica do admin.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<form class="vstack gap-4" data-admin-collaborator-form="true">
|
|
<div class="row g-3">
|
|
<div class="col-12">
|
|
<label class="form-label fw-semibold" for="collaborator-display-name">Nome do colaborador</label>
|
|
<input class="form-control form-control-lg rounded-4 admin-tool-form-control" id="collaborator-display-name" name="display_name" type="text" placeholder="Nome da pessoa" required>
|
|
</div>
|
|
<div class="col-12">
|
|
<label class="form-label fw-semibold" for="collaborator-email">Email interno</label>
|
|
<input class="form-control form-control-lg rounded-4 admin-tool-form-control" id="collaborator-email" name="email" type="email" placeholder="colaborador@empresa.com" autocomplete="email" required>
|
|
</div>
|
|
<div class="col-12">
|
|
<label class="form-label fw-semibold" for="collaborator-password">Senha inicial</label>
|
|
<input class="form-control form-control-lg rounded-4 admin-tool-form-control" id="collaborator-password" name="password" type="password" placeholder="Senha inicial do colaborador" autocomplete="new-password" required>
|
|
</div>
|
|
<div class="col-12">
|
|
<div class="admin-collaborator-kpi rounded-4 p-3 small text-secondary">
|
|
<strong>Politica atual:</strong> {escape(view.password_policy_label)}
|
|
</div>
|
|
</div>
|
|
<div class="col-12">
|
|
<div class="form-check form-switch pt-1">
|
|
<input class="form-check-input" type="checkbox" role="switch" id="collaborator-active" name="is_active" checked>
|
|
<label class="form-check-label text-secondary" for="collaborator-active">Criar conta ja ativa</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="d-flex flex-wrap gap-3">
|
|
<button class="btn btn-dark btn-lg rounded-pill px-4 d-inline-flex align-items-center gap-2" type="submit">
|
|
<span data-collaborator-submit-label>Criar colaborador</span>
|
|
<span class="spinner-border spinner-border-sm d-none" data-collaborator-submit-spinner aria-hidden="true"></span>
|
|
</button>
|
|
<button class="btn btn-outline-secondary btn-lg rounded-pill px-4" type="reset">Limpar</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-12 col-xxl-7">
|
|
<div class="card border-0 shadow-sm admin-surface-card h-100">
|
|
<div class="card-body p-4 p-lg-5 d-flex flex-column gap-4">
|
|
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3">
|
|
<div>
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Equipe cadastrada</p>
|
|
<h3 class="h3 fw-semibold mb-2">Leitura atual da equipe interna</h3>
|
|
<p class="text-secondary mb-0">A lista abaixo vem da superficie web do painel e permite ligar ou desligar acessos rapidamente.</p>
|
|
</div>
|
|
<button class="btn btn-outline-dark rounded-pill px-4" type="button" data-admin-collaborator-refresh>
|
|
<span data-collaborator-refresh-label>Atualizar lista</span>
|
|
<span class="spinner-border spinner-border-sm d-none" data-collaborator-refresh-spinner aria-hidden="true"></span>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="admin-collaborator-grid" data-collaborator-list>
|
|
<div class="admin-tool-empty-state rounded-4 p-4">
|
|
<h4 class="h5 fw-semibold mb-2">Nenhum colaborador carregado ainda</h4>
|
|
<p class="text-secondary mb-0">Clique em atualizar lista para sincronizar o estado atual da equipe.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="{BOOTSTRAP_JS_HREF}" defer></script>
|
|
<script src="{escape(js_href, quote=True)}" defer></script>
|
|
</body>
|
|
</html>
|
|
'''
|
|
|
|
|
|
def _render_collaborator_cards(items: list[dict]) -> str:
|
|
return "\n".join(
|
|
f'''<article class="admin-collaborator-card rounded-4 p-4"><div class="d-flex flex-wrap justify-content-between align-items-start gap-3 mb-3"><div><div class="small text-uppercase fw-semibold text-secondary mb-2">{escape(str(item.get("role") or "colaborador"))}</div><h4 class="h5 fw-semibold mb-1">{escape(str(item.get("display_name") or "Colaborador"))}</h4><div class="small text-secondary">{escape(str(item.get("email") or ""))}</div></div><span class="badge rounded-pill {'bg-success-subtle text-success-emphasis border border-success-subtle' if item.get("is_active") else 'bg-secondary-subtle text-secondary-emphasis border border-secondary-subtle'}">{'Ativo' if item.get("is_active") else 'Inativo'}</span></div><div class="small text-secondary admin-collaborator-meta mb-3"><div><strong>Ultimo login:</strong> {escape(str(item.get("last_login_at") or "Ainda nao acessou"))}</div><div><strong>ID:</strong> {escape(str(item.get("id") or "-"))}</div></div><button class="btn btn-sm {'btn-outline-secondary' if item.get("is_active") else 'btn-outline-dark'} rounded-pill px-3" type="button" data-collaborator-toggle="true" data-collaborator-id="{escape(str(item.get("id") or ""), quote=True)}" data-collaborator-next-state="{'false' if item.get("is_active") else 'true'}">{'Desativar acesso' if item.get("is_active") else 'Reativar acesso'}</button></article>'''
|
|
for item in items
|
|
)
|
|
|
|
|
|
|
|
def render_system_configuration_page(
|
|
view: AdminSystemConfigurationPageView,
|
|
*,
|
|
css_href: str,
|
|
js_href: str,
|
|
) -> str:
|
|
access_notes_markup = _render_text_list(view.access_notes)
|
|
governance_notes_markup = _render_text_list(view.governance_notes)
|
|
|
|
return f'''<!DOCTYPE html>
|
|
<html lang="pt-BR" data-bs-theme="light">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>{escape(view.title)}</title>
|
|
<meta name="description" content="{escape(view.subtitle)}">
|
|
<link rel="stylesheet" href="{BOOTSTRAP_CSS_HREF}">
|
|
<link rel="stylesheet" href="{escape(css_href, quote=True)}">
|
|
</head>
|
|
<body class="admin-view-body admin-system-page">
|
|
<div class="container-xxl py-4 py-lg-5"
|
|
data-admin-system-configuration="true"
|
|
data-overview-endpoint="{escape(view.overview_endpoint, quote=True)}"
|
|
data-runtime-endpoint="{escape(view.runtime_endpoint, quote=True)}"
|
|
data-security-endpoint="{escape(view.security_endpoint, quote=True)}"
|
|
data-model-runtimes-endpoint="{escape(view.model_runtimes_endpoint, quote=True)}"
|
|
data-functional-endpoint="{escape(view.functional_endpoint, quote=True)}"
|
|
data-functional-detail-base="{escape(view.functional_detail_base, quote=True)}"
|
|
data-bot-governance-endpoint="{escape(view.bot_governance_endpoint, quote=True)}">
|
|
<div class="row g-4 align-items-start">
|
|
<aside class="col-12 col-xl-4 col-xxl-3">
|
|
<div class="card border-0 shadow-sm admin-shell-card admin-sidebar-sticky">
|
|
<div class="card-body p-4">
|
|
<div class="d-flex flex-wrap gap-2 mb-3">
|
|
<span class="badge rounded-pill text-bg-dark">Configuracoes</span>
|
|
<span class="badge rounded-pill bg-body-tertiary text-secondary border">Fase 4</span>
|
|
</div>
|
|
<h1 class="display-6 fw-semibold mb-3">{escape(view.title)}</h1>
|
|
<p class="text-secondary mb-4">{escape(view.subtitle)}</p>
|
|
<div class="d-grid gap-2 mb-4 admin-quick-actions">
|
|
<a class="btn btn-dark rounded-pill" href="{escape(view.dashboard_href, quote=True)}">Voltar ao dashboard</a>
|
|
<a class="btn btn-outline-dark rounded-pill" href="#functional-configuration-card">Ver catalogo funcional</a>
|
|
</div>
|
|
<div class="admin-runtime-block p-3 mb-3">
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-3">Contexto atual</p>
|
|
<div class="d-grid gap-3 small">
|
|
<div class="d-flex justify-content-between gap-3"><span class="text-secondary">Aplicacao</span><strong class="text-end">{escape(view.app_name)}</strong></div>
|
|
<div class="d-flex justify-content-between gap-3"><span class="text-secondary">Ambiente</span><strong class="text-end text-uppercase">{escape(view.environment)}</strong></div>
|
|
<div class="d-flex justify-content-between gap-3"><span class="text-secondary">Versao</span><strong class="text-end">{escape(view.version)}</strong></div>
|
|
<div class="d-flex justify-content-between gap-3"><span class="text-secondary">Ultima leitura</span><strong class="text-end" data-system-last-sync>Aguardando</strong></div>
|
|
</div>
|
|
</div>
|
|
<div class="admin-tool-review-note p-4 mb-3">
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Leitura disponivel</p>
|
|
<ul class="small text-secondary ps-3 mb-0">{access_notes_markup}</ul>
|
|
</div>
|
|
<div class="admin-tool-review-note p-4">
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Governanca desta tela</p>
|
|
<ul class="small text-secondary ps-3 mb-0">{governance_notes_markup}</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
<section class="col-12 col-xl-8 col-xxl-9">
|
|
<div class="card border-0 shadow-sm admin-hero-card overflow-hidden mb-4">
|
|
<div class="card-body p-4 p-lg-5">
|
|
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3">
|
|
<div>
|
|
<div class="d-flex flex-wrap gap-2 mb-3">
|
|
<span class="badge rounded-pill bg-white text-dark border">Somente leitura</span>
|
|
<span class="badge rounded-pill bg-dark-subtle text-dark-emphasis border border-dark-subtle">Sessao protegida</span>
|
|
</div>
|
|
<h2 class="display-5 fw-semibold mb-3">Configuracao funcional e regras do painel em uma unica tela</h2>
|
|
<p class="lead text-secondary mb-0">Aqui voce acompanha o que esta disponivel para consulta agora, sem expor detalhes internos demais do ambiente.</p>
|
|
</div>
|
|
<button class="btn btn-outline-dark rounded-pill px-4" type="button" data-admin-system-refresh>
|
|
<span data-system-refresh-label>Atualizar leitura</span>
|
|
<span class="spinner-border spinner-border-sm d-none" data-system-refresh-spinner aria-hidden="true"></span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="alert d-none rounded-4 mb-4" id="admin-system-configuration-feedback" role="status"></div>
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-12 col-md-6 col-xxl-3"><div class="card border-0 shadow-sm admin-metric-card h-100"><div class="card-body p-4"><p class="small text-uppercase fw-semibold text-secondary mb-3">Configuracoes funcionais</p><div class="display-6 fw-semibold mb-2" data-system-config-count>0</div><p class="text-secondary mb-0">Itens que o time pode acompanhar nesta etapa.</p></div></div></div>
|
|
<div class="col-12 col-md-6 col-xxl-3"><div class="card border-0 shadow-sm admin-metric-card h-100"><div class="card-body p-4"><p class="small text-uppercase fw-semibold text-secondary mb-3">Campos governados pelo bot</p><div class="display-6 fw-semibold mb-2" data-system-bot-setting-count>0</div><p class="text-secondary mb-0">Ajustes do atendimento sob controle do admin.</p></div></div></div>
|
|
<div class="col-12 col-md-6 col-xxl-3"><div class="card border-0 shadow-sm admin-metric-card h-100"><div class="card-body p-4"><p class="small text-uppercase fw-semibold text-secondary mb-3">Perfis de runtime</p><div class="display-6 fw-semibold mb-2" data-system-runtime-profile-count>0</div><p class="text-secondary mb-0">Separacao entre atendimento e geracao de tools quando liberada.</p></div></div></div>
|
|
<div class="col-12 col-md-6 col-xxl-3"><div class="card border-0 shadow-sm admin-metric-card h-100"><div class="card-body p-4"><p class="small text-uppercase fw-semibold text-secondary mb-3">Fontes de configuracao</p><div class="display-6 fw-semibold mb-2" data-system-source-count>0</div><p class="text-secondary mb-0">Base usada para montar os dados desta tela.</p></div></div></div>
|
|
</div>
|
|
<div class="row g-4 mb-4">
|
|
<div class="col-12 col-xxl-7"><div id="functional-configuration-card" class="card border-0 shadow-sm admin-surface-card h-100"><div class="card-body p-4 p-lg-5 d-flex flex-column gap-4"><div class="d-flex flex-wrap justify-content-between align-items-start gap-3"><div><p class="text-uppercase small fw-semibold text-secondary mb-2">Catalogo funcional</p><h3 class="h3 fw-semibold mb-2">Configuracoes funcionais do sistema</h3><p class="text-secondary mb-0">Cada card resume o que a configuracao cobre e como ela impacta a operacao.</p></div><span class="badge rounded-pill bg-body-tertiary text-secondary border" data-system-functional-mode>Contrato compartilhado</span></div><div class="admin-system-grid" data-system-functional-list><div class="admin-tool-empty-state rounded-4 p-4"><h4 class="h5 fw-semibold mb-2">Catalogo ainda nao carregado</h4><p class="text-secondary mb-0">Atualize a leitura para montar a superficie funcional desta etapa.</p></div></div></div></div></div>
|
|
<div class="col-12 col-xxl-5"><div class="card border-0 shadow-sm admin-surface-card h-100"><div class="card-body p-4 p-lg-5 d-flex flex-column gap-4"><div><p class="text-uppercase small fw-semibold text-secondary mb-2">Governanca do bot</p><h3 class="h3 fw-semibold mb-2">Campos sob governanca administrativa</h3><p class="text-secondary mb-0">Os ajustes do atendimento aparecem aqui de forma agrupada e clara.</p></div><div><div class="small text-uppercase fw-semibold text-secondary mb-2">Parent config keys</div><div class="admin-system-chip-group" data-system-parent-keys><span class="badge rounded-pill bg-body-tertiary text-secondary border">Aguardando</span></div></div><div class="admin-system-grid" data-system-bot-settings-list><div class="admin-tool-empty-state rounded-4 p-4"><h4 class="h5 fw-semibold mb-2">Governanca ainda nao carregada</h4><p class="text-secondary mb-0">Os campos governados pelo bot aparecem aqui depois da primeira leitura.</p></div></div></div></div></div>
|
|
</div>
|
|
<div class="row g-4 mb-4">
|
|
<div class="col-12 col-lg-6"><div class="card border-0 shadow-sm admin-surface-card h-100"><div class="card-body p-4 p-lg-5 d-flex flex-column gap-4"><div><p class="text-uppercase small fw-semibold text-secondary mb-2">Runtime administrativo</p><h3 class="h3 fw-semibold mb-2">Informacoes essenciais do painel</h3><p class="text-secondary mb-0">Mostra apenas o contexto util para a operacao.</p></div><div data-system-runtime-summary><div class="admin-tool-empty-state rounded-4 p-4"><h4 class="h5 fw-semibold mb-2">Runtime aguardando leitura</h4><p class="text-secondary mb-0">Esta area e carregada conforme a permissao da sessao atual.</p></div></div></div></div></div>
|
|
<div class="col-12 col-lg-6"><div class="card border-0 shadow-sm admin-surface-card h-100"><div class="card-body p-4 p-lg-5 d-flex flex-column gap-4"><div><p class="text-uppercase small fw-semibold text-secondary mb-2">Postura de seguranca</p><h3 class="h3 fw-semibold mb-2">Regras visiveis de senha e sessao</h3><p class="text-secondary mb-0">Exibe somente o necessario para orientar o uso da sessao.</p></div><div data-system-security-summary><div class="admin-tool-empty-state rounded-4 p-4"><h4 class="h5 fw-semibold mb-2">Seguranca aguardando leitura</h4><p class="text-secondary mb-0">A sessao atual precisa de permissao elevada para ver este snapshot.</p></div></div></div></div></div>
|
|
</div>
|
|
<div class="row g-4">
|
|
<div class="col-12 col-xxl-7"><div class="card border-0 shadow-sm admin-surface-card h-100"><div class="card-body p-4 p-lg-5 d-flex flex-column gap-4"><div><p class="text-uppercase small fw-semibold text-secondary mb-2">Separacao de runtime</p><h3 class="h3 fw-semibold mb-2">Modelos do atendimento versus geracao de tools</h3><p class="text-secondary mb-0">Aqui fica clara a separacao entre atendimento e geracao de tools.</p></div><div data-system-model-runtime-summary><div class="admin-tool-empty-state rounded-4 p-4"><h4 class="h5 fw-semibold mb-2">Perfis de runtime aguardando leitura</h4><p class="text-secondary mb-0">A superficie completa aparece quando a sessao pode consultar manage_settings.</p></div></div></div></div></div>
|
|
<div class="col-12 col-xxl-5"><div class="card border-0 shadow-sm admin-surface-card h-100"><div class="card-body p-4 p-lg-5 d-flex flex-column gap-4"><div><p class="text-uppercase small fw-semibold text-secondary mb-2">Fontes do snapshot</p><h3 class="h3 fw-semibold mb-2">De onde cada configuracao vem</h3><p class="text-secondary mb-0">Resumo das bases usadas para montar esta tela.</p></div><div class="admin-system-grid" data-system-source-list><div class="admin-tool-empty-state rounded-4 p-4"><h4 class="h5 fw-semibold mb-2">Fontes aguardando leitura</h4><p class="text-secondary mb-0">As fontes completas entram quando a sessao pode consultar o overview tecnico.</p></div></div></div></div></div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
<script src="{BOOTSTRAP_JS_HREF}" defer></script>
|
|
<script src="{escape(js_href, quote=True)}" defer></script>
|
|
</body>
|
|
</html>
|
|
'''
|
|
|
|
def render_sales_revenue_reports_page(
|
|
view: AdminSalesRevenueReportsPageView,
|
|
*,
|
|
css_href: str,
|
|
js_href: str,
|
|
) -> str:
|
|
access_notes_markup = _render_text_list(view.access_notes)
|
|
reading_notes_markup = _render_text_list(view.reading_notes)
|
|
|
|
return f'''<!DOCTYPE html>
|
|
<html lang="pt-BR" data-bs-theme="light">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>{escape(view.title)}</title>
|
|
<meta name="description" content="{escape(view.subtitle)}">
|
|
<link rel="stylesheet" href="{BOOTSTRAP_CSS_HREF}">
|
|
<link rel="stylesheet" href="{escape(css_href, quote=True)}">
|
|
</head>
|
|
<body class="admin-view-body admin-commercial-reports-page">
|
|
<div class="container-xxl py-4 py-lg-5"
|
|
data-admin-sales-revenue-reports="true"
|
|
data-sales-overview-endpoint="{escape(view.sales_overview_endpoint, quote=True)}"
|
|
data-revenue-overview-endpoint="{escape(view.revenue_overview_endpoint, quote=True)}">
|
|
<div class="row g-4 align-items-start">
|
|
<aside class="col-12 col-xl-4 col-xxl-3">
|
|
<div class="card border-0 shadow-sm admin-shell-card admin-sidebar-sticky">
|
|
<div class="card-body p-4">
|
|
<div class="d-flex flex-wrap gap-2 mb-3">
|
|
<span class="badge rounded-pill text-bg-dark">Relatorios</span>
|
|
<span class="badge rounded-pill bg-body-tertiary text-secondary border">Vendas + arrecadacao</span>
|
|
</div>
|
|
<h1 class="display-6 fw-semibold mb-3">{escape(view.title)}</h1>
|
|
<p class="text-secondary mb-4">{escape(view.subtitle)}</p>
|
|
<div class="d-grid gap-2 mb-4 admin-quick-actions">
|
|
<a class="btn btn-dark rounded-pill" href="{escape(view.dashboard_href, quote=True)}">Voltar ao dashboard</a>
|
|
<a class="btn btn-outline-dark rounded-pill" href="#sales-reports-card">Ver vendas</a>
|
|
</div>
|
|
<div class="admin-runtime-block p-3 mb-3">
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-3">Contexto atual</p>
|
|
<div class="d-grid gap-3 small">
|
|
<div class="d-flex justify-content-between gap-3"><span class="text-secondary">Aplicacao</span><strong class="text-end">{escape(view.app_name)}</strong></div>
|
|
<div class="d-flex justify-content-between gap-3"><span class="text-secondary">Ambiente</span><strong class="text-end text-uppercase">{escape(view.environment)}</strong></div>
|
|
<div class="d-flex justify-content-between gap-3"><span class="text-secondary">Versao</span><strong class="text-end">{escape(view.version)}</strong></div>
|
|
<div class="d-flex justify-content-between gap-3"><span class="text-secondary">Ultima leitura</span><strong class="text-end" data-commercial-last-sync>Aguardando</strong></div>
|
|
</div>
|
|
</div>
|
|
<div class="admin-tool-review-note p-4 mb-3">
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Escopo liberado</p>
|
|
<ul class="small text-secondary ps-3 mb-0">{access_notes_markup}</ul>
|
|
</div>
|
|
<div class="admin-tool-review-note p-4">
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Como ler esta tela</p>
|
|
<ul class="small text-secondary ps-3 mb-0">{reading_notes_markup}</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
<section class="col-12 col-xl-8 col-xxl-9">
|
|
<div class="card border-0 shadow-sm admin-hero-card overflow-hidden mb-4">
|
|
<div class="card-body p-4 p-lg-5">
|
|
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3">
|
|
<div>
|
|
<div class="d-flex flex-wrap gap-2 mb-3">
|
|
<span class="badge rounded-pill bg-white text-dark border">Leitura operacional</span>
|
|
<span class="badge rounded-pill bg-dark-subtle text-dark-emphasis border border-dark-subtle">Fase 4</span>
|
|
</div>
|
|
<h2 class="display-5 fw-semibold mb-3">Vendas e arrecadacao na mesma visao do painel</h2>
|
|
<p class="lead text-secondary mb-0">Aqui o time acompanha os principais blocos comerciais de forma simples e organizada.</p>
|
|
</div>
|
|
<button class="btn btn-outline-dark rounded-pill px-4" type="button" data-admin-commercial-refresh>
|
|
<span data-commercial-refresh-label>Atualizar leitura</span>
|
|
<span class="spinner-border spinner-border-sm d-none" data-commercial-refresh-spinner aria-hidden="true"></span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="alert d-none rounded-4 mb-4" id="admin-commercial-feedback" role="status"></div>
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-12 col-md-6 col-xxl-3"><div class="card border-0 shadow-sm admin-metric-card h-100"><div class="card-body p-4"><p class="small text-uppercase fw-semibold text-secondary mb-3">Relatorios de vendas</p><div class="display-6 fw-semibold mb-2" data-sales-report-count>0</div><p class="text-secondary mb-0">Estrutura inicial do dominio comercial.</p></div></div></div>
|
|
<div class="col-12 col-md-6 col-xxl-3"><div class="card border-0 shadow-sm admin-metric-card h-100"><div class="card-body p-4"><p class="small text-uppercase fw-semibold text-secondary mb-3">Relatorios de arrecadacao</p><div class="display-6 fw-semibold mb-2" data-revenue-report-count>0</div><p class="text-secondary mb-0">Leitura inicial dos recebimentos de locacao.</p></div></div></div>
|
|
<div class="col-12 col-md-6 col-xxl-3"><div class="card border-0 shadow-sm admin-metric-card h-100"><div class="card-body p-4"><p class="small text-uppercase fw-semibold text-secondary mb-3">Bases de leitura</p><div class="display-6 fw-semibold mb-2" data-commercial-dataset-count>0</div><p class="text-secondary mb-0">Bases consolidadas usadas para montar esta tela.</p></div></div></div>
|
|
<div class="col-12 col-md-6 col-xxl-3"><div class="card border-0 shadow-sm admin-metric-card h-100"><div class="card-body p-4"><p class="small text-uppercase fw-semibold text-secondary mb-3">Atualizacao</p><div class="h4 fw-semibold mb-2" data-commercial-sync-strategy>--</div><p class="text-secondary mb-0">Ritmo atual da carga exibida no painel.</p></div></div></div>
|
|
</div>
|
|
<div class="row g-4">
|
|
<div class="col-12 col-xxl-6">
|
|
<div id="sales-reports-card" class="card border-0 shadow-sm admin-surface-card h-100">
|
|
<div class="card-body p-4 p-lg-5 d-flex flex-column gap-4">
|
|
<div>
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Vendas</p>
|
|
<h3 class="h3 fw-semibold mb-2">O que acompanhar em vendas</h3>
|
|
<p class="text-secondary mb-0">Volume de pedidos, ticket medio, cancelamentos e comparativos principais.</p>
|
|
</div>
|
|
<div class="admin-commercial-stack" data-sales-overview-metrics>
|
|
<div class="admin-tool-empty-state rounded-4 p-4"><h4 class="h5 fw-semibold mb-2">Vendas aguardando leitura</h4><p class="text-secondary mb-0">Clique em atualizar leitura para carregar o snapshot de vendas.</p></div>
|
|
</div>
|
|
<div class="admin-commercial-stack" data-sales-materialization></div>
|
|
<div class="admin-commercial-grid" data-sales-report-list></div>
|
|
<div>
|
|
<div class="small text-uppercase fw-semibold text-secondary mb-2">Proximas melhorias</div>
|
|
<div class="admin-commercial-list" data-sales-next-steps></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-12 col-xxl-6">
|
|
<div class="card border-0 shadow-sm admin-surface-card h-100">
|
|
<div class="card-body p-4 p-lg-5 d-flex flex-column gap-4">
|
|
<div>
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Arrecadacao</p>
|
|
<h3 class="h3 fw-semibold mb-2">O que acompanhar em arrecadacao</h3>
|
|
<p class="text-secondary mb-0">Pagamentos liquidados, valor arrecadado e conciliacao por contrato.</p>
|
|
</div>
|
|
<div class="admin-commercial-stack" data-revenue-overview-metrics>
|
|
<div class="admin-tool-empty-state rounded-4 p-4"><h4 class="h5 fw-semibold mb-2">Arrecadacao aguardando leitura</h4><p class="text-secondary mb-0">Clique em atualizar leitura para carregar o snapshot de arrecadacao.</p></div>
|
|
</div>
|
|
<div class="admin-commercial-stack" data-revenue-materialization></div>
|
|
<div class="admin-commercial-grid" data-revenue-report-list></div>
|
|
<div>
|
|
<div class="small text-uppercase fw-semibold text-secondary mb-2">Proximas melhorias</div>
|
|
<div class="admin-commercial-list" data-revenue-next-steps></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
<script src="{BOOTSTRAP_JS_HREF}" defer></script>
|
|
<script src="{escape(js_href, quote=True)}" defer></script>
|
|
</body>
|
|
</html>
|
|
'''
|
|
|
|
def render_rental_reports_page(
|
|
view: AdminRentalReportsPageView,
|
|
*,
|
|
css_href: str,
|
|
js_href: str,
|
|
) -> str:
|
|
access_notes_markup = _render_text_list(view.access_notes)
|
|
reading_notes_markup = _render_text_list(view.reading_notes)
|
|
|
|
return f'''<!DOCTYPE html>
|
|
<html lang="pt-BR" data-bs-theme="light">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>{escape(view.title)}</title>
|
|
<meta name="description" content="{escape(view.subtitle)}">
|
|
<link rel="stylesheet" href="{BOOTSTRAP_CSS_HREF}">
|
|
<link rel="stylesheet" href="{escape(css_href, quote=True)}">
|
|
</head>
|
|
<body class="admin-view-body admin-rental-reports-page">
|
|
<div class="container-xxl py-4 py-lg-5"
|
|
data-admin-rental-reports="true"
|
|
data-rental-overview-endpoint="{escape(view.overview_endpoint, quote=True)}">
|
|
<div class="row g-4 align-items-start">
|
|
<aside class="col-12 col-xl-4 col-xxl-3">
|
|
<div class="card border-0 shadow-sm admin-shell-card admin-sidebar-sticky">
|
|
<div class="card-body p-4">
|
|
<div class="d-flex flex-wrap gap-2 mb-3">
|
|
<span class="badge rounded-pill text-bg-dark">Relatorios</span>
|
|
<span class="badge rounded-pill bg-body-tertiary text-secondary border">Locacao</span>
|
|
</div>
|
|
<h1 class="display-6 fw-semibold mb-3">{escape(view.title)}</h1>
|
|
<p class="text-secondary mb-4">{escape(view.subtitle)}</p>
|
|
<div class="d-grid gap-2 mb-4 admin-quick-actions">
|
|
<a class="btn btn-dark rounded-pill" href="{escape(view.dashboard_href, quote=True)}">Voltar ao dashboard</a>
|
|
<a class="btn btn-outline-dark rounded-pill" href="#rental-report-catalog">Ver catalogo</a>
|
|
</div>
|
|
<div class="admin-runtime-block p-3 mb-3">
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-3">Contexto atual</p>
|
|
<div class="d-grid gap-3 small">
|
|
<div class="d-flex justify-content-between gap-3"><span class="text-secondary">Aplicacao</span><strong class="text-end">{escape(view.app_name)}</strong></div>
|
|
<div class="d-flex justify-content-between gap-3"><span class="text-secondary">Ambiente</span><strong class="text-end text-uppercase">{escape(view.environment)}</strong></div>
|
|
<div class="d-flex justify-content-between gap-3"><span class="text-secondary">Versao</span><strong class="text-end">{escape(view.version)}</strong></div>
|
|
<div class="d-flex justify-content-between gap-3"><span class="text-secondary">Ultima leitura</span><strong class="text-end" data-rental-last-sync>Aguardando</strong></div>
|
|
</div>
|
|
</div>
|
|
<div class="admin-tool-review-note p-4 mb-3">
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Escopo liberado</p>
|
|
<ul class="small text-secondary ps-3 mb-0">{access_notes_markup}</ul>
|
|
</div>
|
|
<div class="admin-tool-review-note p-4">
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Como ler esta tela</p>
|
|
<ul class="small text-secondary ps-3 mb-0">{reading_notes_markup}</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
<section class="col-12 col-xl-8 col-xxl-9">
|
|
<div class="card border-0 shadow-sm admin-hero-card overflow-hidden mb-4">
|
|
<div class="card-body p-4 p-lg-5">
|
|
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3">
|
|
<div>
|
|
<div class="d-flex flex-wrap gap-2 mb-3">
|
|
<span class="badge rounded-pill bg-white text-dark border">Leitura operacional</span>
|
|
<span class="badge rounded-pill bg-dark-subtle text-dark-emphasis border border-dark-subtle">Fase 4</span>
|
|
</div>
|
|
<h2 class="display-5 fw-semibold mb-3">Visao inicial de locacao para frota e contratos</h2>
|
|
<p class="lead text-secondary mb-0">Acompanhe os principais blocos do dominio em uma leitura organizada do painel.</p>
|
|
</div>
|
|
<button class="btn btn-outline-dark rounded-pill px-4" type="button" data-admin-rental-refresh>
|
|
<span data-rental-refresh-label>Atualizar leitura</span>
|
|
<span class="spinner-border spinner-border-sm d-none" data-rental-refresh-spinner aria-hidden="true"></span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="alert d-none rounded-4 mb-4" id="admin-rental-feedback" role="status"></div>
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-12 col-md-6 col-xxl-3"><div class="card border-0 shadow-sm admin-metric-card h-100"><div class="card-body p-4"><p class="small text-uppercase fw-semibold text-secondary mb-3">Relatorios</p><div class="display-6 fw-semibold mb-2" data-rental-report-count>0</div><p class="text-secondary mb-0">Temas iniciais que o time ja consegue acompanhar.</p></div></div></div>
|
|
<div class="col-12 col-md-6 col-xxl-3"><div class="card border-0 shadow-sm admin-metric-card h-100"><div class="card-body p-4"><p class="small text-uppercase fw-semibold text-secondary mb-3">Bases de leitura</p><div class="display-6 fw-semibold mb-2" data-rental-dataset-count>0</div><p class="text-secondary mb-0">Bases consolidadas de frota e contratos.</p></div></div></div>
|
|
<div class="col-12 col-md-6 col-xxl-3"><div class="card border-0 shadow-sm admin-metric-card h-100"><div class="card-body p-4"><p class="small text-uppercase fw-semibold text-secondary mb-3">Atualizacao</p><div class="h4 fw-semibold mb-2" data-rental-sync-strategy>--</div><p class="text-secondary mb-0">Ritmo atual da carga exibida no painel.</p></div></div></div>
|
|
<div class="col-12 col-md-6 col-xxl-3"><div class="card border-0 shadow-sm admin-metric-card h-100"><div class="card-body p-4"><p class="small text-uppercase fw-semibold text-secondary mb-3">Area acompanhada</p><div class="h4 fw-semibold mb-2" data-rental-source-domain>--</div><p class="text-secondary mb-0">Dominio principal desta leitura.</p></div></div></div>
|
|
</div>
|
|
<div class="row g-4">
|
|
<div class="col-12 col-lg-5">
|
|
<div class="card border-0 shadow-sm admin-surface-card h-100">
|
|
<div class="card-body p-4 p-lg-5 d-flex flex-column gap-4">
|
|
<div>
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Overview de locacao</p>
|
|
<h3 class="h3 fw-semibold mb-2">Resumo inicial da operacao</h3>
|
|
<p class="text-secondary mb-0">Os indicadores mostram rapidamente a situacao atual de frota e contratos.</p>
|
|
</div>
|
|
<div class="admin-rental-stack" data-rental-overview-metrics>
|
|
<div class="admin-tool-empty-state rounded-4 p-4"><h4 class="h5 fw-semibold mb-2">Locacao aguardando leitura</h4><p class="text-secondary mb-0">Clique em atualizar leitura para montar o snapshot desta superficie.</p></div>
|
|
</div>
|
|
<div class="admin-rental-stack" data-rental-materialization></div>
|
|
<div>
|
|
<div class="small text-uppercase fw-semibold text-secondary mb-2">Proximas melhorias</div>
|
|
<div class="admin-rental-list" data-rental-next-steps></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-12 col-lg-7">
|
|
<div id="rental-report-catalog" class="card border-0 shadow-sm admin-surface-card h-100">
|
|
<div class="card-body p-4 p-lg-5 d-flex flex-column gap-4">
|
|
<div>
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Relatorios desta etapa</p>
|
|
<h3 class="h3 fw-semibold mb-2">Relatorios disponiveis nesta etapa</h3>
|
|
<p class="text-secondary mb-0">Disponibilidade de frota, lifecycle de contratos, devolucoes em atraso, ocupacao e receita prevista versus final.</p>
|
|
</div>
|
|
<div class="admin-rental-grid" data-rental-report-list>
|
|
<div class="admin-tool-empty-state rounded-4 p-4"><h4 class="h5 fw-semibold mb-2">Catalogo aguardando leitura</h4><p class="text-secondary mb-0">Os cards de relatorio aparecem aqui depois da primeira leitura do painel.</p></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
<script src="{BOOTSTRAP_JS_HREF}" defer></script>
|
|
<script src="{escape(js_href, quote=True)}" defer></script>
|
|
</body>
|
|
</html>
|
|
'''
|
|
|
|
def render_bot_monitoring_page(
|
|
view: AdminBotMonitoringPageView,
|
|
*,
|
|
css_href: str,
|
|
js_href: str,
|
|
) -> str:
|
|
access_notes_markup = _render_text_list(view.access_notes)
|
|
reading_notes_markup = _render_text_list(view.reading_notes)
|
|
|
|
return f'''<!DOCTYPE html>
|
|
<html lang="pt-BR" data-bs-theme="light">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>{escape(view.title)}</title>
|
|
<meta name="description" content="{escape(view.subtitle)}">
|
|
<link rel="stylesheet" href="{BOOTSTRAP_CSS_HREF}">
|
|
<link rel="stylesheet" href="{escape(css_href, quote=True)}">
|
|
</head>
|
|
<body class="admin-view-body admin-bot-monitoring-page">
|
|
<div class="container-xxl py-4 py-lg-5"
|
|
data-admin-bot-monitoring="true"
|
|
data-bot-flow-overview-endpoint="{escape(view.bot_flow_overview_endpoint, quote=True)}"
|
|
data-telemetry-overview-endpoint="{escape(view.telemetry_overview_endpoint, quote=True)}">
|
|
<div class="row g-4 align-items-start">
|
|
<aside class="col-12 col-xl-4 col-xxl-3">
|
|
<div class="card border-0 shadow-sm admin-shell-card admin-sidebar-sticky">
|
|
<div class="card-body p-4">
|
|
<div class="d-flex flex-wrap gap-2 mb-3">
|
|
<span class="badge rounded-pill text-bg-dark">Monitoramento</span>
|
|
<span class="badge rounded-pill bg-body-tertiary text-secondary border">Bot operacional</span>
|
|
</div>
|
|
<h1 class="display-6 fw-semibold mb-3">{escape(view.title)}</h1>
|
|
<p class="text-secondary mb-4">{escape(view.subtitle)}</p>
|
|
<div class="d-grid gap-2 mb-4 admin-quick-actions">
|
|
<a class="btn btn-dark rounded-pill" href="{escape(view.dashboard_href, quote=True)}">Voltar ao dashboard</a>
|
|
<a class="btn btn-outline-dark rounded-pill" href="#bot-flow-monitoring-card">Ver fluxo do bot</a>
|
|
</div>
|
|
<div class="admin-runtime-block p-3 mb-3">
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-3">Contexto atual</p>
|
|
<div class="d-grid gap-3 small">
|
|
<div class="d-flex justify-content-between gap-3"><span class="text-secondary">Aplicacao</span><strong class="text-end">{escape(view.app_name)}</strong></div>
|
|
<div class="d-flex justify-content-between gap-3"><span class="text-secondary">Ambiente</span><strong class="text-end text-uppercase">{escape(view.environment)}</strong></div>
|
|
<div class="d-flex justify-content-between gap-3"><span class="text-secondary">Versao</span><strong class="text-end">{escape(view.version)}</strong></div>
|
|
<div class="d-flex justify-content-between gap-3"><span class="text-secondary">Ultima leitura</span><strong class="text-end" data-bot-monitoring-last-sync>Aguardando</strong></div>
|
|
</div>
|
|
</div>
|
|
<div class="admin-tool-review-note p-4 mb-3">
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Escopo liberado</p>
|
|
<ul class="small text-secondary ps-3 mb-0">{access_notes_markup}</ul>
|
|
</div>
|
|
<div class="admin-tool-review-note p-4">
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Como ler esta tela</p>
|
|
<ul class="small text-secondary ps-3 mb-0">{reading_notes_markup}</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
<section class="col-12 col-xl-8 col-xxl-9">
|
|
<div class="card border-0 shadow-sm admin-hero-card overflow-hidden mb-4">
|
|
<div class="card-body p-4 p-lg-5">
|
|
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3">
|
|
<div>
|
|
<div class="d-flex flex-wrap gap-2 mb-3">
|
|
<span class="badge rounded-pill bg-white text-dark border">Observabilidade operacional</span>
|
|
<span class="badge rounded-pill bg-dark-subtle text-dark-emphasis border border-dark-subtle">Fase 4</span>
|
|
</div>
|
|
<h2 class="display-5 fw-semibold mb-3">Fluxo do bot e saude do atendimento na mesma tela</h2>
|
|
<p class="lead text-secondary mb-0">Acompanhe o basico da operacao e da telemetria sem entrar em detalhes de infraestrutura.</p>
|
|
</div>
|
|
<button class="btn btn-outline-dark rounded-pill px-4" type="button" data-admin-bot-monitoring-refresh>
|
|
<span data-bot-monitoring-refresh-label>Atualizar leitura</span>
|
|
<span class="spinner-border spinner-border-sm d-none" data-bot-monitoring-refresh-spinner aria-hidden="true"></span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="alert d-none rounded-4 mb-4" id="admin-bot-monitoring-feedback" role="status"></div>
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-12 col-md-6 col-xxl-3"><div class="card border-0 shadow-sm admin-metric-card h-100"><div class="card-body p-4"><p class="small text-uppercase fw-semibold text-secondary mb-3">Relatorios de fluxo</p><div class="display-6 fw-semibold mb-2" data-bot-flow-report-count>0</div><p class="text-secondary mb-0">Status, roteamento, tools, fallback e falhas do turno.</p></div></div></div>
|
|
<div class="col-12 col-md-6 col-xxl-3"><div class="card border-0 shadow-sm admin-metric-card h-100"><div class="card-body p-4"><p class="small text-uppercase fw-semibold text-secondary mb-3">Relatorios de saude</p><div class="display-6 fw-semibold mb-2" data-bot-telemetry-report-count>0</div><p class="text-secondary mb-0">Volume, latencia, distribuicao por dominio e saude do atendimento.</p></div></div></div>
|
|
<div class="col-12 col-md-6 col-xxl-3"><div class="card border-0 shadow-sm admin-metric-card h-100"><div class="card-body p-4"><p class="small text-uppercase fw-semibold text-secondary mb-3">Bases de leitura</p><div class="display-6 fw-semibold mb-2" data-bot-monitoring-dataset-count>0</div><p class="text-secondary mb-0">Base consolidada usada pelas duas visoes.</p></div></div></div>
|
|
<div class="col-12 col-md-6 col-xxl-3"><div class="card border-0 shadow-sm admin-metric-card h-100"><div class="card-body p-4"><p class="small text-uppercase fw-semibold text-secondary mb-3">Atualizacao</p><div class="h4 fw-semibold mb-2" data-bot-monitoring-sync-strategy>--</div><p class="text-secondary mb-0">Ritmo atual da carga exibida no painel.</p></div></div></div>
|
|
</div>
|
|
<div class="row g-4">
|
|
<div class="col-12 col-xxl-6">
|
|
<div id="bot-flow-monitoring-card" class="card border-0 shadow-sm admin-surface-card h-100">
|
|
<div class="card-body p-4 p-lg-5 d-flex flex-column gap-4">
|
|
<div>
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Fluxo do bot</p>
|
|
<h3 class="h3 fw-semibold mb-2">Triagem da operacao</h3>
|
|
<p class="text-secondary mb-0">Veja status, roteamento, uso de tools, fallback, handoff e falhas do turno.</p>
|
|
</div>
|
|
<div class="admin-bot-monitoring-stack" data-bot-flow-overview-metrics>
|
|
<div class="admin-tool-empty-state rounded-4 p-4"><h4 class="h5 fw-semibold mb-2">Fluxo aguardando leitura</h4><p class="text-secondary mb-0">Clique em atualizar leitura para carregar o snapshot operacional do bot.</p></div>
|
|
</div>
|
|
<div class="admin-bot-monitoring-stack" data-bot-flow-materialization></div>
|
|
<div class="admin-bot-monitoring-grid" data-bot-flow-report-list></div>
|
|
<div>
|
|
<div class="small text-uppercase fw-semibold text-secondary mb-2">Proximas melhorias</div>
|
|
<div class="admin-bot-monitoring-list" data-bot-flow-next-steps></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-12 col-xxl-6">
|
|
<div class="card border-0 shadow-sm admin-surface-card h-100">
|
|
<div class="card-body p-4 p-lg-5 d-flex flex-column gap-4">
|
|
<div>
|
|
<p class="text-uppercase small fw-semibold text-secondary mb-2">Telemetria conversacional</p>
|
|
<h3 class="h3 fw-semibold mb-2">Saude do atendimento</h3>
|
|
<p class="text-secondary mb-0">Volume, latencia, distribuicao por dominio e sinais de saude da conversa.</p>
|
|
</div>
|
|
<div class="admin-bot-monitoring-stack" data-bot-telemetry-overview-metrics>
|
|
<div class="admin-tool-empty-state rounded-4 p-4"><h4 class="h5 fw-semibold mb-2">Telemetria aguardando leitura</h4><p class="text-secondary mb-0">Clique em atualizar leitura para carregar o snapshot conversacional.</p></div>
|
|
</div>
|
|
<div class="admin-bot-monitoring-stack" data-bot-telemetry-materialization></div>
|
|
<div class="admin-bot-monitoring-grid" data-bot-telemetry-report-list></div>
|
|
<div>
|
|
<div class="small text-uppercase fw-semibold text-secondary mb-2">Proximas melhorias</div>
|
|
<div class="admin-bot-monitoring-list" data-bot-telemetry-next-steps></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
<script src="{BOOTSTRAP_JS_HREF}" defer></script>
|
|
<script src="{escape(js_href, quote=True)}" defer></script>
|
|
</body>
|
|
</html>
|
|
'''
|