document.documentElement.dataset.panelReady = "true"; const loginForm = document.querySelector('[data-admin-login-form="true"]'); const reviewBoard = document.querySelector('[data-admin-tool-review-board="true"]'); if (loginForm) { mountLoginForm(loginForm); } if (reviewBoard) { mountToolReviewBoard(reviewBoard); } function mountLoginForm(form) { const feedback = document.getElementById("admin-login-feedback"); const submitButton = form.querySelector('button[type="submit"]'); const submitLabel = form.querySelector("[data-submit-label]"); const submitSpinner = form.querySelector("[data-submit-spinner]"); form.addEventListener("submit", async (event) => { event.preventDefault(); toggleLoading(true); clearFeedback(); const formData = new FormData(form); const payload = { email: String(formData.get("email") || "").trim(), password: String(formData.get("password") || ""), }; try { const response = await fetch(form.dataset.authEndpoint, { method: "POST", credentials: "same-origin", headers: { "Content-Type": "application/json", Accept: "application/json", }, body: JSON.stringify(payload), }); const authBody = await readJson(response); if (!response.ok) { throw new Error(authBody?.detail || "Nao foi possivel autenticar no admin."); } showFeedback("success", authBody?.message || "Sessao administrativa web iniciada com sucesso."); form.reset(); const redirectTo = authBody?.redirect_to || form.dataset.dashboardHref; if (redirectTo) { window.setTimeout(() => { window.location.assign(redirectTo); }, 250); } } catch (error) { showFeedback("danger", error instanceof Error ? error.message : "Erro inesperado durante o login."); } finally { toggleLoading(false); } }); function toggleLoading(isLoading) { submitButton.disabled = isLoading; submitSpinner.classList.toggle("d-none", !isLoading); submitLabel.textContent = isLoading ? "Validando acesso..." : "Entrar no painel"; } function clearFeedback() { feedback.className = "alert d-none mt-4 mb-0 rounded-4"; feedback.textContent = ""; } function showFeedback(variant, message) { feedback.className = `alert alert-${variant} mt-4 mb-0 rounded-4`; feedback.textContent = message; } } function mountToolReviewBoard(board) { const refreshButton = board.querySelector("[data-admin-tool-refresh]"); const refreshLabel = board.querySelector("[data-tool-refresh-label]"); const refreshSpinner = board.querySelector("[data-tool-refresh-spinner]"); const feedback = document.getElementById("admin-tool-review-feedback"); const queueList = board.querySelector("[data-tool-review-queue-list]"); const publicationList = board.querySelector("[data-tool-publication-list]"); const lifecycleList = board.querySelector("[data-tool-contract-lifecycle]"); const parameterTypes = board.querySelector("[data-tool-parameter-types]"); if (refreshButton) { refreshButton.addEventListener("click", () => { void loadBoard(); }); } void loadBoard(); async function loadBoard() { toggleRefreshing(true); clearFeedback(); const overviewResult = await fetchPanelJson(board.dataset.overviewEndpoint); const contractsResult = await fetchPanelJson(board.dataset.contractsEndpoint); const reviewQueueResult = await fetchPanelJson(board.dataset.reviewQueueEndpoint); const publicationsResult = await fetchPanelJson(board.dataset.publicationsEndpoint); if (!overviewResult.ok && !contractsResult.ok && !reviewQueueResult.ok && !publicationsResult.ok) { showFeedback("warning", overviewResult.message || "Entre com uma sessao administrativa web para carregar esta tela."); } if (overviewResult.ok) { renderOverview(overviewResult.body); } if (contractsResult.ok) { renderContracts(contractsResult.body); } else { renderLockedLifecycle(contractsResult.message); } if (reviewQueueResult.ok) { renderReviewQueue(reviewQueueResult.body); } else { renderLockedQueue(reviewQueueResult.message); } if (publicationsResult.ok) { renderPublications(publicationsResult.body); } else { renderLockedPublications(publicationsResult.message); } setText("[data-tool-review-last-sync]", formatNow()); toggleRefreshing(false); } function toggleRefreshing(isLoading) { if (!refreshButton || !refreshLabel || !refreshSpinner) { return; } refreshButton.disabled = isLoading; refreshSpinner.classList.toggle("d-none", !isLoading); refreshLabel.textContent = isLoading ? "Atualizando..." : "Atualizar leitura"; } function clearFeedback() { feedback.className = "alert d-none rounded-4 mb-4"; feedback.textContent = ""; } function showFeedback(variant, message) { feedback.className = `alert alert-${variant} rounded-4 mb-4`; feedback.textContent = message; } function renderOverview(payload) { const workflow = Array.isArray(payload?.workflow) ? payload.workflow : []; const nextSteps = Array.isArray(payload?.next_steps) ? payload.next_steps : []; setText("[data-tool-review-lifecycle-count]", String(workflow.length || 0)); if (nextSteps.length > 0 && !feedback.textContent) { showFeedback("info", `Proximos passos: ${nextSteps[0]}`); } } function renderContracts(payload) { const lifecycle = Array.isArray(payload?.lifecycle_statuses) ? payload.lifecycle_statuses : []; const parameterTypeList = Array.isArray(payload?.parameter_types) ? payload.parameter_types : []; lifecycleList.innerHTML = lifecycle.length > 0 ? lifecycle.map((item) => `
${escapeHtml(item.label)}
${escapeHtml(item.description)}
`).join("") : `
Nenhuma etapa disponivel.
`; parameterTypes.innerHTML = parameterTypeList.length > 0 ? parameterTypeList.map((item) => `${escapeHtml(item.label)}`).join("") : `Sem tipos`; } function renderLockedLifecycle(message) { lifecycleList.innerHTML = `
Leitura indisponivel
${escapeHtml(message || "A sessao atual nao pode ler o contrato compartilhado.")}
`; parameterTypes.innerHTML = `Bloqueado`; } function renderReviewQueue(payload) { const items = Array.isArray(payload?.items) ? payload.items : []; setText("[data-tool-review-queue-count]", String(items.length)); setText("[data-tool-review-queue-mode]", payload?.queue_mode || "Fila web"); queueList.innerHTML = items.length > 0 ? items.map((item) => `
${escapeHtml(item.gate || "revisao")}

${escapeHtml(item.display_name || item.tool_name || "Tool")}

${escapeHtml(item.tool_name || "")}
${escapeHtml(item.status || "pendente")}

${escapeHtml(item.summary || payload?.message || "Item aguardando analise do time.")}

`).join("") : `

Fila sem itens no momento

${escapeHtml(payload?.message || "Nenhuma tool aguardando revisao agora.")}

`; } function renderLockedQueue(message) { setText("[data-tool-review-queue-count]", "0"); setText("[data-tool-review-queue-mode]", "Bloqueado"); queueList.innerHTML = `

Fila indisponivel

${escapeHtml(message || "A sessao atual nao pode acessar a fila de revisao.")}

`; } function renderPublications(payload) { const items = Array.isArray(payload?.publications) ? payload.publications : []; setText("[data-tool-review-publication-count]", String(items.length)); setText("[data-tool-publication-source]", payload?.source || "Catalogo web"); publicationList.innerHTML = items.length > 0 ? items.slice(0, 9).map((item) => `
${escapeHtml(item.domain || "tool")}

${escapeHtml(item.display_name || item.tool_name || "Tool")}

${escapeHtml(item.tool_name || "")}
v${escapeHtml(String(item.version || 1))}

${escapeHtml(item.description || "Publicacao ativa no catalogo do produto.")}

${escapeHtml(item.implementation_module || "")}
`).join("") : `

Catalogo ativo vazio

Nenhuma publicacao ativa retornada pela sessao web.

`; } function renderLockedPublications(message) { setText("[data-tool-review-publication-count]", "0"); setText("[data-tool-publication-source]", "Bloqueado"); publicationList.innerHTML = `

Catalogo protegido

${escapeHtml(message || "A sessao atual nao possui permissao para ler as publicacoes ativas.")}

`; } } async function fetchPanelJson(url) { const response = await fetch(url, { credentials: "same-origin", headers: { Accept: "application/json" }, }); const body = await readJson(response); if (response.ok) { return { ok: true, body }; } const defaultMessage = response.status === 401 ? "Entre com uma sessao administrativa web para visualizar esta area." : body?.detail || "Nao foi possivel carregar os dados desta superficie."; return { ok: false, body, message: defaultMessage }; } async function readJson(response) { try { return await response.json(); } catch { return null; } } function setText(selector, value) { const target = document.querySelector(selector); if (target) { target.textContent = value; } } function formatNow() { return new Date().toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" }); } function escapeHtml(value) { return String(value || "") .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); }