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 @@
+
+
+
+
+
+
+
+
Cadastrar Novo Usuário
+
+
+
+
+
+
Usuários Cadastrados
+
+
+
+
+ | Usuário |
+ E-mail |
+ Função |
+ Ações |
+
+
+
+
+
+
+
+
+
+
+
+