Files
VPS 4 Builder 4e03e06e32
continuous-integration/webhook Falha no build do client (VPS4)
feat(client): config token-first — apenas token + URL + pasta + senha local
Config UI simplificado:
- Remove campos: Nome do PC, E-mail do Servidor, Senha do Servidor,
  Usuário do Painel (7 campos → 4 campos).
- Mantém: Token, URL do Servidor, Pasta para Monitorar, Senha do Painel.
- Botão "Verificar": chama GET /api/auth/device-info e exibe card com
  Clínica e PC identificados antes de salvar.
- Ao salvar: valida token no servidor, clientName vem do pc_name retornado.
- Token já salvo: verificado automaticamente ao abrir as configurações.
- Login simplificado: pede só a senha (sem campo de usuário).

client-monitor.js:
- server-identified: se pcName vier do servidor, atualiza config.clientName
  localmente para manter em sincronia com o cadastro.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 15:37:09 +02:00

278 lines
13 KiB
JavaScript

document.addEventListener('DOMContentLoaded', async () => {
// ── Elementos ──────────────────────────────────────────────────
const loginContainer = document.getElementById('login-container');
const settingsContainer = document.getElementById('settings-container');
const loginForm = document.getElementById('login-form');
const loginPasswordInput = document.getElementById('login-password');
const btnLoginCancel = document.getElementById('btn-login-cancel');
const configForm = document.getElementById('config-form');
const tokenInput = document.getElementById('token');
const serverUrlInput = document.getElementById('serverUrl');
const monitorPathInput = document.getElementById('monitorPath');
const adminPasswordInput = document.getElementById('adminPassword');
const deviceCard = document.getElementById('device-card');
const deviceClinic = document.getElementById('device-clinic');
const devicePc = document.getElementById('device-pc');
const btnVerifyToken = document.getElementById('btn-verify-token');
const verifySpinner = document.getElementById('verify-spinner');
const verifyText = document.getElementById('verify-text');
const btnTest = document.getElementById('btn-test');
const testSpinner = document.getElementById('test-spinner');
const testText = document.getElementById('test-text');
const btnCancel = document.getElementById('btn-cancel');
const btnLoginCancelBtn = document.getElementById('btn-login-cancel');
let loadedConfig = null;
let verifiedDevice = null; // { clinicName, pcName } após verificação
// ── Toast ───────────────────────────────────────────────────────
function showToast(message, type = 'success') {
const toast = document.getElementById('toast');
toast.textContent = message;
toast.className = `toast show ${type}`;
setTimeout(() => { toast.className = 'toast'; }, 3500);
}
// ── Inicialização ───────────────────────────────────────────────
try {
if (window.api?.getVersion) {
const version = await window.api.getVersion();
const badge = document.getElementById('app-version-badge');
if (badge) badge.innerText = `Client v${version}`;
}
loadedConfig = await window.api.getConfig();
serverUrlInput.value = loadedConfig.serverUrl || 'https://rx.scoreodonto.com';
monitorPathInput.value = loadedConfig.monitorPath || 'C:\\ProgramData\\RF\\Dental Sensor\\Images';
tokenInput.value = loadedConfig.token || '';
adminPasswordInput.value = loadedConfig.adminPassword || '';
// Se já tem token salvo, verificar automaticamente e mostrar card
if (loadedConfig.token && loadedConfig.token.includes('-')) {
await verifyToken(loadedConfig.token, loadedConfig.serverUrl, false);
}
// Carregar logs no terminal
if (window.api?.getLogs) {
const logs = await window.api.getLogs();
if (logs?.length) renderLogs(logs);
}
// Primeira execução: exibe config direto sem pedir senha
const isFirstRun = !loadedConfig.adminPassword;
if (isFirstRun) {
loginContainer.classList.add('hidden');
settingsContainer.classList.remove('hidden');
} else {
loginContainer.classList.remove('hidden');
settingsContainer.classList.add('hidden');
}
} catch (e) {
showToast('Erro ao carregar configurações: ' + e.message, 'error');
}
// ── Verificar token com o servidor ─────────────────────────────
async function verifyToken(token, serverUrl, showFeedback = true) {
if (!token || !token.includes('-')) {
if (showFeedback) showToast('Token inválido — deve ser o UUID gerado no painel.', 'error');
return false;
}
const url = (serverUrl || serverUrlInput.value).trim().replace(/\/$/, '');
if (!url) {
if (showFeedback) showToast('Informe a URL do servidor antes de verificar.', 'error');
return false;
}
try {
const res = await fetch(`${url}/api/auth/device-info?token=${encodeURIComponent(token)}`);
const data = await res.json();
if (data.success) {
verifiedDevice = data;
deviceClinic.textContent = `🏥 ${data.clinicName}`;
devicePc.textContent = `🖥️ ${data.pcName}`;
deviceCard.classList.remove('hidden');
if (showFeedback) showToast(`Dispositivo identificado: ${data.clinicName}${data.pcName}`, 'success');
return true;
} else {
verifiedDevice = null;
deviceCard.classList.add('hidden');
if (showFeedback) showToast('Token inválido: ' + data.error, 'error');
return false;
}
} catch (e) {
verifiedDevice = null;
deviceCard.classList.add('hidden');
if (showFeedback) showToast('Erro ao verificar token: ' + e.message, 'error');
return false;
}
}
// Botão verificar token
btnVerifyToken.addEventListener('click', async () => {
btnVerifyToken.disabled = true;
verifySpinner.style.display = 'inline-block';
verifyText.textContent = ' Verificando...';
await verifyToken(tokenInput.value.trim(), serverUrlInput.value.trim(), true);
btnVerifyToken.disabled = false;
verifySpinner.style.display = 'none';
verifyText.textContent = 'Verificar';
});
// Re-verificar quando URL muda (e já tem token)
serverUrlInput.addEventListener('change', () => {
if (tokenInput.value.trim().includes('-')) {
verifyToken(tokenInput.value.trim(), serverUrlInput.value.trim(), false);
}
});
// ── Login ───────────────────────────────────────────────────────
loginForm.addEventListener('submit', (e) => {
e.preventDefault();
const correctPassword = loadedConfig?.adminPassword || 'admin';
if (loginPasswordInput.value === correctPassword) {
showToast('Autenticado!', 'success');
loginContainer.classList.add('hidden');
settingsContainer.classList.remove('hidden');
} else {
showToast('Senha incorreta.', 'error');
loginPasswordInput.value = '';
loginPasswordInput.focus();
}
});
// ── Salvar configurações ────────────────────────────────────────
configForm.addEventListener('submit', async (e) => {
e.preventDefault();
const token = tokenInput.value.trim();
const serverUrl = serverUrlInput.value.trim();
const monitorPath = monitorPathInput.value.trim();
const adminPassword = adminPasswordInput.value.trim();
if (!token) {
showToast('Cole o Token do Dispositivo antes de salvar.', 'error');
return;
}
if (!serverUrl) {
showToast('Informe a URL do servidor.', 'error');
return;
}
// Verificar token antes de salvar
const saveBtn = document.getElementById('btn-save');
const saveSpinner = document.getElementById('save-spinner');
saveBtn.disabled = true;
saveSpinner.style.display = 'inline-block';
const valid = await verifyToken(token, serverUrl, true);
saveBtn.disabled = false;
saveSpinner.style.display = 'none';
if (!valid) return;
// clientName vem do pc_name do servidor (verifiedDevice)
const clientName = verifiedDevice?.pcName || loadedConfig?.clientName || 'PC-CLINICA';
const newConfig = {
...loadedConfig,
serverUrl,
monitorPath: monitorPath || 'C:\\ProgramData\\RF\\Dental Sensor\\Images',
clientName,
token,
apiKey: token, // compatibilidade retroativa
serverEmail: '', // não mais necessário
serverPassword: '', // não mais necessário
adminUsername: 'admin',
adminPassword: adminPassword || 'admin',
clientType: 'windows',
};
try {
const success = await window.api.saveConfig(newConfig);
if (success) {
showToast('Configurações salvas! Reconectando...');
setTimeout(() => window.api.closeWindow(), 1200);
} else {
showToast('Falha ao gravar as configurações.', 'error');
}
} catch (err) {
showToast('Erro: ' + err.message, 'error');
}
});
// ── Testar conexão ──────────────────────────────────────────────
btnTest.addEventListener('click', async () => {
const url = serverUrlInput.value.trim();
if (!url) { showToast('Informe a URL do servidor.', 'error'); return; }
btnTest.disabled = true;
testSpinner.style.display = 'inline-block';
testText.textContent = ' Testando...';
try {
const result = await window.api.testServer(url);
if (result.success) showToast('Conexão estabelecida com sucesso!', 'success');
else showToast('Falha: ' + result.error, 'error');
} catch (err) {
showToast('Erro: ' + err.message, 'error');
} finally {
btnTest.disabled = false;
testSpinner.style.display = 'none';
testText.textContent = '⚡ Testar Conexão';
}
});
// ── Toggle senha do painel ──────────────────────────────────────
document.getElementById('toggle-admin-password')?.addEventListener('click', () => {
const type = adminPasswordInput.type === 'password' ? 'text' : 'password';
adminPasswordInput.type = type;
document.getElementById('toggle-admin-password').textContent = type === 'password' ? '👁️' : '🙈';
});
// ── Minimizar ───────────────────────────────────────────────────
btnCancel?.addEventListener('click', () => window.api.closeWindow());
btnLoginCancelBtn?.addEventListener('click', () => window.api.closeWindow());
// ── Terminal de logs ────────────────────────────────────────────
function renderLogs(logs) {
const terminal = document.getElementById('terminal-logs');
if (!terminal) return;
terminal.innerHTML = '';
logs.forEach(log => {
const div = document.createElement('div');
div.className = 'log-line';
div.textContent = log;
terminal.appendChild(div);
});
terminal.scrollTop = terminal.scrollHeight;
}
if (window.api?.onLog) {
window.api.onLog((log) => {
const terminal = document.getElementById('terminal-logs');
if (!terminal) return;
const wait = terminal.querySelector('.log-line');
if (wait?.textContent.includes('Aguardando logs')) terminal.innerHTML = '';
const div = document.createElement('div');
div.className = 'log-line';
div.textContent = log;
terminal.appendChild(div);
terminal.scrollTop = terminal.scrollHeight;
while (terminal.children.length > 100) terminal.removeChild(terminal.firstChild);
});
}
document.getElementById('btn-copy-logs')?.addEventListener('click', () => {
const terminal = document.getElementById('terminal-logs');
const text = Array.from(terminal.children).map(el => el.textContent).join('\n');
navigator.clipboard.writeText(text).then(() => {
const btn = document.getElementById('btn-copy-logs');
const orig = btn.innerHTML;
btn.innerHTML = '✅ Copiado';
setTimeout(() => { btn.innerHTML = orig; }, 2000);
});
});
});