(function () { 'use strict'; const esc = (s) => String(s ?? '').replace(/&/g, '&').replace(//g, '>'); const LEVEL_SHORT = { full: 'ADM', read: 'READ', link: 'LINK', api: 'API', system: 'SYS', none: '—', }; const state = { data: null, tab: 'overview', selectedRole: 'super_admin', softwareFilter: 'all', saving: false, saveError: null, moduleDraft: null, bindingDraft: null, }; async function crudApi(method, path, body) { const opts = { method, headers: authHeaders({ 'Content-Type': 'application/json' }), }; if (body !== undefined) opts.body = JSON.stringify(body); const r = await fetchWithTimeout(`/api/v1${path}`, opts); if (!r.ok) throw new Error(`${r.status} ${(await r.text()).slice(0, 200)}`); if (method === 'DELETE' || r.status === 204) return {}; const ct = r.headers.get('content-type') || ''; if (ct.includes('json')) return r.json(); return r.text(); } async function reloadMatrix() { state.data = await api('/rbac/matrix'); state.moduleDraft = null; state.bindingDraft = null; } function selectedRoleMeta() { return state.data?.catalog?.roles?.[state.selectedRole] || {}; } function canCrud() { return !!state.data?.crud_enabled; } function isRoleLocked() { return !!selectedRoleMeta().locked; } async function handleNewRole() { const id = window.prompt('ID da função (slug, ex.: billing_analyst):'); if (!id) return; const label = window.prompt('Nome exibido (pt-BR):', id.replace(/_/g, ' ')); if (!label) return; state.saving = true; state.saveError = null; try { await crudApi('POST', '/rbac/roles', { id: id.trim().toLowerCase(), label: label.trim(), category: 'custom', description: '', }); await reloadMatrix(); state.selectedRole = id.trim().toLowerCase(); const root = document.getElementById('access-matrix-content'); if (root) paint(root); } catch (e) { state.saveError = e.message; const root = document.getElementById('access-matrix-content'); if (root) paint(root); } finally { state.saving = false; } } async function handleCloneRole() { const newId = window.prompt(`Copiar ${state.selectedRole} — novo ID (slug):`); if (!newId) return; const newLabel = window.prompt('Nome da cópia:', `${selectedRoleMeta().label || state.selectedRole} (cópia)`); if (!newLabel) return; state.saving = true; try { await crudApi('POST', `/rbac/roles/${encodeURIComponent(state.selectedRole)}/clone`, { new_id: newId.trim().toLowerCase(), new_label: newLabel.trim(), }); await reloadMatrix(); state.selectedRole = newId.trim().toLowerCase(); const root = document.getElementById('access-matrix-content'); if (root) paint(root); } catch (e) { state.saveError = e.message; const root = document.getElementById('access-matrix-content'); if (root) paint(root); } finally { state.saving = false; } } async function handleFreezeRole() { const meta = selectedRoleMeta(); const next = meta.status === 'frozen' ? 'active' : 'frozen'; const msg = next === 'frozen' ? 'Pausar esta função? Novas atribuições serão bloqueadas.' : 'Reactivar função?'; if (!window.confirm(msg)) return; state.saving = true; try { await crudApi('PATCH', `/rbac/roles/${encodeURIComponent(state.selectedRole)}/status`, { status: next }); await reloadMatrix(); const root = document.getElementById('access-matrix-content'); if (root) paint(root); } catch (e) { state.saveError = e.message; const root = document.getElementById('access-matrix-content'); if (root) paint(root); } finally { state.saving = false; } } async function handleDeleteRole() { if (!window.confirm(`Arquivar função ${state.selectedRole}? Falha se houver utilizadores activos.`)) return; state.saving = true; try { await crudApi('DELETE', `/rbac/roles/${encodeURIComponent(state.selectedRole)}`); await reloadMatrix(); state.selectedRole = state.data.role_columns?.[0] || 'super_admin'; const root = document.getElementById('access-matrix-content'); if (root) paint(root); } catch (e) { state.saveError = e.message; const root = document.getElementById('access-matrix-content'); if (root) paint(root); } finally { state.saving = false; } } function handleReportDownload() { const token = localStorage.getItem('desk_token') || sessionStorage.getItem('desk_token'); const url = `/api/v1/rbac/roles/${encodeURIComponent(state.selectedRole)}/report.csv`; fetch(url, { headers: authHeaders() }) .then((r) => { if (!r.ok) throw new Error(String(r.status)); return r.blob(); }) .then((blob) => { const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `matriz-${state.selectedRole}.csv`; a.click(); }) .catch((e) => { state.saveError = e.message; paint(document.getElementById('access-matrix-content')); }); } async function saveModuleDraft() { if (!state.moduleDraft) return; state.saving = true; try { await crudApi('PUT', `/rbac/roles/${encodeURIComponent(state.selectedRole)}/modules`, { modules: state.moduleDraft, }); await reloadMatrix(); const root = document.getElementById('access-matrix-content'); if (root) paint(root); } catch (e) { state.saveError = e.message; const root = document.getElementById('access-matrix-content'); if (root) paint(root); } finally { state.saving = false; } } async function saveBindingDraft() { if (!state.bindingDraft) return; state.saving = true; try { await crudApi('PUT', `/rbac/roles/${encodeURIComponent(state.selectedRole)}/bindings`, { bindings: state.bindingDraft, }); await reloadMatrix(); const root = document.getElementById('access-matrix-content'); if (root) paint(root); } catch (e) { state.saveError = e.message; const root = document.getElementById('access-matrix-content'); if (root) paint(root); } finally { state.saving = false; } } function renderToolbar() { if (!canCrud()) return ''; const meta = selectedRoleMeta(); const frozen = meta.status === 'frozen'; return `
`; } async function patchApi(path, body) { const r = await fetchWithTimeout(`/api/v1${path}`, { method: 'PATCH', headers: authHeaders({ 'Content-Type': 'application/json' }), body: JSON.stringify(body), }); if (!r.ok) throw new Error(`${r.status} ${(await r.text()).slice(0, 200)}`); return r.json(); } function bindingMeta(agentId, roleId, relation) { const b = state.data?.agent_bindings_matrix?.agents?.[agentId]?.[roleId]?.[relation]; return { enabled: !!b?.enabled, locked: !!b?.locked, }; } function capMeta(capId, roleId) { const c = state.data?.agent_bindings_matrix?.caps?.[capId]?.[roleId]; return { enabled: !!c?.enabled, locked: !!c?.locked }; } async function toggleBinding(agentId, roleId, relation) { const meta = bindingMeta(agentId, roleId, relation); if (meta.locked) return; state.saving = true; state.saveError = null; try { await patchApi('/rbac/agent-bindings', { agent_id: agentId, role_id: roleId, relation, enabled: !meta.enabled, }); state.data = await api('/rbac/matrix'); const root = document.getElementById('access-matrix-content'); if (root) paint(root); } catch (e) { state.saveError = e.message; const root = document.getElementById('access-matrix-content'); if (root) paint(root); } finally { state.saving = false; } } async function toggleCap(capId, roleId) { const meta = capMeta(capId, roleId); if (meta.locked) return; state.saving = true; state.saveError = null; try { await patchApi('/rbac/agent-governance', { cap_id: capId, role_id: roleId, enabled: !meta.enabled, }); state.data = await api('/rbac/matrix'); const root = document.getElementById('access-matrix-content'); if (root) paint(root); } catch (e) { state.saveError = e.message; const root = document.getElementById('access-matrix-content'); if (root) paint(root); } finally { state.saving = false; } } async function api(path) { const r = await fetchWithTimeout(`/api/v1${path}`, { headers: authHeaders({ 'Content-Type': 'application/json' }), }); if (!r.ok) throw new Error(`${r.status} ${(await r.text()).slice(0, 200)}`); return r.json(); } function levelMeta(level) { return state.data.legend[level] || state.data.legend.none; } function badgeHtml(level, opts = {}) { const meta = levelMeta(level); const short = LEVEL_SHORT[meta.access] || '—'; const cls = opts.compact ? ' am-badge--compact' : ''; return `${short}`; } function serviceLabel(svc) { return state.data.service_labels?.[svc] || svc; } function rolesByCategory(catalog, categoryLabels) { const groups = {}; Object.values(catalog.roles || {}).forEach((role) => { const cat = role.category || 'platform'; if (!groups[cat]) groups[cat] = []; groups[cat].push(role); }); const order = ['ops', 'commercial', 'business', 'platform', 'custom', 'system']; return order .filter((c) => groups[c]?.length) .map((c) => ({ id: c, label: categoryLabels[c] || c, roles: groups[c] })); } function roleDeskRows() { const roleId = state.selectedRole; return (state.data.desk_modules || []).map((row) => ({ ...row, level: row.levels[roleId] || 'none', })).filter((r) => r.level !== 'none'); } function renderRoleSidebar() { const groups = rolesByCategory(state.data.catalog, state.data.category_labels || {}); return groups.map((g) => ` `).join(''); } function renderScopeBar() { return `
${(state.data.scope_layers || []).map((l) => `
${esc(l.label)} ${esc(l.host)} ${esc(l.desc)}
`).join('')}
`; } function renderMatrixTable(rows, rowLabel, idKey) { const { role_columns, legend } = state.data; const cols = role_columns || []; const head = cols.map((c) => { const label = state.data.catalog.roles[c]?.label || c; const hl = c === state.selectedRole ? ' am-col-active' : ''; return `${esc(label.split(' ')[0])}`; }).join(''); const body = rows.map((row) => { const cells = cols.map((role) => { const lv = row.levels[role] || 'none'; const tdHl = role === state.selectedRole ? ' am-row-highlight' : ''; return `${badgeHtml(lv)}`; }).join(''); const sub = row.product ? `${esc(row.product)}` : `${esc(row[idKey] || '')}`; return ` ${esc(row.label)} ${sub} ${cells}`; }).join(''); return `
${head}${body}
${esc(rowLabel)}
`; } function renderDeskMatrix() { if (canCrud() && !isRoleLocked()) { if (!state.moduleDraft) { const draft = {}; (state.data.desk_modules || []).forEach((row) => { draft[row.id] = row.levels[state.selectedRole] || 'none'; }); state.moduleDraft = draft; } const levels = ['full', 'read', 'api', 'link', 'none']; const rows = (state.data.desk_modules || []).map((row) => ` ${esc(row.label)} ${esc(row.id)} `).join(''); return `

Editor de módulos Desk — função ${esc(roleLabel(state.selectedRole))}

${rows}
MóduloAcesso

Grelha completa (todas as funções)

${renderMatrixTable(state.data.desk_modules || [], 'Módulo Desk', 'id')}`; } return `

Grelha completa — módulos internos do Desk (VM122).

${renderMatrixTable(state.data.desk_modules || [], 'Módulo Desk', 'id')}`; } function renderBindingCards(bindings, title) { if (!bindings.length) { return `

${title ? esc(title) + ' — ' : ''}sem registos para esta função.

`; } const byService = {}; bindings.forEach((b) => { if (!byService[b.service]) byService[b.service] = []; byService[b.service].push(b); }); return ` ${title ? `

${esc(title)}

` : ''}
${Object.entries(byService).flatMap(([svc, items]) => items.map((b) => `
${esc(serviceLabel(svc))} ${badgeHtml(b.access || 'full', { compact: true })}
${esc(b.type || 'binding')}
${esc(b.value)}
`) ).join('')}
`; } function renderSoftwareCards(rows, title) { if (!rows.length) { return `

${esc(title || 'Software')} — sem acesso directo.

`; } return ` ${title ? `

${esc(title)}

` : ''}
${rows.map((r) => { const meta = levelMeta(r.level); return `
${esc(r.label)} ${esc(r.product)}
${badgeHtml(r.level, { compact: true })}

${esc(r.group)}

${r.host && r.host !== '—' ? `${esc(r.host)}` : ''}
`; }).join('')}
`; } function renderDeskChips() { const rows = roleDeskRows(); if (!rows.length) { return '

Sem módulos Desk activos para esta função.

'; } return `
${rows.map((r) => ` ${esc(r.label)} ${badgeHtml(r.level, { compact: true })} `).join('')}
`; } function renderRoleStats(role) { const sw = state.data.role_software_summaries?.[state.selectedRole] || []; const bindings = role?.bindings || []; const desk = roleDeskRows(); return `
${desk.length}Módulos Desk
${sw.length}Software
${bindings.length}APIs / Grupos
${(state.data.role_agents_summaries?.[state.selectedRole] || []).length}Agentes
`; } function renderRoleOverview() { const role = state.data.catalog.roles[state.selectedRole]; const sw = state.data.role_software_summaries?.[state.selectedRole] || []; const bindings = role?.bindings || []; return ` ${renderScopeBar()} ${renderRoleStats(role)}

Desk VM122 — módulos activos

${renderDeskChips()}
${renderSoftwareCards(sw, 'Software & infra em escopo')}
${renderBindingCards(bindings, 'APIs, grupos Odoo e permissões')}

Agentics — capacidades da função

${renderRoleAgentCaps()} ${renderRoleAgentsSummary('')}
`; } function renderSoftwareMatrix() { const { software_groups } = state.data; const groups = (software_groups || []).filter((g) => state.softwareFilter === 'all' || g.id === state.softwareFilter ); const filters = [ { id: 'all', label: 'Todas as camadas' }, ...(software_groups || []).map((g) => ({ id: g.id, label: g.label.split('—')[0].trim() })), ]; const filterBar = `
${filters.map((f) => ` `).join('')}
`; const sections = groups.map((grp) => { const meta = [grp.host, grp.url].filter(Boolean).join(' · '); return `

${esc(grp.label)}

${meta ? `${esc(meta)}` : ''}
${renderMatrixTable(grp.items || [], 'Recurso', 'id')}
`; }).join(''); const sw = state.data.role_software_summaries?.[state.selectedRole] || []; return `

Matriz de software fora do Desk — VM112 (onboard/mail), VM123 (finance/hosting) e consolas de infra.

${renderSoftwareCards(sw, 'Resumo da função seleccionada')} ${filterBar}${sections}`; } function renderBindings() { const role = state.data.catalog.roles[state.selectedRole]; if (!role) return '

Função não encontrada

'; let editor = ''; if (canCrud() && !isRoleLocked()) { if (!state.bindingDraft) { state.bindingDraft = (role.bindings || []).map((b) => ({ ...b })); } const rows = state.bindingDraft.map((b, i) => ` `).join(''); editor = `

Editor bindings

${rows}
ServiçoTipoValorAcesso
`; } const groups = (state.data.external_groups || []).map((g) => `
  • ${esc(g.id)} — ${esc(g.label)} ${esc(g.service)}
  • ` ).join(''); return ` ${editor}

    Bindings estilo Odoo — grupos, roles e permissões provisionados por função.

    ${renderBindingCards(role.bindings || [], '')} ${groups ? `

    Grupos externos registados

    ` : ''}`; } function renderAudit() { const entries = state.data.role_audit || []; if (!entries.length) return '

    Sem alterações registadas ainda.

    '; return `
    ${entries.map((e) => `
    ${esc(e.action)} · ${esc(e.entity_type)} ${esc(e.entity_id)}

    ${esc(e.actor)} · ${esc(e.created_at)}

    `).join('')}
    `; } function roleLabel(roleId) { return state.data.catalog.roles[roleId]?.label || roleId; } function renderRelationChip(rel) { const labels = state.data.agent_relation_labels || { approve: 'Aprova', focus: 'Operador', ui: 'UI', }; return `${esc(labels[rel] || rel)}`; } function renderRoleChips(roleIds) { return (roleIds || []).map((r) => `${esc(roleLabel(r))}` ).join(''); } function renderAgentGovernanceLegend() { const labels = state.data.agent_relation_labels || {}; return `
    ${Object.entries(labels).map(([k, v]) => `${renderRelationChip(k)} ${esc(v)}` ).join('')}
    `; } function renderRoleAgentCaps() { const capsDef = [ ['use_ui', 'UI Agentics'], ['trigger_runs', 'Disparar cenários'], ['approve_runbooks', 'Aprovar runbooks'], ['configure_models', 'Configurar LLM'], ]; const roleId = state.selectedRole; if (!state.data.editable) { const caps = state.data.role_agentic_caps?.[roleId] || []; if (!caps.length) { return '

    Esta função não acede ao módulo Agentics.

    '; } return `
    ${caps.map((c) => `${esc(c.label)}`).join('')}
    `; } return `
    ${capsDef.map(([id, label]) => { const meta = capMeta(id, roleId); return ``; }).join('')}
    `; } function renderRoleAgentsSummary(title) { const rows = state.data.role_agents_summaries?.[state.selectedRole] || []; if (!rows.length) { return `

    ${esc(title || 'Agentes')} — sem interacção directa.

    `; } return ` ${title ? `

    ${esc(title)}

    ` : ''}
    ${rows.map((a) => `
    ${esc(a.id)} ${esc(a.name)}

    ${esc(a.role)}

    ${(a.relations || []).map(renderRelationChip).join('')}
    `).join('')}
    `; } function renderAgentCard(a) { const rels = a.role_relations?.[state.selectedRole] || []; const hit = rels.length > 0; return `
    ${esc(a.id)}
    ${esc(a.name)} ${esc(a.codename)}

    ${esc(a.role)}

    ${hit ? `
    ${rels.map(renderRelationChip).join('')} esta função
    ` : ''}
    Aprova ${renderRoleChips(a.approvers)}
    Operadores ${renderRoleChips(a.operators)}
    ${(a.reads || []).length ? `
    ${esc(a.reads.slice(0, 3).join(' · '))}
    ` : ''}
    `; } function renderRelationToggle(agentId, roleId, relation) { const meta = bindingMeta(agentId, roleId, relation); const short = { ui: 'UI', focus: 'OP', approve: 'AP' }[relation] || relation; return ``; } function renderAgentMatrixCell(agentId, role, rels) { const tdHl = role === state.selectedRole ? ' am-row-highlight' : ''; if (!state.data.editable) { const inner = rels.length ? rels.map(renderRelationChip).join('') : ''; return `${inner}`; } return ` ${renderRelationToggle(agentId, role, 'ui')} ${renderRelationToggle(agentId, role, 'focus')} ${renderRelationToggle(agentId, role, 'approve')} `; } function renderAgentsMatrix() { const agents = state.data.agents || []; const cols = state.data.role_columns || []; const head = cols.map((c) => { const hl = c === state.selectedRole ? ' am-col-active' : ''; return `${esc(roleLabel(c).split(' ')[0])}`; }).join(''); const body = agents.map((a) => { const cells = cols.map((role) => { const rels = a.role_relations?.[role] || []; return renderAgentMatrixCell(a.id, role, rels); }).join(''); return ` ${esc(a.id)} · ${esc(a.name)} ${esc(a.role)} ${cells}`; }).join(''); return `

    Matriz agente × função

    ${renderAgentGovernanceLegend()}
    ${head}${body}
    Agente
    `; } function renderAgents() { const caps = state.data.role_agentic_caps?.[state.selectedRole] || []; return `

    Agentes A0–A7 usam conta agent_system. ${state.data.editable ? 'Clique UI / OP / AP para ligar ou desligar atribuições (audit activo).' : 'Três relações: UI · Operador · Aprova.'}

    ${state.saveError ? `

    ${esc(state.saveError)}

    ` : ''} ${(state.data.editable || caps.length) ? `

    Capacidades — ${esc(roleLabel(state.selectedRole))}

    ${renderRoleAgentCaps()}
    ` : ''} ${renderAgentsMatrix()}

    Detalhe por agente

    ${(state.data.agents || []).map(renderAgentCard).join('')}
    `; } function renderLegend() { const legend = state.data.legend || {}; return Object.entries(legend).map(([key, l]) => `${badgeHtml(key, { compact: true })} ${esc(l.label)}` ).join(''); } function activeTabHint() { const tab = (state.data.tabs || []).find((t) => t.id === state.tab); return tab?.hint || ''; } function renderMainPanel() { if (state.tab === 'access-control') { return `
    `; } if (state.tab === 'quem-faz-o-que') { return `
    `; } const role = state.data.catalog.roles[state.selectedRole]; let body = ''; if (state.tab === 'overview') body = renderRoleOverview(); else if (state.tab === 'vm122') body = renderDeskMatrix(); else if (state.tab === 'software') body = renderSoftwareMatrix(); else if (state.tab === 'bindings') body = renderBindings(); else if (state.tab === 'agents') body = renderAgents(); else if (state.tab === 'audit') body = renderAudit(); return `
    Spec 027 · RBAC

    ${esc(role?.label || state.selectedRole)}

    ${esc(role?.description || '')}

    ${activeTabHint() ? `

    ${esc(activeTabHint())}

    ` : ''}
    ${renderLegend()}
    ${body}
    `; } function renderTabs() { return (state.data.tabs || []).map((t) => { const sep = t.separated ? '' : ''; const extra = t.id === 'access-control' ? ' am-tab--access-control' : (t.id === 'quem-faz-o-que' ? ' am-tab--quem-faz-o-que' : ''); return `${sep}`; }).join(''); } function bindEvents(root) { root.querySelectorAll('[data-am-role]').forEach((btn) => { btn.addEventListener('click', () => { state.selectedRole = btn.dataset.amRole; state.moduleDraft = null; state.bindingDraft = null; window.DeskExecutiveMap?.resetFilters?.(); paint(root); }); }); root.querySelectorAll('[data-am-tab]').forEach((btn) => { btn.addEventListener('click', () => { state.tab = btn.dataset.amTab; if (typeof window.getDeskState === 'function') { const ds = window.getDeskState(); if (ds) ds.matrixTab = state.tab; } paint(root); }); }); root.querySelectorAll('[data-am-sw-filter]').forEach((btn) => { btn.addEventListener('click', () => { state.softwareFilter = btn.dataset.amSwFilter; paint(root); }); }); root.querySelectorAll('[data-am-toggle]').forEach((btn) => { btn.addEventListener('click', () => { if (state.saving || !state.data?.editable) return; const [agentId, roleId, relation] = btn.dataset.amToggle.split('|'); toggleBinding(agentId, roleId, relation); }); }); root.querySelectorAll('[data-am-cap]').forEach((btn) => { btn.addEventListener('click', () => { if (state.saving || !state.data?.editable) return; const [capId, roleId] = btn.dataset.amCap.split('|'); toggleCap(capId, roleId); }); }); root.querySelectorAll('[data-am-action]').forEach((btn) => { btn.addEventListener('click', () => { const a = btn.dataset.amAction; if (a === 'new-role') handleNewRole(); if (a === 'clone-role') handleCloneRole(); if (a === 'freeze-role') handleFreezeRole(); if (a === 'delete-role') handleDeleteRole(); if (a === 'report-csv') handleReportDownload(); if (a === 'save-modules') saveModuleDraft(); if (a === 'save-bindings') saveBindingDraft(); if (a === 'add-binding') { state.bindingDraft = state.bindingDraft || []; state.bindingDraft.push({ service: 'desk', type: 'permission', value: 'read_tickets', access: 'read' }); paint(root); } }); }); root.querySelectorAll('[data-am-mod]').forEach((sel) => { sel.addEventListener('change', () => { if (!state.moduleDraft) return; state.moduleDraft[sel.dataset.amMod] = sel.value; }); }); root.querySelectorAll('.am-bind-in').forEach((inp) => { inp.addEventListener('change', () => { const i = Number(inp.dataset.amBind); const field = inp.dataset.field; if (!state.bindingDraft?.[i]) return; state.bindingDraft[i][field] = inp.value; }); }); root.querySelectorAll('[data-am-rm-bind]').forEach((btn) => { btn.addEventListener('click', () => { const i = Number(btn.dataset.amRmBind); state.bindingDraft.splice(i, 1); paint(root); }); }); } function paint(root) { root.innerHTML = `
    ${state.data?.crud_enabled ? 'CRUD · audit ON' : state.data?.editable ? 'Edição · audit ON' : 'Consulta'}${state.saving ? ' · …' : ''}
    ${renderToolbar()} ${state.saveError ? `

    ${esc(state.saveError)}

    ` : ''}
    ${renderMainPanel()}
    `; bindEvents(root); window.DeskTopnav?.remountMatrixRoles?.(); if (state.tab === 'access-control') { const host = root.querySelector('#am-access-control-host'); const roleMeta = state.data?.catalog?.roles?.[state.selectedRole]; window.DeskAccessControlPanel?.paint?.(host, { selectedRole: state.selectedRole, roleMeta, catalog: state.data?.catalog, editable: !!state.data?.editable, }); } if (state.tab === 'quem-faz-o-que') { const host = root.querySelector('#am-executive-map-host'); window.DeskExecutiveMap?.paint?.(host, { selectedRole: state.selectedRole }); } } async function renderAccessMatrix() { const root = document.getElementById('access-matrix-content'); if (!root) return; if (typeof ensureValidSession === 'function' && !(await ensureValidSession())) return; root.innerHTML = '

    Carregando Matriz de Acesso…

    '; try { state.data = await api('/rbac/matrix'); if (!state.selectedRole && state.data.role_columns?.length) { state.selectedRole = state.data.role_columns[0]; } const tabIds = (state.data.tabs || []).map((t) => t.id); if (typeof window.getDeskState === 'function') { const ds = window.getDeskState(); if (ds?.matrixTab && tabIds.includes(ds.matrixTab)) { state.tab = ds.matrixTab; } } if (!tabIds.includes(state.tab) && tabIds.length) state.tab = tabIds[0]; paint(root); } catch (e) { root.innerHTML = `

    Matriz indisponível: ${esc(e.message)}

    `; } } window.renderAccessMatrix = renderAccessMatrix; window.DeskAccessMatrix = { renderAccessMatrix, getState: () => state, }; })();