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 name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - Dental Server</title>
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<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;
padding: 0;
@@ -12,8 +26,8 @@
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: linear-gradient(135deg, var(--bg-color) 0%, #eff6ff 100%);
min-height: 100vh;
display: flex;
justify-content: center;
@@ -22,67 +36,82 @@
}
.login-container {
background: white;
border-radius: 15px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
padding: 40px;
max-width: 400px;
background: var(--bg-surface);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
padding: 48px;
max-width: 420px;
width: 100%;
border: 1px solid var(--border-color);
}
h1 {
text-align: center;
color: #667eea;
font-size: 2rem;
margin-bottom: 10px;
font-size: 2.2rem;
margin-bottom: 8px;
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 {
text-align: center;
color: #666;
margin-bottom: 30px;
color: var(--text-secondary);
margin-bottom: 32px;
font-size: 1.05rem;
font-weight: 500;
}
.form-group {
margin-bottom: 20px;
margin-bottom: 24px;
}
label {
display: block;
margin-bottom: 8px;
color: #333;
color: var(--text-primary);
font-weight: 600;
font-size: 0.95rem;
}
input {
width: 100%;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
padding: 14px 16px;
border: 2px solid var(--border-color);
border-radius: 12px;
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 {
outline: none;
border-color: #667eea;
border-color: var(--primary-color);
background: var(--bg-surface);
box-shadow: 0 0 0 4px rgba(79, 70, 229, 0.1);
}
button {
width: 100%;
padding: 12px;
background: linear-gradient(135deg, #667eea, #764ba2);
padding: 14px;
background: linear-gradient(135deg, var(--primary-color) 0%, #4338ca 100%);
color: white;
border: none;
border-radius: 8px;
font-size: 1rem;
border-radius: 12px;
font-size: 1.05rem;
font-weight: 600;
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 {
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(79, 70, 229, 0.3);
}
button:active {
@@ -90,22 +119,24 @@
}
.alert {
padding: 12px;
border-radius: 8px;
margin-bottom: 20px;
padding: 14px 16px;
border-radius: 12px;
margin-bottom: 24px;
display: none;
font-weight: 500;
font-size: 0.95rem;
}
.alert.error {
background: #fee;
color: #c33;
border: 1px solid #fcc;
background: rgba(239, 68, 68, 0.1);
color: #dc2626;
border: 1px solid rgba(239, 68, 68, 0.2);
}
.alert.success {
background: #efe;
color: #3c3;
border: 1px solid #cfc;
background: rgba(16, 185, 129, 0.1);
color: #059669;
border: 1px solid rgba(16, 185, 129, 0.2);
}
#loginBtn {
@@ -113,23 +144,27 @@
}
#loginBtn.loading {
opacity: 0.7;
opacity: 0.8;
cursor: not-allowed;
color: transparent;
}
#loginBtn.loading::after {
content: '';
position: absolute;
width: 16px;
height: 16px;
border: 2px solid #fff;
border-top-color: transparent;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 20px;
height: 20px;
border: 3px solid rgba(255,255,255,0.3);
border-top-color: white;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
to { transform: translate(-50%, -50%) rotate(360deg); }
}
</style>
</head>
@@ -166,6 +201,9 @@
<button type="submit" class="btn" id="loginBtn">Entrar</button>
</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>
<script>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"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",
"main": "server.js",
"scripts": {
+36 -27
View File
@@ -1,22 +1,23 @@
:root {
--primary: #4361ee;
--primary-hover: #3a56d4;
--primary-light: rgba(67, 97, 238, 0.1);
--success: #2ec4b6;
--success-light: rgba(46, 196, 182, 0.15);
--danger: #ef476f;
--danger-light: rgba(239, 71, 111, 0.15);
--warning: #ff9f1c;
--dark: #0b132b;
--gray-900: #1c2541;
--gray-800: #2b3a67;
--primary: #4f46e5;
--primary-hover: #4338ca;
--primary-light: rgba(79, 70, 229, 0.1);
--success: #10b981;
--success-light: rgba(16, 185, 129, 0.15);
--danger: #ef4444;
--danger-light: rgba(239, 68, 68, 0.15);
--warning: #f59e0b;
--dark: #0f172a;
--gray-900: #f8fafc;
--gray-800: #e2e8f0;
--gray-200: #e2e8f0;
--light: #f8f9fa;
--light: #1e293b;
--white: #ffffff;
--glass-bg: rgba(255, 255, 255, 0.05);
--glass-border: rgba(255, 255, 255, 0.1);
--glass-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.3);
--glass-bg: rgba(255, 255, 255, 0.7);
--glass-border: rgba(255, 255, 255, 0.5);
--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-body: 'Inter', sans-serif;
@@ -30,7 +31,7 @@
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);
min-height: 100vh;
display: flex;
@@ -66,12 +67,15 @@ body {
/* Sidebar */
.sidebar {
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);
display: flex;
flex-direction: column;
padding: 24px 0;
z-index: 10;
box-shadow: var(--glass-shadow);
}
.sidebar-header {
@@ -111,7 +115,8 @@ body {
.nav-item {
padding: 12px 16px;
border-radius: 10px;
color: #a0aec0;
color: var(--dark);
opacity: 0.7;
text-decoration: none;
font-weight: 500;
display: flex;
@@ -127,15 +132,19 @@ body {
}
.nav-item:hover {
background: var(--glass-bg);
color: var(--white);
background: var(--white);
color: var(--primary);
opacity: 1;
transform: translateX(4px);
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
}
.nav-item.active {
background: var(--primary-light);
color: #4cc9f0;
border-left: 3px solid #4cc9f0;
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-hover) 100%);
color: var(--white);
opacity: 1;
box-shadow: var(--glass-shadow);
font-weight: 600;
}
.sidebar-footer {
@@ -216,9 +225,9 @@ body {
}
.secondary-btn {
background: transparent;
color: var(--white);
border: 1px solid var(--glass-border);
background: var(--white);
color: var(--dark);
border: 1px solid var(--gray-800);
padding: 12px 24px;
border-radius: 10px;
font-family: var(--font-body);
@@ -228,7 +237,7 @@ body {
}
.secondary-btn:hover {
background: var(--glass-bg);
background: var(--gray-900);
}
.icon-btn {
+26 -12
View File
@@ -21,28 +21,42 @@
<!-- Sidebar -->
<aside class="sidebar">
<div class="sidebar-header">
<div class="logo-icon">
<i class="fa-solid fa-tooth"></i>
</div>
<h2>RF Dental</h2>
<h2>🦷 DentalSys</h2>
</div>
<nav class="sidebar-nav">
<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 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 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>
</nav>
<div class="sidebar-footer">
<button id="logoutBtn" class="logout-btn">
<i class="fa-solid fa-arrow-right-from-bracket"></i> Sair
</button>
<div class="sidebar-brand" style="color: var(--dark); opacity: 0.8; font-weight: 500; text-align: center; font-size: 14px;">
<span>🦷 DentalSys</span>
<span class="version-badge">v2.0.3</span>
</div>
</div>
</aside>
+2 -2
View File
@@ -234,10 +234,10 @@ document.addEventListener('DOMContentLoaded', () => {
});
});
document.getElementById('logoutBtn').addEventListener('click', () => {
window.logout = function() {
localStorage.removeItem('auth_token');
window.location.href = '/login';
});
};
function showToast(message, type = 'success') {
const toast = document.getElementById('toast');
+15
View File
@@ -152,6 +152,21 @@ document.addEventListener('DOMContentLoaded', async () => {
setupEventListeners();
initDragAndDrop();
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) {
_log.error('INIT', 'Erro fatal na inicialização:', error.message, error);
showErrorBanner('Erro na inicialização', error.message);
+158 -156
View File
@@ -4,133 +4,103 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Clientes Conectados - Dental Server</title>
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="stylesheet" href="/style.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f5f5f5;
padding: 20px;
}
.container {
.clients-container {
max-width: 1200px;
margin: 0 auto;
padding: 40px 20px;
}
.header {
background: white;
padding: 20px;
border-radius: 10px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
.clients-header {
background: var(--glass-bg);
backdrop-filter: var(--glass-blur);
-webkit-backdrop-filter: var(--glass-blur);
border: 1px solid var(--glass-border);
padding: 24px 32px;
border-radius: var(--radius-lg);
margin-bottom: 32px;
box-shadow: var(--shadow-md);
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
color: #333;
margin-bottom: 0;
.clients-header h1 {
color: var(--dark-color);
margin-bottom: 4px;
font-size: 1.8rem;
font-weight: 700;
}
.header-actions {
display: flex;
gap: 10px;
}
.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;
gap: 12px;
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 20px;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 24px;
margin-bottom: 32px;
}
.stat-card {
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
background: var(--bg-surface);
padding: 24px;
border-radius: var(--radius-lg);
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 {
color: #666;
font-size: 14px;
margin-bottom: 5px;
color: var(--text-secondary);
font-size: 0.95rem;
font-weight: 600;
margin-bottom: 8px;
}
.stat-card .value {
font-size: 32px;
font-weight: bold;
color: #667eea;
font-size: 2.5rem;
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;
}
.clients-list {
background: white;
border-radius: 10px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
.clients-list-card {
background: var(--bg-surface);
border-radius: var(--radius-lg);
padding: 32px;
box-shadow: var(--shadow);
border: 1px solid var(--border-color);
}
.client-item {
padding: 15px;
border-bottom: 1px solid #eee;
padding: 20px;
border: 1px solid var(--border-color);
border-radius: var(--radius);
margin-bottom: 16px;
display: flex;
justify-content: space-between;
align-items: center;
transition: all 0.2s ease;
background: #fdfdfd;
}
.client-item:last-child {
border-bottom: none;
.client-item:hover {
border-color: var(--primary-light);
background: var(--bg-surface);
box-shadow: var(--shadow-sm);
}
.client-info {
@@ -138,94 +108,75 @@
}
.client-name {
font-weight: 600;
color: #333;
margin-bottom: 5px;
font-weight: 700;
font-size: 1.1rem;
color: var(--dark-color);
margin-bottom: 6px;
}
.client-details {
font-size: 12px;
color: #666;
line-height: 1.6;
font-size: 0.9rem;
color: var(--text-secondary);
line-height: 1.5;
}
.client-status {
padding: 5px 15px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
margin-left: 10px;
padding: 8px 16px;
border-radius: var(--radius-full);
font-size: 0.85rem;
font-weight: 700;
margin-left: 20px;
box-shadow: var(--shadow-sm);
}
.status-connected {
background: #d4edda;
color: #155724;
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
}
.status-identified {
background: #cce5ff;
color: #004085;
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
color: white;
}
.status-unknown {
background: #fff3cd;
color: #856404;
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
color: white;
}
.status-testing {
background: #e2e3e5;
color: #383d41;
}
.empty-state {
text-align: center;
padding: 40px;
color: #666;
background: #e2e8f0;
color: var(--text-primary);
}
.timestamp {
font-size: 12px;
color: #999;
margin-top: 10px;
font-size: 0.85rem;
color: var(--text-muted);
margin-top: 20px;
text-align: right;
font-weight: 500;
}
.test-result {
margin-top: 10px;
padding: 10px;
border-radius: 5px;
font-size: 12px;
margin-top: 12px;
padding: 10px 16px;
border-radius: var(--radius-sm);
font-size: 0.85rem;
font-weight: 600;
}
.test-result.success {
background: #d4edda;
color: #155724;
background: rgba(16, 185, 129, 0.1);
color: var(--success-color);
border: 1px solid rgba(16, 185, 129, 0.2);
}
.test-result.error {
background: #f8d7da;
color: #721c24;
background: rgba(239, 68, 68, 0.1);
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>
<script>
// Redirecionar se não estiver autenticado
@@ -234,15 +185,58 @@
}
</script>
</head>
<body>
<div class="container">
<div class="header">
<div>
<h1>🖥️ Clientes Conectados</h1>
<p style="color: #666; margin-top: 5px; font-size: 14px;">Monitoramento em tempo real dos clientes Socket.IO</p>
<body class="app-container">
<!-- Sidebar -->
<aside class="sidebar">
<div class="sidebar-header">
<h2>🦷 DentalSys</h2>
</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 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-success" onclick="testConnection()" id="testBtn">🧪 Testar Conexão</button>
</div>
@@ -252,9 +246,12 @@
<!-- Stats serão carregados aqui -->
</div>
<div class="clients-list">
<div class="clients-list-card">
<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 class="timestamp" id="timestamp"></div>
</div>
@@ -468,10 +465,14 @@
}
function updateTestResults() {
// Atualizar interface com resultados
loadClients();
}
function logout() {
localStorage.removeItem('auth_token');
window.location.replace('/login');
}
// Inicializar Socket.IO e carregar clientes
initSocket();
loadClients();
@@ -479,6 +480,7 @@
// Atualizar a cada 3 segundos
setInterval(loadClients, 3000);
</script>
</main>
</body>
</html>
+54 -52
View File
@@ -19,22 +19,22 @@
<a href="/" class="nav-item active">
<span class="icon">🖼️</span> Pacientes
</a>
<a href="#" class="nav-item" onclick="openCreatePatientModal(); return false;">
<a href="/?action=new-patient" class="nav-item">
<span class="icon"></span> Novo Paciente
</a>
<a href="/clients" class="nav-item">
<span class="icon">🖥️</span> Dispositivos
</a>
<a href="#" class="nav-item" onclick="openSettingsModal(); return false;">
<a href="/?action=settings" class="nav-item">
<span class="icon">⚙️</span> Configurações
</a>
<a href="#" class="nav-item" onclick="openPluginsModal(); return false;">
<a href="/?action=plugins" class="nav-item">
<span class="icon">🔌</span> Plugins / Wasabi
</a>
<a href="#" class="nav-item" onclick="openSyncModal(); return false;">
<a href="/?action=sync" class="nav-item">
<span class="icon">🔄</span> Sincronização
</a>
<a href="/reset" class="nav-item" style="color: #dc3545;">
<a href="/reset" class="nav-item text-danger">
<span class="icon">⚠️</span> Zerar Sistema
</a>
<a href="/admin-clinics" class="nav-item">
@@ -44,19 +44,19 @@
<span class="icon">🚪</span> Sair
</a>
<div class="nav-divider"></div>
<div style="padding: 10px 20px;">
<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;">
<option value="" style="color: black;">📋 Todos os Clientes</option>
<div class="sidebar-filter-wrapper">
<select id="clientFilter" class="client-filter">
<option value="">📋 Todos os Clientes</option>
</select>
</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>
</button>
</nav>
<div style="padding: 16px 20px; border-top: 1px solid rgba(255,255,255,0.08); margin-top: auto;">
<div style="display: flex; align-items: center; gap: 8px; opacity: 0.5;">
<span style="font-size: 11px; color: rgba(255,255,255,0.7);">🦷 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>
<div class="sidebar-footer">
<div class="sidebar-brand">
<span>🦷 DentalSys</span>
<span class="version-badge">v2.0.3</span>
</div>
</div>
</aside>
@@ -120,75 +120,77 @@
<!-- Transform Modal -->
<div id="transformModal" class="modal">
<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 style="display: flex; align-items: center; gap: 15px; flex-wrap: wrap;">
<h2 id="transformModalTitle" style="margin: 0; font-size: 1.4rem;"><!-- Dynamic Title (Patient + Date) --></h2>
<div style="display: flex; gap: 8px;">
<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="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="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="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>
<div class="modal-header">
<div class="modal-title-group">
<h2 id="transformModalTitle"><!-- Dynamic Title --></h2>
<div class="modal-actions-group">
<button id="btnShowTransform" class="btn btn-primary" onclick="toggleModalView('transform')" title="Ajustar Imagem">🔄</button>
<button id="btnShowGto" class="btn btn-secondary" onclick="toggleModalView('gto')" title="Gerenciar GTOs">📋</button>
<button id="btnShowInfo" class="btn btn-secondary" onclick="toggleModalInfo()" title="Informações"></button>
<button id="btnResetImage" class="btn btn-danger" onclick="resetImageToOriginal()" title="Resetar para Original">↩️</button>
</div>
</div>
<span class="modal-close" id="closeTransformModal">×</span>
<span class="modal-close" id="closeTransformModal">&times;</span>
</div>
<div class="modal-body" style="padding-top: 15px;">
<div class="modal-body">
<!-- 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 -->
</div>
<!-- View 1: Transforms -->
<div id="transformViewWrapper" style="display: flex; flex-direction: column; flex: 1; min-height: 0; height: 100%;">
<div id="transformOptions" class="transform-grid" style="flex: 1; min-height: 0;"></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="transformViewWrapper" class="transform-view-wrapper">
<div id="transformOptions" class="transform-grid"></div>
<div id="transformActionArea" class="transform-action-area" style="display: none;">
<!-- 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;">
<span style="font-weight: 700; font-size: 0.95rem; margin-bottom: 10px; color: var(--dark-color); display: flex; align-items: center; gap: 6px;">
<div class="fine-tune-section">
<span class="fine-tune-title">
📐 Ajuste Fino de Ângulo (Sensor Inclinado)
</span>
<div style="display: flex; align-items: center; gap: 8px; justify-content: center; flex-wrap: wrap;">
<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" style="padding: 6px 12px; font-size: 0.85rem; display: flex; align-items: center; gap: 3px;" onclick="adjustFineTune(-1)" title="Rotacionar -1°">🔄 -1°</button>
<div class="fine-tune-controls">
<button type="button" class="btn btn-secondary btn-small" onclick="adjustFineTune(-5)" title="Rotacionar -5°">🔄 -5°</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>
<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" 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(1)" title="Rotacionar +1°">🔄 +1°</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>
<label style="font-weight: 600; display: block; margin-bottom: 8px;">Nova Observação / Detalhes (Opcional):</label>
<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>
<div style="display: flex; gap: 10px; margin-top: 10px;">
<button class="btn btn-secondary" style="flex: 1; padding: 12px; font-size: 1.1rem;" onclick="clearTransformSelection()">Cancelar</button>
<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-group">
<label>Nova Observação / Detalhes (Opcional):</label>
<textarea id="newImageRemark" class="form-control" rows="2" placeholder="Ex: Raio-X Dente 45..."></textarea>
</div>
<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>
<!-- View 2: GTO List & Link -->
<div id="gtoView" style="display: none;">
<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
<h3 style="margin-top: 0; font-size: 1.1rem; margin-bottom: 15px;">Cadastrar Nova GTO</h3>
<div style="display: flex; gap: 10px;">
<input type="text" id="inlineGtoNumber" class="search-input" placeholder="Número/ID da GTO" style="flex: 1; padding: 10px;">
<input type="text" id="inlineGtoDesc" class="search-input" placeholder="Descrição (Opcional)" style="flex: 2; padding: 10px;">
<div class="gto-create-section">
<h3 class="section-title">Cadastrar Nova GTO</h3>
<div class="form-actions-inline">
<input type="text" id="inlineGtoNumber" class="form-control" placeholder="Número/ID da GTO" style="flex: 1;">
<input type="text" id="inlineGtoDesc" class="form-control" placeholder="Descrição (Opcional)" style="flex: 2;">
<button class="btn btn-primary" onclick="inlineCreateGto()">Cadastrar</button>
</div>
</div>
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 15px;">
<h3 style="margin: 0; font-size: 1.1rem;">Vincular esta imagem a uma GTO:</h3>
<div class="gto-link-section">
<h3 class="section-title">Vincular esta imagem a uma GTO:</h3>
<button class="btn btn-success" onclick="linkImageToGto()" id="btnLinkGto">✅ Vincular Selecionada</button>
</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 -->
</div>
</div>
@@ -198,12 +200,12 @@
<!-- Modal Novo Paciente -->
<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">
<h2>Novo Paciente</h2>
<div class="modal-close" onclick="closeCreatePatientModal()">&times;</div>
</div>
<div class="modal-body" style="overflow-y: auto;">
<div class="modal-body">
<form id="create-patient-form" onsubmit="submitCreatePatient(event)">
<div class="form-group">
<label>Nome *</label>
@@ -248,7 +250,7 @@
<!-- Modal Nova GTO -->
<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">
<h2>Nova GTO</h2>
<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.