Empresa ativa
Selecione uma empresa
Dashboard
Empresas cadastradas
Dados RBAC são isolados por empresa
Usuários da Plataforma
Contas de acesso ao Portal de Gerenciamento de RBAC
NomeE-mailPerfilProviderStatusÚltimo acesso
Acessar como Usuário
Visualize a plataforma com as permissões de outro usuário
NomeE-mailPerfilEmpresasStatus
`; const secConflitos = conf.length ? `

Conflitos de Acesso Identificados (${conf.length})

${conf.map(c=>``).join('')}
SistemaPerfil APerfil B
${esc(c.sistema)}${esc(c.a)}${esc(c.b)}
` : `

Conflitos de Acesso

✓ Nenhum conflito identificado.

`; const html = `Relatório RBAC — ${esc(emp)}
${logoBase64?`OwlID`:'OwlID'}
${esc(emp)}
Gerado em ${esc(now)}

Relatório de Governança RBAC

Empresa: ${esc(emp)} · Gerado em ${esc(now)}

Sistemas
${sistemas.length}
Sistemas Críticos
${sistemas.filter(s=>s.criticidade==='1-Crítico').length}
Perfis de Acesso
${acessos.length}
Perfis Críticos
${acessos.filter(a=>a.critico==='Sim').length}
Funções
${funcoes.length}
Entradas RBAC
${rbac.length}
Conflitos
${conf.length}
${secSistemas}${secAcessos}${secFuncoes}${secRbac}${secConflitos} `; const blob = new Blob([html], {type:'text/html;charset=utf-8'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `relatorio-rbac-${emp.replace(/\s+/g,'-').toLowerCase()}-${new Date().toISOString().slice(0,10)}.html`; a.click(); URL.revokeObjectURL(url); toast('Relatório exportado com sucesso.'); } catch(e) { toast(e.message, 'error'); } } // ===== CONFLITOS ===== async function loadConflitos(){ try{ if(!cache.acessos.length) cache.acessos=await GET('/acessos?empresaId='+currentEmpresaId); const pairs=getConflictPairs(),el=document.getElementById('conflitos-list'); setBadge('badge-conflitos',pairs.length); el.innerHTML=pairs.length?pairs.map(p=>`
${p.a} conflita com ${p.b}
Sistema: ${p.sistema}
`).join(''):'
Nenhum conflito identificado
'; }catch(e){toast(e.message,'error');} } // ===== ADMIN ===== async function loadAdmin(){ const guard=document.getElementById('admin-guard'); const adminContent=document.getElementById('admin-content'); if(!currentEmpresaId){ guard.innerHTML='
Selecione uma empresa
Usuários e áreas são segregados por empresa.
'; adminContent.style.display='none';return; } guard.innerHTML='';adminContent.style.display=''; try{ [cache.usuarios,cache.areas]=await Promise.all([GET('/admin/usuarios?empresaId='+currentEmpresaId),GET('/admin/areas?empresaId='+currentEmpresaId)]); document.getElementById('users-list').innerHTML=cache.usuarios.map(u=>`
👤
${u.nome}
${[u.departamento,u.email].filter(Boolean).join(' · ')||'—'}
`).join('')||'
Nenhum usuário cadastrado
'; document.getElementById('areas-list').innerHTML=cache.areas.map(a=>`
🏢${a.nome}
`).join('')||'
Nenhuma área cadastrada
'; }catch(e){toast(e.message,'error');} } let targetAreaSelectId = null; function openNewAreaModal(selectId){ if(!currentEmpresaId){toast('Selecione uma empresa primeiro.','warning');return;} targetAreaSelectId=selectId; document.getElementById('na-nome').value=''; openModal('modal-new-area'); } async function saveNewArea(){ const nome=document.getElementById('na-nome').value.trim(); if(!nome){toast('Nome é obrigatório.','error');return;} try{ const a=await POST('/admin/areas',{empresaId:currentEmpresaId,nome}); cache.areas.push(a);cache.areas.sort((a,b)=>a.nome.localeCompare(b.nome)); const aopts=cache.areas.map(a=>``).join(''); ['s-resp-acessos','s-resp-tecnico'].forEach(sid=>{const el=document.getElementById(sid);if(el)el.innerHTML=''+aopts;}); if(targetAreaSelectId){const t=document.getElementById(targetAreaSelectId);if(t){t.innerHTML=''+aopts;t.value=a.nome;}} // atualiza f-area se o modal de funcao estiver aberto const fa=document.getElementById('f-area'); if(fa){fa.innerHTML=''+aopts;} const deptSel2=document.getElementById('nu-depto');if(deptSel2)deptSel2.innerHTML=''+cache.areas.map(a=>``).join(''); closeModal('modal-new-area'); toast('Área cadastrada.'); }catch(e){toast(e.message,'error');} } function openNewUserModal(selectId){ if(!currentEmpresaId){toast('Selecione uma empresa primeiro.','warning');return;} targetUserSelectId=selectId; document.getElementById('nu-nome').value=''; document.getElementById('nu-email').value=''; const deptSel=document.getElementById('nu-depto'); deptSel.innerHTML=''+(cache.areas||[]).map(a=>``).join(''); deptSel.value=''; openModal('modal-new-user'); } async function saveNewUser(){ const nome=document.getElementById('nu-nome').value.trim(); const departamento=document.getElementById('nu-depto').value.trim(); const email=document.getElementById('nu-email').value.trim(); if(!nome){toast('Nome é obrigatório.','error');return;} try{ const u=await POST('/admin/usuarios',{empresaId:currentEmpresaId,nome,departamento,email}); cache.usuarios.push(u);cache.usuarios.sort((a,b)=>a.nome.localeCompare(b.nome)); const uopts=cache.usuarios.map(u=>``).join(''); ['s-owner'].forEach(sid=>{const el=document.getElementById(sid);if(el){const p=el.value;el.innerHTML=''+uopts;el.value=p;}}); ['r-aprov1','r-aprov2'].forEach(sid=>{const el=document.getElementById(sid);if(el){const p=el.value;el.innerHTML=''+uopts;el.value=p;}}); if(targetUserSelectId){const el=document.getElementById(targetUserSelectId);if(el)el.value=u.nome;} closeModal('modal-new-user'); toast('Usuário cadastrado.'); }catch(e){toast(e.message,'error');} } async function removeUser(id){try{await DEL('/admin/usuarios/'+id);toast('Usuário removido.','warning');await loadAdmin();}catch(e){toast(e.message,'error');}} async function addArea(){const n=document.getElementById('new-area-name').value.trim();if(!n)return;try{await POST('/admin/areas',{empresaId:currentEmpresaId,nome:n});document.getElementById('new-area-name').value='';toast('Área adicionada.');await loadAdmin();}catch(e){toast(e.message,'error');}} async function removeArea(id){try{await DEL('/admin/areas/'+id);toast('Área removida.','warning');await loadAdmin();}catch(e){toast(e.message,'error');}} async function clearAll(){try{await DEL('/admin/reset');currentEmpresaId=null;document.getElementById('cs-name').textContent='Selecione uma empresa';document.getElementById('cs-dot').className='cs-dot none';cache={sistemas:[],acessos:[],funcoes:[],rbac:[],usuarios:[],areas:[],empresas:[]};toast('Todos os dados limpos.','warning');navigate('empresas');}catch(e){toast(e.message,'error');}} // ===== SEARCH & FILTER TOGGLES ===== function toggleSearch(mod) { const wrap = document.getElementById('sw-' + mod); const inp = wrap.querySelector('input'); const isOpen = wrap.classList.toggle('open'); if (isOpen) { inp.focus(); } else { inp.value = ''; } if (mod === 'sistemas') renderSistemas(); else if (mod === 'acessos') renderAcessos(); else if (mod === 'funcoes') renderFuncoes(); else if (mod === 'rbac') renderRbac(); } function toggleFilter(mod) { const drop = document.getElementById('fdrop-' + mod); drop.classList.toggle('open'); } function updateFilterBtn(mod) { const drop = document.getElementById('fdrop-' + mod); const btn = document.getElementById('fbtn-' + mod); const hasActive = [...drop.querySelectorAll('select')].some(s => s.value !== '') || [...drop.querySelectorAll('input[type="text"]')].some(i => i.value !== ''); btn.classList.toggle('filter-active', hasActive); } document.addEventListener('click', e => { document.querySelectorAll('.filter-drop.open').forEach(drop => { if (!drop.closest('.filter-wrap').contains(e.target)) drop.classList.remove('open'); }); }); // ===== PLATFORM USERS ===== let editingPUId = null; function onPuRoleChange() { const role = document.getElementById('pu-role').value; document.getElementById('pu-empresas-group').style.display = role === 'superadmin' ? 'none' : ''; } function openPlataformaUserModal(user) { editingPUId = user ? user.id : null; document.getElementById('modal-pu-title').textContent = user ? 'Editar Usuário' : 'Novo Usuário da Plataforma'; document.getElementById('pu-nome').value = user ? user.nome : ''; document.getElementById('pu-email').value = user ? user.email : ''; document.getElementById('pu-email').disabled = !!user; document.getElementById('pu-role').value = user ? user.role : 'viewer'; document.getElementById('pu-pwd-group').style.display = user ? 'none' : ''; document.getElementById('pu-reset-group').style.display = user ? '' : 'none'; document.getElementById('pu-ativo-group').style.display = user ? '' : 'none'; if (user) document.getElementById('pu-ativo').value = String(user.ativo); document.getElementById('pu-password').value = ''; if (document.getElementById('pu-new-password')) document.getElementById('pu-new-password').value = ''; // Populate empresa multi-select let permitidas = null; if (user) { if (user.empresasPermitidas === null || user.empresasPermitidas === undefined) permitidas = null; else { try { permitidas = JSON.parse(user.empresasPermitidas); } catch { permitidas = []; } } } else { permitidas = []; // novos usuarios: sem acesso por padrao } puEmpresasState = permitidas === null ? { mode: 'todas', ids: new Set() } : { mode: 'especificas', ids: new Set(permitidas) }; renderPuEmpresasChips(); onPuRoleChange(); openModal('modal-plataforma-user'); } let puEmpresasState = { mode: 'todas', ids: new Set() }; function togglePuEmpresas(e) { e.stopPropagation(); const opts = document.getElementById('pu-emp-options'); if (opts.style.display !== 'none') { opts.style.display = 'none'; return; } opts.innerHTML = [ `
Todas as empresas
`, ...cache.empresas.map(e => `
${e.nome}
` ) ].join(''); if (!cache.empresas.length) opts.innerHTML += '
Nenhuma empresa cadastrada
'; opts.style.display = 'block'; const rect = document.getElementById('pu-emp-container').getBoundingClientRect(); opts.style.width = rect.width + 'px'; } function setPuEmpresasTodas() { puEmpresasState = { mode: 'todas', ids: new Set() }; renderPuEmpresasChips(); document.getElementById('pu-emp-options').style.display = 'none'; } function togglePuEmpresa(id, nome) { puEmpresasState.mode = 'especificas'; if (puEmpresasState.ids.has(id)) puEmpresasState.ids.delete(id); else puEmpresasState.ids.add(id); renderPuEmpresasChips(); // update option highlight document.querySelectorAll('#pu-emp-options .multi-option').forEach(el => { if (el.dataset.id === id) el.classList.toggle('selected', puEmpresasState.ids.has(id)); if (!el.dataset.id) el.classList.toggle('selected', puEmpresasState.mode==='todas'); }); } function renderPuEmpresasChips() { const chips = document.getElementById('pu-emp-chips'); const ph = document.getElementById('pu-emp-placeholder'); if (puEmpresasState.mode === 'todas') { chips.innerHTML = `Todas as empresas×`; ph.style.display = 'none'; } else if (puEmpresasState.ids.size === 0) { chips.innerHTML = ''; ph.style.display = ''; ph.textContent = 'Sem acesso a empresas'; } else { chips.innerHTML = [...puEmpresasState.ids].map(id => { const emp = cache.empresas.find(e => e.id === id); return emp ? `${emp.nome}×` : ''; }).join(''); ph.style.display = 'none'; } } function puClearAll(e) { e.stopPropagation(); puEmpresasState = { mode: 'especificas', ids: new Set() }; renderPuEmpresasChips(); } document.addEventListener('click', e => { if (!e.target.closest('#pu-emp-container') && !e.target.closest('#pu-emp-options')) document.getElementById('pu-emp-options').style.display = 'none'; }); function getSelectedEmpresas() { if (puEmpresasState.mode === 'todas') return null; return JSON.stringify([...puEmpresasState.ids]); } async function savePlataformaUser() { const nome = document.getElementById('pu-nome').value.trim(); const email = document.getElementById('pu-email').value.trim(); const role = document.getElementById('pu-role').value; const empresasPermitidas = role === 'superadmin' ? null : getSelectedEmpresas(); try { if (editingPUId) { const ativo = document.getElementById('pu-ativo').value === 'true'; const r = await fetch('/auth/users/' + editingPUId, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ nome, role, ativo, empresasPermitidas }) }); if (!r.ok) { const d = await r.json(); toast(d.error || 'Erro.', 'error'); return; } const np = document.getElementById('pu-new-password').value; if (np) await fetch('/auth/users/' + editingPUId + '/reset-password', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ password: np }) }); toast('Usuário atualizado.'); } else { const password = document.getElementById('pu-password').value; const r = await fetch('/auth/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ nome, email, password, role, empresasPermitidas }) }); const d = await r.json(); if (!r.ok) { toast(d.error || 'Erro ao criar usuário.', 'error'); return; } toast('Usuário criado.'); } closeModal('modal-plataforma-user'); await loadPlataformaUsers(); } catch (e) { toast(e.message, 'error'); } } async function loadPlataformaUsers() { try { const users = await fetch('/auth/users', { credentials: 'include' }).then(r => r.json()); const tbody = document.getElementById('tbody-plataforma-users'); const empty = document.getElementById('empty-plataforma-users'); if (!users.length) { tbody.innerHTML = ''; empty.style.display = ''; return; } empty.style.display = 'none'; const roleLabel = { superadmin: 'Superadmin', admin: 'Admin', editor: 'Editor', viewer: 'Viewer' }; const roleBadge = { superadmin: 'badge-purple', admin: 'badge-blue', editor: 'badge-medium', viewer: 'badge-no' }; tbody.innerHTML = users.map(u => ` ${u.nome} ${u.email} ${roleLabel[u.role] || u.role} ${u.provider === 'microsoft' ? 'Microsoft' : 'Local'} ${u.ativo ? 'Ativo' : 'Inativo'} ${u.ultimoAcesso ? new Date(u.ultimoAcesso).toLocaleString('pt-BR') : '—'}
${u.id !== currentUser?.id ? `` : ''}
`).join(''); setTimeout(() => lucide.createIcons(), 50); } catch (e) { toast(e.message, 'error'); } } async function deletePlataformaUser(id) { if (!confirm('Excluir este usuário da plataforma?')) return; try { const r = await fetch('/auth/users/' + id, { method: 'DELETE', credentials: 'include' }); const d = await r.json(); if (!r.ok) { toast(d.error || 'Erro.', 'error'); return; } toast('Usuário excluído.', 'warning'); await loadPlataformaUsers(); } catch (e) { toast(e.message, 'error'); } } function confirmLogout() { return confirm('Deseja sair da plataforma?'); } // ===== IMPERSONATION ===== async function loadImpersonateList() { try { const users = await fetch('/auth/users', { credentials: 'include' }).then(r => r.json()); const tbody = document.getElementById('tbody-impersonate'); const empty = document.getElementById('empty-impersonate'); const eligible = users.filter(u => u.role !== 'superadmin' && u.ativo); if (!eligible.length) { tbody.innerHTML = ''; empty.style.display = ''; return; } empty.style.display = 'none'; const roleLabel = { admin: 'Admin', editor: 'Editor', viewer: 'Viewer' }; const roleBadge = { admin: 'badge-blue', editor: 'badge-medium', viewer: 'badge-no' }; tbody.innerHTML = eligible.map(u => { let empresasLabel = '—'; if (u.empresasPermitidas === null || u.empresasPermitidas === undefined) empresasLabel = 'Todas'; else { try { const ids = JSON.parse(u.empresasPermitidas); empresasLabel = ids.length ? `${ids.length} empresa(s)` : 'Sem acesso'; } catch { empresasLabel = '—'; } } return ` ${u.nome} ${u.email} ${roleLabel[u.role] || u.role} ${empresasLabel} ${u.ativo ? 'Ativo' : 'Inativo'} `; }).join(''); setTimeout(() => lucide.createIcons(), 50); } catch (e) { toast(e.message, 'error'); } } async function startImpersonate(id, nome) { if (!confirm(`Acessar a plataforma como "${nome}"?\n\nVocê verá exatamente o que este usuário vê.`)) return; try { const r = await fetch(`/auth/impersonate/${id}`, { method: 'POST', credentials: 'include' }); const d = await r.json(); if (!r.ok) { toast(d.error || 'Erro.', 'error'); return; } window.location.href = '/'; } catch (e) { toast(e.message, 'error'); } } async function endImpersonate() { try { const r = await fetch('/auth/impersonate/end', { method: 'POST', credentials: 'include' }); const d = await r.json(); if (!r.ok) { toast(d.error || 'Erro.', 'error'); return; } window.location.href = '/'; } catch (e) { toast(e.message, 'error'); } } function showImpersonateBanner(nome) { document.getElementById('impersonate-target-name').textContent = nome; const banner = document.getElementById('impersonate-banner'); banner.style.display = 'flex'; document.querySelector('.main').style.marginTop = '40px'; setTimeout(() => lucide.createIcons(), 50); } function hidImpersonateBanner() { document.getElementById('impersonate-banner').style.display = 'none'; document.querySelector('.main').style.marginTop = ''; } // ===== INIT ===== (async () => { // Auth check try { const me = await fetch('/auth/me', { credentials: 'include' }); if (!me.ok) { window.location.replace('/login'); return; } currentUser = await me.json(); document.getElementById('user-name').textContent = currentUser.nome; document.getElementById('user-role').textContent = currentUser.role; document.getElementById('user-avatar').textContent = currentUser.nome.charAt(0).toUpperCase(); if (currentUser.role === 'superadmin' || currentUser.role === 'admin') { document.getElementById('nav-section-config').style.display = ''; document.getElementById('nav-plataforma-users').style.display = ''; } if (currentUser.role === 'superadmin' && !currentUser.impersonatedBy) { document.getElementById('nav-impersonate').style.display = ''; } if (currentUser.impersonatedBy) { showImpersonateBanner(currentUser.nome); } } catch (e) { window.location.replace('/login'); return; } try { const empresas = await GET('/empresas'); cache.empresas = empresas; updateBadgeEmpresas(empresas.length); } catch (e) { console.error('Erro ao conectar com a API:', e.message); } navigate('dashboard'); if(window.lucide) lucide.createIcons(); })();