From da40ab11f6bb2521dcdb80a38c9ed5a94b63d18b Mon Sep 17 00:00:00 2001 From: VPS 4 Builder Date: Tue, 26 May 2026 20:45:00 +0200 Subject: [PATCH] feat: implement user management dashboard and visibility access controls --- dental-server/auth/login.html | 2 +- dental-server/package.json | 2 +- dental-server/public/app.js | 176 +++++++++++++++++++++++++++++++- dental-server/public/index.html | 56 ++++++++++ 4 files changed, 233 insertions(+), 3 deletions(-) diff --git a/dental-server/auth/login.html b/dental-server/auth/login.html index 74d24db..bbf15a0 100644 --- a/dental-server/auth/login.html +++ b/dental-server/auth/login.html @@ -201,7 +201,7 @@
- v2.0.4 + v2.0.5
diff --git a/dental-server/package.json b/dental-server/package.json index d69c309..b780505 100644 --- a/dental-server/package.json +++ b/dental-server/package.json @@ -1,6 +1,6 @@ { "name": "dental-image-server", - "version": "2.0.4", + "version": "2.0.5", "description": "Servidor Socket.IO para receber e processar imagens dentais com interface web", "main": "server.js", "scripts": { diff --git a/dental-server/public/app.js b/dental-server/public/app.js index 6b3a757..569680e 100644 --- a/dental-server/public/app.js +++ b/dental-server/public/app.js @@ -52,7 +52,21 @@ async function verifyAuth() { if (!response.ok) { localStorage.removeItem('auth_token'); window.location.href = '/login'; return false; } const data = await response.json(); authVerificationInProgress = false; - if (data.valid) return true; + if (data.valid) { + const isAdmin = !!data.user.is_admin; + localStorage.setItem('is_admin', isAdmin ? 'true' : 'false'); + + // Controle de visibilidade de itens de menu + updateAdminMenuVisibility(isAdmin); + + // Se usuário comum tentar acessar clients ou admin-clinics no front, joga de volta pra home + if (!isAdmin && (window.location.pathname === '/clients' || window.location.pathname === '/admin-clinics')) { + window.location.href = '/'; + return false; + } + + return true; + } localStorage.removeItem('auth_token'); window.location.href = '/login'; return false; } catch { localStorage.removeItem('auth_token'); @@ -61,6 +75,18 @@ async function verifyAuth() { } } +function updateAdminMenuVisibility(isAdmin) { + // Esconder links não autorizados da Sidebar + const connectionsLink = document.querySelector('.sidebar-nav .nav-item[href="/clients"]'); + const clinicsLink = document.querySelector('.sidebar-nav .nav-item[href="/admin-clinics"]'); + if (connectionsLink) connectionsLink.style.display = isAdmin ? 'flex' : 'none'; + if (clinicsLink) clinicsLink.style.display = isAdmin ? 'flex' : 'none'; + + // Esconder botão de gerenciamento de usuários na toolbar + const btnUserManagement = document.getElementById('btnUserManagement'); + if (btnUserManagement) btnUserManagement.style.display = isAdmin ? 'flex' : 'none'; +} + async function fetchWithAuth(url, options = {}) { const token = getAuthToken(); _log.info('FETCH', `${options.method || 'GET'} ${url}`); @@ -1170,6 +1196,154 @@ window.openSettingsModal = openSettingsModal; window.closeSettingsModal = closeSettingsModal; window.updateCredentials = updateCredentials; +// ================================================================ +// GERENCIAMENTO DE USUÁRIOS (ADMIN ONLY) +// ================================================================ + +function openUserManagementModal() { + const modal = document.getElementById('userManagementModal'); + if (modal) modal.style.display = 'block'; + + // Limpar formulário de criação + document.getElementById('new_username').value = ''; + document.getElementById('new_email').value = ''; + document.getElementById('new_password').value = ''; + + loadUsersList(); +} + +function closeUserManagementModal() { + const modal = document.getElementById('userManagementModal'); + if (modal) modal.style.display = 'none'; +} + +async function loadUsersList() { + const listBody = document.getElementById('usersListBody'); + if (!listBody) return; + + listBody.innerHTML = ' Carregando...'; + + try { + const token = getAuthToken(); + const res = await fetch(`${API_URL}/auth/users`, { + headers: { 'Authorization': `Bearer ${token}` } + }); + const data = await res.json(); + + if (!res.ok) throw new Error(data.error || 'Erro ao carregar usuários.'); + + if (data.success && data.users) { + listBody.innerHTML = ''; + + // Decodifica o token atual para não permitir excluir a si mesmo + let currentUserId = null; + try { + const payload = JSON.parse(atob(token.split('.')[1])); + currentUserId = payload.id; + } catch (e) {} + + data.users.forEach(u => { + const role = u.is_admin ? '👑 Administrador' : '🩺 Dentista / Comum'; + const tr = document.createElement('tr'); + tr.style.borderBottom = '1px solid var(--border-color)'; + + const isSelf = currentUserId && parseInt(u.id) === parseInt(currentUserId); + const deleteBtnHtml = isSelf + ? 'Você' + : ``; + + tr.innerHTML = ` + ${u.username} + ${u.email} + ${role} + ${deleteBtnHtml} + `; + listBody.appendChild(tr); + }); + } else { + listBody.innerHTML = 'Falha ao carregar dados.'; + } + } catch (e) { + console.error(e); + listBody.innerHTML = `${e.message || 'Erro ao carregar.'}`; + } +} + +async function createUser(event) { + event.preventDefault(); + + const username = document.getElementById('new_username').value.trim(); + const email = document.getElementById('new_email').value.trim(); + const password = document.getElementById('new_password').value; + + if (!username || !email || !password) { + showToast('Todos os campos obrigatórios.', 'error'); + return; + } + + const submitBtn = event.target.querySelector('button[type="submit"]'); + const originalText = submitBtn.innerHTML; + submitBtn.innerHTML = ' Cadastrando...'; + submitBtn.disabled = true; + + try { + const token = getAuthToken(); + const res = await fetch(`${API_URL}/auth/create-user`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify({ username, email, password }) + }); + const data = await res.json(); + + if (!res.ok) throw new Error(data.error || 'Erro ao cadastrar usuário.'); + + showToast('Usuário cadastrado com sucesso!', 'success'); + + // Limpar formulário + document.getElementById('new_username').value = ''; + document.getElementById('new_email').value = ''; + document.getElementById('new_password').value = ''; + + // Atualizar lista + loadUsersList(); + } catch (e) { + console.error(e); + showToast(e.message, 'error'); + } finally { + submitBtn.innerHTML = originalText; + submitBtn.disabled = false; + } +} + +async function deleteUser(userId) { + if (!confirm('Tem certeza que deseja excluir este usuário permanentemente?')) return; + + try { + const token = getAuthToken(); + const res = await fetch(`${API_URL}/auth/users/${userId}`, { + method: 'DELETE', + headers: { 'Authorization': `Bearer ${token}` } + }); + const data = await res.json(); + + if (!res.ok) throw new Error(data.error || 'Erro ao excluir usuário.'); + + showToast('Usuário excluído com sucesso!', 'success'); + loadUsersList(); + } catch (e) { + console.error(e); + showToast(e.message, 'error'); + } +} + +window.openUserManagementModal = openUserManagementModal; +window.closeUserManagementModal = closeUserManagementModal; +window.createUser = createUser; +window.deleteUser = deleteUser; + // ================================================================ // CONFIGURAÇÕES DE PLUGINS (Wasabi Storage) // ================================================================ diff --git a/dental-server/public/index.html b/dental-server/public/index.html index 32794c0..c96672b 100644 --- a/dental-server/public/index.html +++ b/dental-server/public/index.html @@ -92,6 +92,9 @@ + @@ -322,6 +325,59 @@ + + +