feat: update to v2.0.3, premium redesign and fix navigation bugs
continuous-integration/webhook Deploy concluido (rx.scoreodonto.com)

This commit is contained in:
VPS 4 Builder
2026-05-26 19:41:32 +02:00
parent ee2ec49d5f
commit 6df952acae
10 changed files with 983 additions and 1293 deletions
+78 -40
View File
@@ -4,7 +4,21 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - Dental Server</title> <title>Login - Dental Server</title>
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<style> <style>
:root {
--primary-color: #4f46e5;
--secondary-color: #ec4899;
--dark-color: #0f172a;
--bg-color: #f8fafc;
--bg-surface: #ffffff;
--border-color: #e2e8f0;
--text-primary: #1e293b;
--text-secondary: #64748b;
--radius-lg: 20px;
--shadow-lg: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
}
* { * {
margin: 0; margin: 0;
padding: 0; padding: 0;
@@ -12,8 +26,8 @@
} }
body { body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, var(--bg-color) 0%, #eff6ff 100%);
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
justify-content: center; justify-content: center;
@@ -22,67 +36,82 @@
} }
.login-container { .login-container {
background: white; background: var(--bg-surface);
border-radius: 15px; border-radius: var(--radius-lg);
box-shadow: 0 20px 60px rgba(0,0,0,0.3); box-shadow: var(--shadow-lg);
padding: 40px; padding: 48px;
max-width: 400px; max-width: 420px;
width: 100%; width: 100%;
border: 1px solid var(--border-color);
} }
h1 { h1 {
text-align: center; text-align: center;
color: #667eea; font-size: 2.2rem;
font-size: 2rem; margin-bottom: 8px;
margin-bottom: 10px; font-weight: 700;
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
} }
p { p {
text-align: center; text-align: center;
color: #666; color: var(--text-secondary);
margin-bottom: 30px; margin-bottom: 32px;
font-size: 1.05rem;
font-weight: 500;
} }
.form-group { .form-group {
margin-bottom: 20px; margin-bottom: 24px;
} }
label { label {
display: block; display: block;
margin-bottom: 8px; margin-bottom: 8px;
color: #333; color: var(--text-primary);
font-weight: 600; font-weight: 600;
font-size: 0.95rem;
} }
input { input {
width: 100%; width: 100%;
padding: 12px; padding: 14px 16px;
border: 2px solid #e0e0e0; border: 2px solid var(--border-color);
border-radius: 8px; border-radius: 12px;
font-size: 1rem; font-size: 1rem;
transition: border-color 0.3s; transition: all 0.2s ease;
color: var(--text-primary);
background: var(--bg-color);
outline: none;
} }
input:focus { input:focus {
outline: none; border-color: var(--primary-color);
border-color: #667eea; background: var(--bg-surface);
box-shadow: 0 0 0 4px rgba(79, 70, 229, 0.1);
} }
button { button {
width: 100%; width: 100%;
padding: 12px; padding: 14px;
background: linear-gradient(135deg, #667eea, #764ba2); background: linear-gradient(135deg, var(--primary-color) 0%, #4338ca 100%);
color: white; color: white;
border: none; border: none;
border-radius: 8px; border-radius: 12px;
font-size: 1rem; font-size: 1.05rem;
font-weight: 600; font-weight: 600;
cursor: pointer; cursor: pointer;
transition: transform 0.3s; transition: all 0.2s;
margin-top: 8px;
box-shadow: 0 4px 6px -1px rgba(79, 70, 229, 0.2);
} }
button:hover { button:hover {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(79, 70, 229, 0.3);
} }
button:active { button:active {
@@ -90,22 +119,24 @@
} }
.alert { .alert {
padding: 12px; padding: 14px 16px;
border-radius: 8px; border-radius: 12px;
margin-bottom: 20px; margin-bottom: 24px;
display: none; display: none;
font-weight: 500;
font-size: 0.95rem;
} }
.alert.error { .alert.error {
background: #fee; background: rgba(239, 68, 68, 0.1);
color: #c33; color: #dc2626;
border: 1px solid #fcc; border: 1px solid rgba(239, 68, 68, 0.2);
} }
.alert.success { .alert.success {
background: #efe; background: rgba(16, 185, 129, 0.1);
color: #3c3; color: #059669;
border: 1px solid #cfc; border: 1px solid rgba(16, 185, 129, 0.2);
} }
#loginBtn { #loginBtn {
@@ -113,23 +144,27 @@
} }
#loginBtn.loading { #loginBtn.loading {
opacity: 0.7; opacity: 0.8;
cursor: not-allowed; cursor: not-allowed;
color: transparent;
} }
#loginBtn.loading::after { #loginBtn.loading::after {
content: ''; content: '';
position: absolute; position: absolute;
width: 16px; left: 50%;
height: 16px; top: 50%;
border: 2px solid #fff; transform: translate(-50%, -50%);
border-top-color: transparent; width: 20px;
height: 20px;
border: 3px solid rgba(255,255,255,0.3);
border-top-color: white;
border-radius: 50%; border-radius: 50%;
animation: spin 0.8s linear infinite; animation: spin 0.8s linear infinite;
} }
@keyframes spin { @keyframes spin {
to { transform: rotate(360deg); } to { transform: translate(-50%, -50%) rotate(360deg); }
} }
</style> </style>
</head> </head>
@@ -166,6 +201,9 @@
<button type="submit" class="btn" id="loginBtn">Entrar</button> <button type="submit" class="btn" id="loginBtn">Entrar</button>
</form> </form>
<div style="text-align: center; margin-top: 20px; color: var(--text-secondary); font-size: 0.85rem; font-weight: 500;">
v2.0.3
</div>
</div> </div>
<script> <script>
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "dental-image-server", "name": "dental-image-server",
"version": "2.0.1", "version": "2.0.2",
"description": "Servidor Socket.IO para receber e processar imagens dentais com interface web", "description": "Servidor Socket.IO para receber e processar imagens dentais com interface web",
"main": "server.js", "main": "server.js",
"scripts": { "scripts": {
+36 -27
View File
@@ -1,22 +1,23 @@
:root { :root {
--primary: #4361ee; --primary: #4f46e5;
--primary-hover: #3a56d4; --primary-hover: #4338ca;
--primary-light: rgba(67, 97, 238, 0.1); --primary-light: rgba(79, 70, 229, 0.1);
--success: #2ec4b6; --success: #10b981;
--success-light: rgba(46, 196, 182, 0.15); --success-light: rgba(16, 185, 129, 0.15);
--danger: #ef476f; --danger: #ef4444;
--danger-light: rgba(239, 71, 111, 0.15); --danger-light: rgba(239, 68, 68, 0.15);
--warning: #ff9f1c; --warning: #f59e0b;
--dark: #0b132b;
--gray-900: #1c2541; --dark: #0f172a;
--gray-800: #2b3a67; --gray-900: #f8fafc;
--gray-800: #e2e8f0;
--gray-200: #e2e8f0; --gray-200: #e2e8f0;
--light: #f8f9fa; --light: #1e293b;
--white: #ffffff; --white: #ffffff;
--glass-bg: rgba(255, 255, 255, 0.05); --glass-bg: rgba(255, 255, 255, 0.7);
--glass-border: rgba(255, 255, 255, 0.1); --glass-border: rgba(255, 255, 255, 0.5);
--glass-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.3); --glass-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--font-heading: 'Outfit', sans-serif; --font-heading: 'Outfit', sans-serif;
--font-body: 'Inter', sans-serif; --font-body: 'Inter', sans-serif;
@@ -30,7 +31,7 @@
body { body {
font-family: var(--font-body); font-family: var(--font-body);
background: linear-gradient(135deg, var(--dark) 0%, var(--gray-900) 100%); background: linear-gradient(135deg, #f8fafc 0%, #eff6ff 100%);
color: var(--light); color: var(--light);
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
@@ -66,12 +67,15 @@ body {
/* Sidebar */ /* Sidebar */
.sidebar { .sidebar {
width: 260px; width: 260px;
background: rgba(11, 19, 43, 0.8); background: var(--glass-bg);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-right: 1px solid var(--glass-border); border-right: 1px solid var(--glass-border);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 24px 0; padding: 24px 0;
z-index: 10; z-index: 10;
box-shadow: var(--glass-shadow);
} }
.sidebar-header { .sidebar-header {
@@ -111,7 +115,8 @@ body {
.nav-item { .nav-item {
padding: 12px 16px; padding: 12px 16px;
border-radius: 10px; border-radius: 10px;
color: #a0aec0; color: var(--dark);
opacity: 0.7;
text-decoration: none; text-decoration: none;
font-weight: 500; font-weight: 500;
display: flex; display: flex;
@@ -127,15 +132,19 @@ body {
} }
.nav-item:hover { .nav-item:hover {
background: var(--glass-bg); background: var(--white);
color: var(--white); color: var(--primary);
opacity: 1;
transform: translateX(4px); transform: translateX(4px);
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
} }
.nav-item.active { .nav-item.active {
background: var(--primary-light); background: linear-gradient(135deg, var(--primary) 0%, var(--primary-hover) 100%);
color: #4cc9f0; color: var(--white);
border-left: 3px solid #4cc9f0; opacity: 1;
box-shadow: var(--glass-shadow);
font-weight: 600;
} }
.sidebar-footer { .sidebar-footer {
@@ -216,9 +225,9 @@ body {
} }
.secondary-btn { .secondary-btn {
background: transparent; background: var(--white);
color: var(--white); color: var(--dark);
border: 1px solid var(--glass-border); border: 1px solid var(--gray-800);
padding: 12px 24px; padding: 12px 24px;
border-radius: 10px; border-radius: 10px;
font-family: var(--font-body); font-family: var(--font-body);
@@ -228,7 +237,7 @@ body {
} }
.secondary-btn:hover { .secondary-btn:hover {
background: var(--glass-bg); background: var(--gray-900);
} }
.icon-btn { .icon-btn {
+26 -12
View File
@@ -21,28 +21,42 @@
<!-- Sidebar --> <!-- Sidebar -->
<aside class="sidebar"> <aside class="sidebar">
<div class="sidebar-header"> <div class="sidebar-header">
<div class="logo-icon"> <h2>🦷 DentalSys</h2>
<i class="fa-solid fa-tooth"></i>
</div>
<h2>RF Dental</h2>
</div> </div>
<nav class="sidebar-nav"> <nav class="sidebar-nav">
<a href="/" class="nav-item"> <a href="/" class="nav-item">
<i class="fa-solid fa-chart-line"></i> Dashboard <span class="icon">🖼️</span> Pacientes
</a>
<a href="/?action=new-patient" class="nav-item">
<span class="icon"></span> Novo Paciente
</a> </a>
<a href="/clients" class="nav-item"> <a href="/clients" class="nav-item">
<i class="fa-solid fa-network-wired"></i> Conexões <span class="icon">🖥️</span> Dispositivos
</a>
<a href="/?action=settings" class="nav-item">
<span class="icon">⚙️</span> Configurações
</a>
<a href="/?action=plugins" class="nav-item">
<span class="icon">🔌</span> Plugins / Wasabi
</a>
<a href="/?action=sync" class="nav-item">
<span class="icon">🔄</span> Sincronização
</a>
<a href="/reset" class="nav-item text-danger">
<span class="icon">⚠️</span> Zerar Sistema
</a> </a>
<a href="/admin-clinics" class="nav-item active"> <a href="/admin-clinics" class="nav-item active">
<i class="fa-solid fa-hospital"></i> Clínicas e Acessos <span class="icon">🏢</span> Gerenciar Clínicas
</a>
<a href="#" class="nav-item" onclick="logout(); return false;">
<span class="icon">🚪</span> Sair
</a> </a>
</nav> </nav>
<div class="sidebar-footer"> <div class="sidebar-footer">
<button id="logoutBtn" class="logout-btn"> <div class="sidebar-brand" style="color: var(--dark); opacity: 0.8; font-weight: 500; text-align: center; font-size: 14px;">
<i class="fa-solid fa-arrow-right-from-bracket"></i> Sair <span>🦷 DentalSys</span>
</button> <span class="version-badge">v2.0.3</span>
</div>
</div> </div>
</aside> </aside>
+2 -2
View File
@@ -234,10 +234,10 @@ document.addEventListener('DOMContentLoaded', () => {
}); });
}); });
document.getElementById('logoutBtn').addEventListener('click', () => { window.logout = function() {
localStorage.removeItem('auth_token'); localStorage.removeItem('auth_token');
window.location.href = '/login'; window.location.href = '/login';
}); };
function showToast(message, type = 'success') { function showToast(message, type = 'success') {
const toast = document.getElementById('toast'); const toast = document.getElementById('toast');
+15
View File
@@ -152,6 +152,21 @@ document.addEventListener('DOMContentLoaded', async () => {
setupEventListeners(); setupEventListeners();
initDragAndDrop(); initDragAndDrop();
setInterval(loadClientsList, 5000); setInterval(loadClientsList, 5000);
// Process Querystring actions (from navigation links in other pages)
const urlParams = new URLSearchParams(window.location.search);
const action = urlParams.get('action');
if (action) {
setTimeout(() => {
if (action === 'new-patient') window.openCreatePatientModal();
else if (action === 'settings') window.openSettingsModal();
else if (action === 'plugins') window.openPluginsModal();
else if (action === 'sync') window.openSyncModal();
// Remove querystring to prevent reopening on reload
window.history.replaceState({}, document.title, window.location.pathname);
}, 300);
}
} catch (error) { } catch (error) {
_log.error('INIT', 'Erro fatal na inicialização:', error.message, error); _log.error('INIT', 'Erro fatal na inicialização:', error.message, error);
showErrorBanner('Erro na inicialização', error.message); showErrorBanner('Erro na inicialização', error.message);
+158 -156
View File
@@ -4,133 +4,103 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Clientes Conectados - Dental Server</title> <title>Clientes Conectados - Dental Server</title>
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="stylesheet" href="/style.css">
<style> <style>
* { .clients-container {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f5f5f5;
padding: 20px;
}
.container {
max-width: 1200px; max-width: 1200px;
margin: 0 auto; margin: 0 auto;
padding: 40px 20px;
} }
.header { .clients-header {
background: white; background: var(--glass-bg);
padding: 20px; backdrop-filter: var(--glass-blur);
border-radius: 10px; -webkit-backdrop-filter: var(--glass-blur);
margin-bottom: 20px; border: 1px solid var(--glass-border);
box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 24px 32px;
border-radius: var(--radius-lg);
margin-bottom: 32px;
box-shadow: var(--shadow-md);
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
} }
.header h1 { .clients-header h1 {
color: #333; color: var(--dark-color);
margin-bottom: 0; margin-bottom: 4px;
font-size: 1.8rem;
font-weight: 700;
} }
.header-actions { .header-actions {
display: flex; display: flex;
gap: 10px; gap: 12px;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
transition: all 0.3s;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5568d3;
}
.btn-success {
background: #48bb78;
color: white;
}
.btn-success:hover {
background: #38a169;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-link {
background: transparent;
color: #667eea;
text-decoration: none;
border: 1px solid #667eea;
}
.btn-link:hover {
background: #667eea;
color: white;
} }
.stats { .stats {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 15px; gap: 24px;
margin-bottom: 20px; margin-bottom: 32px;
} }
.stat-card { .stat-card {
background: white; background: var(--bg-surface);
padding: 20px; padding: 24px;
border-radius: 10px; border-radius: var(--radius-lg);
box-shadow: 0 2px 4px rgba(0,0,0,0.1); box-shadow: var(--shadow);
border: 1px solid var(--border-color);
transition: all 0.3s ease;
}
.stat-card:hover {
transform: translateY(-4px);
box-shadow: var(--shadow-md);
border-color: var(--primary-light);
} }
.stat-card .label { .stat-card .label {
color: #666; color: var(--text-secondary);
font-size: 14px; font-size: 0.95rem;
margin-bottom: 5px; font-weight: 600;
margin-bottom: 8px;
} }
.stat-card .value { .stat-card .value {
font-size: 32px; font-size: 2.5rem;
font-weight: bold; font-weight: 700;
color: #667eea; background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
} }
.clients-list { .clients-list-card {
background: white; background: var(--bg-surface);
border-radius: 10px; border-radius: var(--radius-lg);
padding: 20px; padding: 32px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1); box-shadow: var(--shadow);
border: 1px solid var(--border-color);
} }
.client-item { .client-item {
padding: 15px; padding: 20px;
border-bottom: 1px solid #eee; border: 1px solid var(--border-color);
border-radius: var(--radius);
margin-bottom: 16px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
transition: all 0.2s ease;
background: #fdfdfd;
} }
.client-item:last-child { .client-item:hover {
border-bottom: none; border-color: var(--primary-light);
background: var(--bg-surface);
box-shadow: var(--shadow-sm);
} }
.client-info { .client-info {
@@ -138,94 +108,75 @@
} }
.client-name { .client-name {
font-weight: 600; font-weight: 700;
color: #333; font-size: 1.1rem;
margin-bottom: 5px; color: var(--dark-color);
margin-bottom: 6px;
} }
.client-details { .client-details {
font-size: 12px; font-size: 0.9rem;
color: #666; color: var(--text-secondary);
line-height: 1.6; line-height: 1.5;
} }
.client-status { .client-status {
padding: 5px 15px; padding: 8px 16px;
border-radius: 20px; border-radius: var(--radius-full);
font-size: 12px; font-size: 0.85rem;
font-weight: 600; font-weight: 700;
margin-left: 10px; margin-left: 20px;
box-shadow: var(--shadow-sm);
} }
.status-connected { .status-connected {
background: #d4edda; background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: #155724; color: white;
} }
.status-identified { .status-identified {
background: #cce5ff; background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
color: #004085; color: white;
} }
.status-unknown { .status-unknown {
background: #fff3cd; background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
color: #856404; color: white;
} }
.status-testing { .status-testing {
background: #e2e3e5; background: #e2e8f0;
color: #383d41; color: var(--text-primary);
}
.empty-state {
text-align: center;
padding: 40px;
color: #666;
} }
.timestamp { .timestamp {
font-size: 12px; font-size: 0.85rem;
color: #999; color: var(--text-muted);
margin-top: 10px; margin-top: 20px;
text-align: right;
font-weight: 500;
} }
.test-result { .test-result {
margin-top: 10px; margin-top: 12px;
padding: 10px; padding: 10px 16px;
border-radius: 5px; border-radius: var(--radius-sm);
font-size: 12px; font-size: 0.85rem;
font-weight: 600;
} }
.test-result.success { .test-result.success {
background: #d4edda; background: rgba(16, 185, 129, 0.1);
color: #155724; color: var(--success-color);
border: 1px solid rgba(16, 185, 129, 0.2);
} }
.test-result.error { .test-result.error {
background: #f8d7da; background: rgba(239, 68, 68, 0.1);
color: #721c24; color: var(--danger-color);
border: 1px solid rgba(239, 68, 68, 0.2);
} }
.test-result.info {
background: #d1ecf1;
color: #0c5460;
}
.loading {
display: inline-block;
width: 14px;
height: 14px;
border: 2px solid #f3f3f3;
border-top: 2px solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-left: 5px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style> </style>
<script> <script>
// Redirecionar se não estiver autenticado // Redirecionar se não estiver autenticado
@@ -234,15 +185,58 @@
} }
</script> </script>
</head> </head>
<body> <body class="app-container">
<div class="container"> <!-- Sidebar -->
<div class="header"> <aside class="sidebar">
<div> <div class="sidebar-header">
<h1>🖥️ Clientes Conectados</h1> <h2>🦷 DentalSys</h2>
<p style="color: #666; margin-top: 5px; font-size: 14px;">Monitoramento em tempo real dos clientes Socket.IO</p> </div>
<nav class="sidebar-nav">
<a href="/" class="nav-item">
<span class="icon">🖼️</span> Pacientes
</a>
<a href="/?action=new-patient" class="nav-item">
<span class="icon"></span> Novo Paciente
</a>
<a href="/clients" class="nav-item active">
<span class="icon">🖥️</span> Dispositivos
</a>
<a href="/?action=settings" class="nav-item">
<span class="icon">⚙️</span> Configurações
</a>
<a href="/?action=plugins" class="nav-item">
<span class="icon">🔌</span> Plugins / Wasabi
</a>
<a href="/?action=sync" class="nav-item">
<span class="icon">🔄</span> Sincronização
</a>
<a href="/reset" class="nav-item text-danger">
<span class="icon">⚠️</span> Zerar Sistema
</a>
<a href="/admin-clinics" class="nav-item">
<span class="icon">🏢</span> Gerenciar Clínicas
</a>
<a href="#" class="nav-item" onclick="logout(); return false;">
<span class="icon">🚪</span> Sair
</a>
</nav>
<div class="sidebar-footer">
<div class="sidebar-brand">
<span>🦷 DentalSys</span>
<span class="version-badge">v2.0.3</span>
</div>
</div>
</aside>
<main class="main-content">
<div class="clients-container">
<div class="clients-header">
<div>
<h1>🖥️ Clientes Conectados</h1>
<p style="color: var(--text-secondary); margin-top: 5px; font-size: 1rem; font-weight: 500;">Monitoramento em tempo real dos clientes Socket.IO</p>
</div> </div>
<div class="header-actions"> <div class="header-actions">
<a href="/" class="btn btn-link">← Voltar</a> <a href="/" class="btn btn-secondary">← Voltar</a>
<button class="btn btn-primary" onclick="loadClients()">🔄 Atualizar</button> <button class="btn btn-primary" onclick="loadClients()">🔄 Atualizar</button>
<button class="btn btn-success" onclick="testConnection()" id="testBtn">🧪 Testar Conexão</button> <button class="btn btn-success" onclick="testConnection()" id="testBtn">🧪 Testar Conexão</button>
</div> </div>
@@ -252,9 +246,12 @@
<!-- Stats serão carregados aqui --> <!-- Stats serão carregados aqui -->
</div> </div>
<div class="clients-list"> <div class="clients-list-card">
<div id="clientsList"> <div id="clientsList">
<div class="empty-state">Carregando...</div> <div class="empty-state">
<div class="spinner" style="margin: 0 auto 16px auto;"></div>
Carregando...
</div>
</div> </div>
<div class="timestamp" id="timestamp"></div> <div class="timestamp" id="timestamp"></div>
</div> </div>
@@ -468,10 +465,14 @@
} }
function updateTestResults() { function updateTestResults() {
// Atualizar interface com resultados
loadClients(); loadClients();
} }
function logout() {
localStorage.removeItem('auth_token');
window.location.replace('/login');
}
// Inicializar Socket.IO e carregar clientes // Inicializar Socket.IO e carregar clientes
initSocket(); initSocket();
loadClients(); loadClients();
@@ -479,6 +480,7 @@
// Atualizar a cada 3 segundos // Atualizar a cada 3 segundos
setInterval(loadClients, 3000); setInterval(loadClients, 3000);
</script> </script>
</main>
</body> </body>
</html> </html>
+54 -52
View File
@@ -19,22 +19,22 @@
<a href="/" class="nav-item active"> <a href="/" class="nav-item active">
<span class="icon">🖼️</span> Pacientes <span class="icon">🖼️</span> Pacientes
</a> </a>
<a href="#" class="nav-item" onclick="openCreatePatientModal(); return false;"> <a href="/?action=new-patient" class="nav-item">
<span class="icon"></span> Novo Paciente <span class="icon"></span> Novo Paciente
</a> </a>
<a href="/clients" class="nav-item"> <a href="/clients" class="nav-item">
<span class="icon">🖥️</span> Dispositivos <span class="icon">🖥️</span> Dispositivos
</a> </a>
<a href="#" class="nav-item" onclick="openSettingsModal(); return false;"> <a href="/?action=settings" class="nav-item">
<span class="icon">⚙️</span> Configurações <span class="icon">⚙️</span> Configurações
</a> </a>
<a href="#" class="nav-item" onclick="openPluginsModal(); return false;"> <a href="/?action=plugins" class="nav-item">
<span class="icon">🔌</span> Plugins / Wasabi <span class="icon">🔌</span> Plugins / Wasabi
</a> </a>
<a href="#" class="nav-item" onclick="openSyncModal(); return false;"> <a href="/?action=sync" class="nav-item">
<span class="icon">🔄</span> Sincronização <span class="icon">🔄</span> Sincronização
</a> </a>
<a href="/reset" class="nav-item" style="color: #dc3545;"> <a href="/reset" class="nav-item text-danger">
<span class="icon">⚠️</span> Zerar Sistema <span class="icon">⚠️</span> Zerar Sistema
</a> </a>
<a href="/admin-clinics" class="nav-item"> <a href="/admin-clinics" class="nav-item">
@@ -44,19 +44,19 @@
<span class="icon">🚪</span> Sair <span class="icon">🚪</span> Sair
</a> </a>
<div class="nav-divider"></div> <div class="nav-divider"></div>
<div style="padding: 10px 20px;"> <div class="sidebar-filter-wrapper">
<select id="clientFilter" class="client-filter" style="width: 100%; border: 1px solid rgba(255,255,255,0.2); background: rgba(255,255,255,0.1); color: white; border-radius: 6px; padding: 8px;"> <select id="clientFilter" class="client-filter">
<option value="" style="color: black;">📋 Todos os Clientes</option> <option value="">📋 Todos os Clientes</option>
</select> </select>
</div> </div>
<button id="toggleDisabledBtn" class="btn btn-secondary" style="margin: 10px 20px; width: calc(100% - 40px);"> <button id="toggleDisabledBtn" class="btn btn-secondary btn-block" style="margin: 0 12px; width: calc(100% - 24px);">
<span id="toggleText">Ocultas</span> <span id="toggleText">Ocultas</span>
</button> </button>
</nav> </nav>
<div style="padding: 16px 20px; border-top: 1px solid rgba(255,255,255,0.08); margin-top: auto;"> <div class="sidebar-footer">
<div style="display: flex; align-items: center; gap: 8px; opacity: 0.5;"> <div class="sidebar-brand">
<span style="font-size: 11px; color: rgba(255,255,255,0.7);">🦷 DentalSys</span> <span>🦷 DentalSys</span>
<span style="font-size: 10px; background: rgba(255,255,255,0.1); color: rgba(255,255,255,0.6); padding: 2px 7px; border-radius: 20px; font-family: monospace; letter-spacing: 0.5px;">Server v2.0.0</span> <span class="version-badge">v2.0.3</span>
</div> </div>
</div> </div>
</aside> </aside>
@@ -120,75 +120,77 @@
<!-- Transform Modal --> <!-- Transform Modal -->
<div id="transformModal" class="modal"> <div id="transformModal" class="modal">
<div class="modal-content modal-large"> <div class="modal-content modal-large">
<div class="modal-header" style="display: flex; justify-content: space-between; width: 100%; align-items: center; border-bottom: 1px solid #eee; padding-bottom: 15px;"> <div class="modal-header">
<div style="display: flex; align-items: center; gap: 15px; flex-wrap: wrap;"> <div class="modal-title-group">
<h2 id="transformModalTitle" style="margin: 0; font-size: 1.4rem;"><!-- Dynamic Title (Patient + Date) --></h2> <h2 id="transformModalTitle"><!-- Dynamic Title --></h2>
<div style="display: flex; gap: 8px;"> <div class="modal-actions-group">
<button id="btnShowTransform" class="btn btn-primary" onclick="toggleModalView('transform')" style="width: 60px; height: 40px; padding: 0; display: flex; align-items: center; justify-content: center; font-size: 1.2rem;" title="Ajustar Imagem">🔄</button> <button id="btnShowTransform" class="btn btn-primary" onclick="toggleModalView('transform')" title="Ajustar Imagem">🔄</button>
<button id="btnShowGto" class="btn btn-secondary" onclick="toggleModalView('gto')" style="width: 60px; height: 40px; padding: 0; display: flex; align-items: center; justify-content: center; font-size: 1.2rem;" title="Gerenciar GTOs">📋</button> <button id="btnShowGto" class="btn btn-secondary" onclick="toggleModalView('gto')" title="Gerenciar GTOs">📋</button>
<button id="btnShowInfo" class="btn btn-secondary" onclick="toggleModalInfo()" style="width: 60px; height: 40px; padding: 0; display: flex; align-items: center; justify-content: center; font-size: 1.2rem;" title="Informações"></button> <button id="btnShowInfo" class="btn btn-secondary" onclick="toggleModalInfo()" title="Informações"></button>
<button id="btnResetImage" class="btn btn-danger" onclick="resetImageToOriginal()" style="width: 60px; height: 40px; padding: 0; display: flex; align-items: center; justify-content: center; font-size: 1.2rem; background-color: #ff6b6b; border-color: #ff6b6b; color: white;" title="Resetar para Original">↩️</button> <button id="btnResetImage" class="btn btn-danger" onclick="resetImageToOriginal()" title="Resetar para Original">↩️</button>
</div> </div>
</div> </div>
<span class="modal-close" id="closeTransformModal">×</span> <span class="modal-close" id="closeTransformModal">&times;</span>
</div> </div>
<div class="modal-body" style="padding-top: 15px;"> <div class="modal-body">
<!-- Bloco oculto de informações --> <!-- Bloco oculto de informações -->
<div id="transformExtraInfo" style="display: none; font-size: 0.95rem; color: #555; background: #f8f9fa; padding: 12px 20px; border-radius: 8px; border: 1px solid #eee; margin-bottom: 15px;"> <div id="transformExtraInfo" class="extra-info-panel" style="display: none;">
<!-- Preenchido via JS --> <!-- Preenchido via JS -->
</div> </div>
<!-- View 1: Transforms --> <!-- View 1: Transforms -->
<div id="transformViewWrapper" style="display: flex; flex-direction: column; flex: 1; min-height: 0; height: 100%;"> <div id="transformViewWrapper" class="transform-view-wrapper">
<div id="transformOptions" class="transform-grid" style="flex: 1; min-height: 0;"></div> <div id="transformOptions" class="transform-grid"></div>
<div id="transformActionArea" style="display: none; margin-top: 20px; background: #f8f9fa; padding: 15px; border-radius: 8px; flex-shrink: 0; border: 1px solid #e3e8ee;"> <div id="transformActionArea" class="transform-action-area" style="display: none;">
<!-- Ajuste Fino de Rotação --> <!-- Ajuste Fino de Rotação -->
<div style="display: flex; flex-direction: column; align-items: center; margin-bottom: 15px; border-bottom: 1px solid #e3e8ee; padding-bottom: 15px;"> <div class="fine-tune-section">
<span style="font-weight: 700; font-size: 0.95rem; margin-bottom: 10px; color: var(--dark-color); display: flex; align-items: center; gap: 6px;"> <span class="fine-tune-title">
📐 Ajuste Fino de Ângulo (Sensor Inclinado) 📐 Ajuste Fino de Ângulo (Sensor Inclinado)
</span> </span>
<div style="display: flex; align-items: center; gap: 8px; justify-content: center; flex-wrap: wrap;"> <div class="fine-tune-controls">
<button type="button" class="btn btn-secondary" style="padding: 6px 12px; font-size: 0.85rem; display: flex; align-items: center; gap: 3px;" onclick="adjustFineTune(-5)" title="Rotacionar -5°">🔄 -5°</button> <button type="button" class="btn btn-secondary btn-small" onclick="adjustFineTune(-5)" title="Rotacionar -5°">🔄 -5°</button>
<button type="button" class="btn btn-secondary" style="padding: 6px 12px; font-size: 0.85rem; display: flex; align-items: center; gap: 3px;" onclick="adjustFineTune(-1)" title="Rotacionar -1°">🔄 -1°</button> <button type="button" class="btn btn-secondary btn-small" onclick="adjustFineTune(-1)" title="Rotacionar -1°">🔄 -1°</button>
<div style="min-width: 100px; text-align: center; padding: 6px 12px; background: white; border: 1px solid #cbd5e0; border-radius: 6px; font-family: monospace; font-weight: 700; font-size: 1.1rem; color: var(--primary-color);" id="fineTuneValDisplay"> <div id="fineTuneValDisplay" class="fine-tune-display">
</div> </div>
<button type="button" class="btn btn-secondary" style="padding: 6px 12px; font-size: 0.85rem; display: flex; align-items: center; gap: 3px;" onclick="adjustFineTune(1)" title="Rotacionar +1°">🔄 +1°</button> <button type="button" class="btn btn-secondary btn-small" onclick="adjustFineTune(1)" title="Rotacionar +1°">🔄 +1°</button>
<button type="button" class="btn btn-secondary" style="padding: 6px 12px; font-size: 0.85rem; display: flex; align-items: center; gap: 3px;" onclick="adjustFineTune(5)" title="Rotacionar +5°">🔄 +5°</button> <button type="button" class="btn btn-secondary btn-small" onclick="adjustFineTune(5)" title="Rotacionar +5°">🔄 +5°</button>
<button type="button" class="btn btn-danger btn-small" style="padding: 6px 12px; font-size: 0.85rem; margin-left: 8px; display: none; background: #e53e3e; border-color: #e53e3e; color: white;" id="btnResetFineTune" onclick="resetFineTune()" title="Resetar ajuste fino">Reset</button> <button type="button" class="btn btn-danger btn-small" id="btnResetFineTune" onclick="resetFineTune()" style="display: none;" title="Resetar ajuste fino">Reset</button>
</div> </div>
</div> </div>
<label style="font-weight: 600; display: block; margin-bottom: 8px;">Nova Observação / Detalhes (Opcional):</label> <div class="form-group">
<textarea id="newImageRemark" class="search-input" rows="2" style="width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 6px;" placeholder="Ex: Raio-X Dente 45..."></textarea> <label>Nova Observação / Detalhes (Opcional):</label>
<div style="display: flex; gap: 10px; margin-top: 10px;"> <textarea id="newImageRemark" class="form-control" rows="2" placeholder="Ex: Raio-X Dente 45..."></textarea>
<button class="btn btn-secondary" style="flex: 1; padding: 12px; font-size: 1.1rem;" onclick="clearTransformSelection()">Cancelar</button> </div>
<button id="btnSaveTransform" class="btn btn-primary" style="flex: 2; padding: 12px; font-size: 1.1rem;" onclick="saveSelectedTransform()">Salvar Nova Imagem</button> <div class="form-actions-inline">
<button class="btn btn-secondary" onclick="clearTransformSelection()">Cancelar</button>
<button id="btnSaveTransform" class="btn btn-primary" onclick="saveSelectedTransform()">Salvar Nova Imagem</button>
</div> </div>
</div> </div>
</div> </div>
<!-- View 2: GTO List & Link --> <!-- View 2: GTO List & Link -->
<div id="gtoView" style="display: none;"> <div id="gtoView" style="display: none;">
<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 20px;"> <div class="gto-create-section">
<h3 style="margin-top: 0; font-size: 1.1rem; margin-bottom: 15px;">Cadastrar Nova GTO</h3> <h3 class="section-title">Cadastrar Nova GTO</h3>
<div style="display: flex; gap: 10px;"> <div class="form-actions-inline">
<input type="text" id="inlineGtoNumber" class="search-input" placeholder="Número/ID da GTO" style="flex: 1; padding: 10px;"> <input type="text" id="inlineGtoNumber" class="form-control" placeholder="Número/ID da GTO" style="flex: 1;">
<input type="text" id="inlineGtoDesc" class="search-input" placeholder="Descrição (Opcional)" style="flex: 2; padding: 10px;"> <input type="text" id="inlineGtoDesc" class="form-control" placeholder="Descrição (Opcional)" style="flex: 2;">
<button class="btn btn-primary" onclick="inlineCreateGto()">Cadastrar</button> <button class="btn btn-primary" onclick="inlineCreateGto()">Cadastrar</button>
</div> </div>
</div> </div>
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 15px;"> <div class="gto-link-section">
<h3 style="margin: 0; font-size: 1.1rem;">Vincular esta imagem a uma GTO:</h3> <h3 class="section-title">Vincular esta imagem a uma GTO:</h3>
<button class="btn btn-success" onclick="linkImageToGto()" id="btnLinkGto">✅ Vincular Selecionada</button> <button class="btn btn-success" onclick="linkImageToGto()" id="btnLinkGto">✅ Vincular Selecionada</button>
</div> </div>
<div id="gtoListContainer" style="border: 1px solid #ddd; border-radius: 8px; overflow: hidden;"> <div id="gtoListContainer" class="gto-list-container">
<!-- GTO list goes here --> <!-- GTO list goes here -->
</div> </div>
</div> </div>
@@ -198,12 +200,12 @@
<!-- Modal Novo Paciente --> <!-- Modal Novo Paciente -->
<div class="modal" id="create-patient-modal"> <div class="modal" id="create-patient-modal">
<div class="modal-content" style="max-width: 500px; height: auto; border-radius: var(--radius);"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h2>Novo Paciente</h2> <h2>Novo Paciente</h2>
<div class="modal-close" onclick="closeCreatePatientModal()">&times;</div> <div class="modal-close" onclick="closeCreatePatientModal()">&times;</div>
</div> </div>
<div class="modal-body" style="overflow-y: auto;"> <div class="modal-body">
<form id="create-patient-form" onsubmit="submitCreatePatient(event)"> <form id="create-patient-form" onsubmit="submitCreatePatient(event)">
<div class="form-group"> <div class="form-group">
<label>Nome *</label> <label>Nome *</label>
@@ -248,7 +250,7 @@
<!-- Modal Nova GTO --> <!-- Modal Nova GTO -->
<div class="modal" id="create-gto-modal"> <div class="modal" id="create-gto-modal">
<div class="modal-content" style="max-width: 400px; height: auto; border-radius: var(--radius);"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h2>Nova GTO</h2> <h2>Nova GTO</h2>
<div class="modal-close" onclick="closeCreateGtoModal()">&times;</div> <div class="modal-close" onclick="closeCreateGtoModal()">&times;</div>
File diff suppressed because it is too large Load Diff
+21
View File
@@ -0,0 +1,21 @@
Responda, raciocinios durante as implementações em pt-br
Se encontrar problemas com deploy ou com o servidor, me avise
Se algo não der certo revise outras partes do projeto em questão, não deve mexer em outro projeto se ele não fizer parte da estrutura do atual que esta sendo implementado.
Temos uma rede vpn
notebook 10.99.0.10
vps1 10.99.0.1 - roteador traefik e docker sem root apenas produção, sem build
vps3 10.99.0.3 - apenas postgresql e dragonflydb e o gitea central de documentações
vps4 10.99.0.4 - apenas desenvolvimento nunca banco de dados
Possuimos um sistema de Deploy automativo via gitea, quando é feito push para o branch main ou master ele faz o deploy automatico. A vps1 é a que tem o docker-compose principal que fica em /opt/stack
Toda e qualquer implementação deve ser feita pensando em segurança, escalabilidade e manutenibilidade.
Você deve me consultar antes de fazer qualquer alteração significativa no código ou estrutura de arquivos.
Estou debaixo de um sistema de VPN, todos os acessos ao servidor são feitos via VPN. Se cair a conexão da VPN, me avise e não continue a implementação.
Todos os projetos devem ser versionados, v.2.0.0 como exemplo cada deploy deve gerar uma nova versão. A pagina de login deve conter a versão do sistema. Dentro das paginas caso não tenha tbm devem conter a versão do sistema.