fix: wire TicketsWorkspace.renderPage in app.js (VM122 live)
This commit is contained in:
parent
cb07e3bf88
commit
44dad0063c
1 changed files with 148 additions and 11 deletions
|
|
@ -84,9 +84,26 @@ const views = {
|
|||
};
|
||||
|
||||
function roleLabel(role) {
|
||||
return { super_admin: 'Super Admin', ops_lead: 'Chefe Ops', technician: 'Suporte', noc: 'NOC' }[role] || role;
|
||||
return ROLE_LABELS[role] || role;
|
||||
}
|
||||
|
||||
const ROLE_LABELS = {
|
||||
super_admin: 'Super Admin',
|
||||
ops_lead: 'Chefe Ops',
|
||||
technician: 'Suporte',
|
||||
noc: 'NOC',
|
||||
sales_admin: 'Sales Admin',
|
||||
sales_support: 'Sales Support',
|
||||
finance: 'Financeiro',
|
||||
marketing: 'Marketing',
|
||||
seo: 'SEO',
|
||||
developer: 'Developer',
|
||||
devops: 'DevOps',
|
||||
security_analyst: 'Segurança / SOC',
|
||||
content_editor: 'Conteúdo / CMS',
|
||||
agentic_operator: 'Operador Agentes IA',
|
||||
};
|
||||
|
||||
function statusLabel(status) {
|
||||
return {
|
||||
pending: 'pendente',
|
||||
|
|
@ -1914,7 +1931,7 @@ async function renderTickets(options = {}) {
|
|||
state.tickets = tickets;
|
||||
listEl.innerHTML = state.tickets.length
|
||||
? state.tickets.map(ticketRowHtml).join('')
|
||||
: '<p class="loading">Nenhum ticket nesto filtro</p>';
|
||||
: '<p class="loading">Nenhum ticket neste filtro</p>';
|
||||
listEl.querySelectorAll('.ticket-row').forEach((btn) => {
|
||||
btn.addEventListener('click', () => {
|
||||
state.selectedTicketId = Number(btn.dataset.id);
|
||||
|
|
@ -2568,6 +2585,16 @@ function roleBadgeHtml(role) {
|
|||
ops_lead: 'role-lead',
|
||||
technician: 'role-tech',
|
||||
noc: 'role-noc',
|
||||
sales_admin: 'role-sales-admin',
|
||||
sales_support: 'role-sales-support',
|
||||
finance: 'role-finance',
|
||||
marketing: 'role-marketing',
|
||||
seo: 'role-seo',
|
||||
developer: 'role-developer',
|
||||
devops: 'role-devops',
|
||||
security_analyst: 'role-security',
|
||||
content_editor: 'role-content',
|
||||
agentic_operator: 'role-agentic',
|
||||
}[role] || 'role-default';
|
||||
return `<span class="role-badge ${cls}">${esc(roleLabel(role))}</span>`;
|
||||
}
|
||||
|
|
@ -2582,14 +2609,34 @@ function mfaBadgeHtml(user) {
|
|||
}
|
||||
|
||||
const ROLE_OPTIONS = [
|
||||
{ value: 'super_admin', label: 'Super Admin' },
|
||||
{ value: 'ops_lead', label: 'Chefe Ops' },
|
||||
{ value: 'technician', label: 'Suporte' },
|
||||
{ value: 'noc', label: 'NOC' },
|
||||
{ value: 'super_admin', label: 'Super Admin', group: 'Ops' },
|
||||
{ value: 'ops_lead', label: 'Chefe Ops', group: 'Ops' },
|
||||
{ value: 'technician', label: 'Suporte', group: 'Ops' },
|
||||
{ value: 'noc', label: 'NOC', group: 'Ops' },
|
||||
{ value: 'sales_admin', label: 'Sales Admin', group: 'Comercial' },
|
||||
{ value: 'sales_support', label: 'Sales Support', group: 'Comercial' },
|
||||
{ value: 'finance', label: 'Financeiro', group: 'Negócio' },
|
||||
{ value: 'marketing', label: 'Marketing', group: 'Negócio' },
|
||||
{ value: 'seo', label: 'SEO', group: 'Negócio' },
|
||||
{ value: 'developer', label: 'Developer', group: 'Plataforma' },
|
||||
{ value: 'devops', label: 'DevOps', group: 'Plataforma' },
|
||||
{ value: 'security_analyst', label: 'Segurança / SOC', group: 'Plataforma' },
|
||||
{ value: 'content_editor', label: 'Conteúdo / CMS', group: 'Plataforma' },
|
||||
{ value: 'agentic_operator', label: 'Operador Agentes IA', group: 'Plataforma' },
|
||||
];
|
||||
|
||||
const ASSIGNABLE_ROLE_OPTIONS = ROLE_OPTIONS.filter((r) => r.value !== 'super_admin');
|
||||
|
||||
function registrationRoleSelectHtml(selected = 'technician') {
|
||||
const groups = [...new Set(ASSIGNABLE_ROLE_OPTIONS.map((r) => r.group))];
|
||||
return groups.map((group) => {
|
||||
const opts = ASSIGNABLE_ROLE_OPTIONS.filter((r) => r.group === group)
|
||||
.map((r) => `<option value="${r.value}" ${r.value === selected ? 'selected' : ''}>${esc(r.label)}</option>`)
|
||||
.join('');
|
||||
return `<optgroup label="${esc(group)}">${opts}</optgroup>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function roleSelectHtml(username, current, assignableOnly = true) {
|
||||
const options = assignableOnly && current !== 'super_admin'
|
||||
? ASSIGNABLE_ROLE_OPTIONS
|
||||
|
|
@ -2961,7 +3008,7 @@ async function renderModules() {
|
|||
}
|
||||
}
|
||||
|
||||
const REG_ROLE_LABELS = { ops_lead: 'Chefe Ops (admin)', technician: 'Técnico', noc: 'NOC' };
|
||||
const REG_ROLE_LABELS = ROLE_LABELS;
|
||||
|
||||
async function renderMessages() {
|
||||
const el = document.getElementById('messages-content');
|
||||
|
|
@ -2986,9 +3033,7 @@ async function renderMessages() {
|
|||
</div>
|
||||
<label>Perfil a atribuir
|
||||
<select class="req-role">
|
||||
<option value="ops_lead">Chefe Ops (admin)</option>
|
||||
<option value="technician" selected>Técnico</option>
|
||||
<option value="noc">NOC</option>
|
||||
${registrationRoleSelectHtml('technician')}
|
||||
</select>
|
||||
</label>
|
||||
<div class="actions" style="margin-top:0.75rem">
|
||||
|
|
@ -3001,7 +3046,7 @@ async function renderMessages() {
|
|||
<tr>
|
||||
<td>${esc(r.email)}</td>
|
||||
<td><span class="badge ${r.status === 'active' ? 'ok' : r.status === 'rejected' ? 'closed' : 'review'}">${esc(statusLabel(r.status))}</span></td>
|
||||
<td>${esc(r.role || '—')}</td>
|
||||
<td>${esc(r.role ? roleLabel(r.role) : '—')}</td>
|
||||
<td>${fmtDate(r.updated_at || r.created_at)}</td>
|
||||
</tr>`).join('');
|
||||
el.innerHTML = `
|
||||
|
|
@ -3356,6 +3401,90 @@ function showSocWebhookTestError(err) {
|
|||
modal.setAttribute('aria-hidden', 'false');
|
||||
}
|
||||
|
||||
function showOpenPanelTestResult(result) {
|
||||
const modal = document.getElementById('soc-test-modal');
|
||||
const title = document.getElementById('soc-test-modal-title');
|
||||
const sub = document.getElementById('soc-test-modal-sub');
|
||||
const body = document.getElementById('soc-test-modal-body');
|
||||
if (!modal || !body) return;
|
||||
|
||||
const ok = result.ok === true;
|
||||
title.textContent = ok ? 'OpenPanel API — confirmado' : 'OpenPanel API — falha';
|
||||
sub.textContent = `Spec 028 · ${result.steps_passed || 0}/${result.steps_total || 0} passos · ${result.duration_sec || '—'}s`;
|
||||
|
||||
const steps = (result.steps || []).map((s) => `
|
||||
<li class="badge ${s.ok ? 'ok' : 'escalated'}">
|
||||
<strong>${esc(s.name)}</strong> — ${esc(s.detail || (s.ok ? 'OK' : 'FAIL'))}
|
||||
</li>`).join('');
|
||||
|
||||
body.innerHTML = `
|
||||
<div class="soc-test-result">
|
||||
<div class="soc-test-status ${ok ? 'soc-test-status--ok' : 'soc-test-status--fail'}">
|
||||
<span class="soc-sev ${ok ? 'soc-sev--low' : 'soc-sev--high'}"></span>
|
||||
${esc(result.message || (ok ? 'Multidomínio OK' : 'Falha'))}
|
||||
</div>
|
||||
<ul class="soc-alerts" style="list-style:none;padding:0;margin:0.75rem 0;display:flex;flex-direction:column;gap:0.35rem">${steps || '<li>—</li>'}</ul>
|
||||
<p class="soc-test-hint">
|
||||
Suite <code>openpanel-multidomain-api-confirm</code> — provisiona 2 contas temporárias
|
||||
(2 domínios na plataforma), valida listagem e remove. Pode executar quantas vezes quiser.
|
||||
Script CLI: <code>scripts/test-openpanel-multidomain-api.sh</code>
|
||||
</p>
|
||||
<div class="soc-test-actions">
|
||||
<button type="button" class="soc-btn soc-btn--ghost" data-close-soc-test-modal>Fechar</button>
|
||||
</div>
|
||||
</div>`;
|
||||
body.querySelector('[data-close-soc-test-modal]')?.addEventListener('click', closeSocTestModal);
|
||||
modal.classList.remove('hidden');
|
||||
modal.setAttribute('aria-hidden', 'false');
|
||||
}
|
||||
|
||||
function showOpenPanelTestError(err) {
|
||||
const modal = document.getElementById('soc-test-modal');
|
||||
const title = document.getElementById('soc-test-modal-title');
|
||||
const sub = document.getElementById('soc-test-modal-sub');
|
||||
const body = document.getElementById('soc-test-modal-body');
|
||||
if (!modal || !body) return;
|
||||
|
||||
const msg = err?.message || String(err);
|
||||
const is403 = /403|permiss/i.test(msg);
|
||||
title.textContent = 'OpenPanel API — erro';
|
||||
sub.textContent = 'Teste não concluído';
|
||||
body.innerHTML = `
|
||||
<div class="soc-test-result">
|
||||
<div class="soc-test-status soc-test-status--fail">
|
||||
<span class="soc-sev soc-sev--high"></span>
|
||||
${esc(msg)}
|
||||
</div>
|
||||
${is403 ? '<p class="soc-test-hint">Perfis: <strong>super_admin</strong>, <strong>devops</strong>, <strong>developer</strong>.</p>' : ''}
|
||||
<div class="soc-test-actions">
|
||||
<button type="button" class="soc-btn soc-btn--ghost" data-close-soc-test-modal>Fechar</button>
|
||||
</div>
|
||||
</div>`;
|
||||
body.querySelector('[data-close-soc-test-modal]')?.addEventListener('click', closeSocTestModal);
|
||||
modal.classList.remove('hidden');
|
||||
modal.setAttribute('aria-hidden', 'false');
|
||||
}
|
||||
|
||||
async function runOpenPanelApiTest() {
|
||||
const btn = document.getElementById('btn-test-openpanel-api');
|
||||
const prevLabel = btn?.textContent;
|
||||
if (btn) {
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Testando…';
|
||||
}
|
||||
try {
|
||||
const r = await api('/v1/vm123/openpanel/test-confirm', { method: 'POST' });
|
||||
showOpenPanelTestResult(r);
|
||||
} catch (ex) {
|
||||
showOpenPanelTestError(ex);
|
||||
} finally {
|
||||
if (btn) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = prevLabel || 'Testar multidomínio';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function runWebhookIntegrationTest(refreshView) {
|
||||
const btn = document.getElementById('soc-btn-test') || document.getElementById('btn-test-webhook');
|
||||
const prevLabel = btn?.textContent;
|
||||
|
|
@ -3726,6 +3855,13 @@ async function renderInfra() {
|
|||
</div>
|
||||
<p class="health-card-hint">Alerta se gap > ${health.webhook_gap_alert_minutes || 15} min sem eventos VM112.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>OpenPanel API — Re-engenharia Ligbox</h3>
|
||||
<p class="health-card-hint">Spec 028 · VM123 bridge :18087 · multidomínio · conta temporária com cleanup automático.</p>
|
||||
<div class="actions">
|
||||
<button type="button" class="btn secondary" id="btn-test-openpanel-api">Testar multidomínio</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>VM112 — Portal Onboard</h3>
|
||||
<dl class="kv">
|
||||
|
|
@ -3746,6 +3882,7 @@ async function renderInfra() {
|
|||
</div>`;
|
||||
document.getElementById('btn-refresh-health')?.addEventListener('click', () => renderInfra());
|
||||
document.getElementById('btn-test-webhook')?.addEventListener('click', () => runWebhookIntegrationTest('infra'));
|
||||
document.getElementById('btn-test-openpanel-api')?.addEventListener('click', () => runOpenPanelApiTest());
|
||||
} catch (e) {
|
||||
el.innerHTML = `<p class="loading">Erro: ${esc(e.message)}</p>`;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue