feat(client): config token-first — apenas token + URL + pasta + senha local
continuous-integration/webhook Falha no build do client (VPS4)
continuous-integration/webhook Falha no build do client (VPS4)
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>
This commit is contained in:
@@ -1073,6 +1073,15 @@ function connectToServer() {
|
||||
? `Clínica: ${info.clinicName}`
|
||||
: '⚠️ Clínica não identificada — configure o Token no painel de configurações';
|
||||
addLog(`✅ Identificação confirmada: PC=${info.name} | ${clinicLabel}`);
|
||||
|
||||
// Se o servidor enviou o pcName autoritativo (via machine_token),
|
||||
// atualiza o clientName local para manter em sincronia.
|
||||
if (info.pcName && info.pcName !== config.clientName) {
|
||||
addLog(`🔄 Nome do PC atualizado pelo servidor: ${info.pcName}`);
|
||||
config.clientName = info.pcName;
|
||||
try { persistConfig(); } catch (_) {}
|
||||
}
|
||||
|
||||
isIdentified = true;
|
||||
sendStatusToParent();
|
||||
});
|
||||
|
||||
+53
-47
@@ -25,22 +25,17 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- MAIN CONTENT AREA -->
|
||||
<main class="main-content">
|
||||
|
||||
<!-- 1. LOGIN SCREEN -->
|
||||
|
||||
<!-- 1. TELA DE LOGIN -->
|
||||
<div id="login-container" class="container-panel">
|
||||
<div class="panel-header">
|
||||
<h2>🔒 Acesso Restrito</h2>
|
||||
<p>Insira as credenciais do Administrador para alterar as configurações.</p>
|
||||
<p>Insira a senha do painel para alterar as configurações.</p>
|
||||
</div>
|
||||
<form id="login-form" class="login-form">
|
||||
<div class="form-group">
|
||||
<label for="login-username">👤 Usuário</label>
|
||||
<input type="text" id="login-username" placeholder="Digite o usuário" required autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="login-password">🔑 Senha</label>
|
||||
<label for="login-password">🔑 Senha do Painel</label>
|
||||
<input type="password" id="login-password" placeholder="Digite a senha" required>
|
||||
</div>
|
||||
<div class="login-actions">
|
||||
@@ -50,64 +45,76 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 2. CONFIGURATION SCREEN (Hidden by default, shown after login or on first run) -->
|
||||
<!-- 2. TELA DE CONFIGURAÇÃO -->
|
||||
<div id="settings-container" class="container-panel hidden">
|
||||
|
||||
<!-- Card de identidade do dispositivo (preenchido após validar o token) -->
|
||||
<div id="device-card" class="hidden" style="
|
||||
background: linear-gradient(135deg, rgba(124,77,255,0.12), rgba(0,229,255,0.08));
|
||||
border: 1px solid rgba(124,77,255,0.3);
|
||||
border-radius: 12px;
|
||||
padding: 14px 18px;
|
||||
margin-bottom: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;">
|
||||
<span style="font-size: 2rem; line-height:1;">🏥</span>
|
||||
<div>
|
||||
<div id="device-clinic" style="font-weight:700; font-size:1rem;"></div>
|
||||
<div id="device-pc" style="font-size:0.8rem; opacity:0.7; margin-top:2px;"></div>
|
||||
</div>
|
||||
<span style="margin-left:auto; background:rgba(34,197,94,0.15); color:#16a34a; border:1px solid rgba(34,197,94,0.3); border-radius:20px; padding:3px 12px; font-size:0.75rem; font-weight:700;">✓ Token válido</span>
|
||||
</div>
|
||||
|
||||
<form id="config-form" class="config-form">
|
||||
<div class="form-grid">
|
||||
<!-- Machine Name - High Priority -->
|
||||
|
||||
<!-- Token do servidor (único campo de credencial) -->
|
||||
<div class="form-group full-width">
|
||||
<label for="clientName">🖥️ Nome Deste Computador</label>
|
||||
<input type="text" id="clientName" placeholder="Ex: PC-CONSULTORIO-1" required autocomplete="off">
|
||||
<small class="help-text">Insira um nome único identificável no painel da ScoreOdonto.</small>
|
||||
<label for="token">🔑 Token do Dispositivo</label>
|
||||
<div style="display:flex; gap:8px;">
|
||||
<input type="text" id="token"
|
||||
placeholder="Cole aqui o token gerado no painel do servidor"
|
||||
autocomplete="off" spellcheck="false"
|
||||
style="font-family:'JetBrains Mono',monospace; font-size:0.82rem; flex:1;">
|
||||
<button type="button" id="btn-verify-token" class="btn btn-outline" style="white-space:nowrap;">
|
||||
<span id="verify-spinner" class="spinner"></span>
|
||||
<span id="verify-text">Verificar</span>
|
||||
</button>
|
||||
</div>
|
||||
<small class="help-text">Token gerado em <b>Clínicas e Acessos</b> no painel do servidor. Ao verificar, a clínica e o PC são identificados automaticamente.</small>
|
||||
</div>
|
||||
|
||||
<!-- Server URL -->
|
||||
<!-- URL do servidor -->
|
||||
<div class="form-group">
|
||||
<label for="serverUrl">🌐 URL do Servidor</label>
|
||||
<input type="text" id="serverUrl" placeholder="https://rx.scoreodonto.com" required autocomplete="off">
|
||||
<input type="text" id="serverUrl"
|
||||
placeholder="https://rx.scoreodonto.com"
|
||||
autocomplete="off">
|
||||
</div>
|
||||
|
||||
<!-- Monitor Path -->
|
||||
<!-- Pasta a monitorar -->
|
||||
<div class="form-group">
|
||||
<label for="monitorPath">📁 Pasta para Monitorar</label>
|
||||
<input type="text" id="monitorPath" placeholder="C:\ProgramData\RF\Dental Sensor\Images" required autocomplete="off">
|
||||
<input type="text" id="monitorPath"
|
||||
placeholder="C:\ProgramData\RF\Dental Sensor\Images"
|
||||
autocomplete="off">
|
||||
</div>
|
||||
|
||||
<!-- Server Email -->
|
||||
<div class="form-group">
|
||||
<label for="serverEmail">📧 E-mail do Servidor</label>
|
||||
<input type="email" id="serverEmail" placeholder="email@clinica.com" required autocomplete="off">
|
||||
</div>
|
||||
|
||||
<!-- Server Password -->
|
||||
<div class="form-group">
|
||||
<label for="serverPassword">🔑 Senha do Servidor</label>
|
||||
<input type="password" id="serverPassword" placeholder="Sua senha no servidor" required autocomplete="off">
|
||||
</div>
|
||||
|
||||
<!-- Server Token -->
|
||||
<div class="form-group">
|
||||
<label for="token">🔑 Token do Servidor</label>
|
||||
<input type="text" id="token" placeholder="Token de 48 caracteres do cadastro" required autocomplete="off">
|
||||
</div>
|
||||
|
||||
<!-- Local Admin Username -->
|
||||
<div class="form-group">
|
||||
<label for="adminUsername">👤 Usuário do Painel</label>
|
||||
<input type="text" id="adminUsername" placeholder="Usuário admin" required autocomplete="off">
|
||||
</div>
|
||||
|
||||
<!-- Local Admin Password -->
|
||||
<!-- Senha do painel local -->
|
||||
<div class="form-group full-width">
|
||||
<label for="adminPassword">🔒 Senha do Painel (Bloqueio de Configurações)</label>
|
||||
<label for="adminPassword">🔒 Senha do Painel Local</label>
|
||||
<div class="password-wrapper">
|
||||
<input type="password" id="adminPassword" placeholder="Senha do painel" required autocomplete="off">
|
||||
<input type="password" id="adminPassword"
|
||||
placeholder="Protege o acesso a estas configurações"
|
||||
autocomplete="off">
|
||||
<button type="button" id="toggle-admin-password" class="btn-toggle-password">👁️</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ACTIONS -->
|
||||
<!-- AÇÕES -->
|
||||
<div class="form-actions">
|
||||
<button type="button" id="btn-test" class="btn btn-outline">
|
||||
<span id="test-spinner" class="spinner"></span>
|
||||
@@ -139,7 +146,6 @@
|
||||
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="config-ui.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+185
-193
@@ -1,94 +1,74 @@
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
// Containers
|
||||
const loginContainer = document.getElementById('login-container');
|
||||
// ── Elementos ──────────────────────────────────────────────────
|
||||
const loginContainer = document.getElementById('login-container');
|
||||
const settingsContainer = document.getElementById('settings-container');
|
||||
|
||||
// Login Form Elements
|
||||
const loginForm = document.getElementById('login-form');
|
||||
const loginUsernameInput = document.getElementById('login-username');
|
||||
const loginForm = document.getElementById('login-form');
|
||||
const loginPasswordInput = document.getElementById('login-password');
|
||||
const btnLoginCancel = document.getElementById('btn-login-cancel');
|
||||
const btnLoginCancel = document.getElementById('btn-login-cancel');
|
||||
|
||||
// Config Form Elements
|
||||
const configForm = document.getElementById('config-form');
|
||||
const clientNameInput = document.getElementById('clientName');
|
||||
const serverUrlInput = document.getElementById('serverUrl');
|
||||
const monitorPathInput = document.getElementById('monitorPath');
|
||||
const serverEmailInput = document.getElementById('serverEmail');
|
||||
const serverPasswordInput = document.getElementById('serverPassword');
|
||||
const tokenInput = document.getElementById('token');
|
||||
const adminUsernameInput = document.getElementById('adminUsername');
|
||||
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');
|
||||
|
||||
// Action Buttons
|
||||
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 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
|
||||
|
||||
// Show message notification
|
||||
// ── Toast ───────────────────────────────────────────────────────
|
||||
function showToast(message, type = 'success') {
|
||||
const toast = document.getElementById('toast');
|
||||
toast.textContent = message;
|
||||
toast.className = `toast show ${type}`;
|
||||
setTimeout(() => {
|
||||
toast.className = 'toast';
|
||||
}, 3000);
|
||||
setTimeout(() => { toast.className = 'toast'; }, 3500);
|
||||
}
|
||||
|
||||
// Initialize layout and load config
|
||||
// ── Inicialização ───────────────────────────────────────────────
|
||||
try {
|
||||
if (window.api && window.api.getVersion) {
|
||||
if (window.api?.getVersion) {
|
||||
const version = await window.api.getVersion();
|
||||
const badge = document.getElementById('app-version-badge');
|
||||
if (badge) badge.innerText = `Client v${version}`;
|
||||
}
|
||||
|
||||
if (window.api && window.api.getLogs) {
|
||||
const logs = await window.api.getLogs();
|
||||
if (logs && logs.length > 0) {
|
||||
const terminalLogs = document.getElementById('terminal-logs');
|
||||
if (terminalLogs) {
|
||||
terminalLogs.innerHTML = ''; // clear wait message
|
||||
logs.forEach(log => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'log-line';
|
||||
div.textContent = log;
|
||||
terminalLogs.appendChild(div);
|
||||
});
|
||||
terminalLogs.scrollTop = terminalLogs.scrollHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadedConfig = await window.api.getConfig();
|
||||
|
||||
// Populate settings form
|
||||
if (loadedConfig.clientName === 'DIGITE-O-NOME-DA-MAQUINA') {
|
||||
clientNameInput.value = '';
|
||||
} else {
|
||||
clientNameInput.value = loadedConfig.clientName || '';
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
serverUrlInput.value = loadedConfig.serverUrl || 'https://rx.scoreodonto.com';
|
||||
monitorPathInput.value = loadedConfig.monitorPath || 'C:\\ProgramData\\RF\\Dental Sensor\\Images';
|
||||
serverEmailInput.value = loadedConfig.serverEmail || '';
|
||||
serverPasswordInput.value = loadedConfig.serverPassword || '';
|
||||
tokenInput.value = loadedConfig.token || '';
|
||||
adminUsernameInput.value = loadedConfig.adminUsername || 'admin';
|
||||
adminPasswordInput.value = loadedConfig.adminPassword || 'admin';
|
||||
|
||||
// Check if it is a first run (clientName not configured yet)
|
||||
const isFirstRun = !loadedConfig.clientName || loadedConfig.clientName === 'DIGITE-O-NOME-DA-MAQUINA';
|
||||
// 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) {
|
||||
// Bypass login overlay and show setup screen directly
|
||||
loginContainer.classList.add('hidden');
|
||||
settingsContainer.classList.remove('hidden');
|
||||
} else {
|
||||
// Show login screen
|
||||
loginContainer.classList.remove('hidden');
|
||||
settingsContainer.classList.add('hidden');
|
||||
}
|
||||
@@ -96,117 +76,145 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
showToast('Erro ao carregar configurações: ' + e.message, 'error');
|
||||
}
|
||||
|
||||
// Handle Login Submit
|
||||
// ── 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 username = loginUsernameInput.value.trim();
|
||||
const password = loginPasswordInput.value;
|
||||
|
||||
const correctUsername = loadedConfig.adminUsername || 'admin';
|
||||
const correctPassword = loadedConfig.adminPassword || 'admin';
|
||||
|
||||
if (username === correctUsername && password === correctPassword) {
|
||||
showToast('Autenticado com sucesso!', 'success');
|
||||
// Transition panels
|
||||
const correctPassword = loadedConfig?.adminPassword || 'admin';
|
||||
if (loginPasswordInput.value === correctPassword) {
|
||||
showToast('Autenticado!', 'success');
|
||||
loginContainer.classList.add('hidden');
|
||||
settingsContainer.classList.remove('hidden');
|
||||
} else {
|
||||
showToast('Usuário ou senha inválidos.', 'error');
|
||||
showToast('Senha incorreta.', 'error');
|
||||
loginPasswordInput.value = '';
|
||||
loginPasswordInput.focus();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle Config Form Submit
|
||||
// ── Salvar configurações ────────────────────────────────────────
|
||||
configForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const name = clientNameInput.value.trim();
|
||||
if (!name || name === 'DIGITE-O-NOME-DA-MAQUINA') {
|
||||
showToast('Por favor, informe um nome válido para o computador.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const token = tokenInput.value.trim();
|
||||
const serverUrl = serverUrlInput.value.trim();
|
||||
const serverEmail = serverEmailInput.value.trim();
|
||||
const serverPassword = serverPasswordInput.value;
|
||||
const token = tokenInput.value.trim();
|
||||
const monitorPath = monitorPathInput.value.trim();
|
||||
const adminPassword = adminPasswordInput.value.trim();
|
||||
|
||||
// Validar credenciais no servidor
|
||||
const saveSpinner = document.getElementById('save-spinner');
|
||||
const saveBtn = document.getElementById('btn-save');
|
||||
saveBtn.disabled = true;
|
||||
if (saveSpinner) saveSpinner.style.display = 'inline-block';
|
||||
|
||||
try {
|
||||
showToast('Validando credenciais com o servidor...', 'info');
|
||||
const verifyResult = await window.api.verifyCredentials(serverUrl, serverEmail, serverPassword, token);
|
||||
|
||||
if (saveSpinner) saveSpinner.style.display = 'none';
|
||||
saveBtn.disabled = false;
|
||||
|
||||
if (!verifyResult.success) {
|
||||
showToast('Falha na validação: ' + verifyResult.error, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
showToast('Credenciais confirmadas pelo servidor!', 'success');
|
||||
} catch (authErr) {
|
||||
if (saveSpinner) saveSpinner.style.display = 'none';
|
||||
saveBtn.disabled = false;
|
||||
showToast('Erro ao conectar ao servidor para validar: ' + authErr.message, 'error');
|
||||
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,
|
||||
clientName: name,
|
||||
serverUrl: serverUrl,
|
||||
monitorPath: monitorPathInput.value.trim(),
|
||||
apiKey: token, // Salva o token também como apiKey para compatibilidade retroativa
|
||||
serverEmail: serverEmail,
|
||||
serverPassword: serverPassword,
|
||||
token: token,
|
||||
adminUsername: adminUsernameInput.value.trim(),
|
||||
adminPassword: adminPasswordInput.value.trim(),
|
||||
clientType: 'windows'
|
||||
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!');
|
||||
setTimeout(() => {
|
||||
window.api.closeWindow();
|
||||
}, 1000);
|
||||
showToast('Configurações salvas! Reconectando...');
|
||||
setTimeout(() => window.api.closeWindow(), 1200);
|
||||
} else {
|
||||
showToast('Falha ao salvar as configurações.', 'error');
|
||||
showToast('Falha ao gravar as configurações.', 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
showToast('Erro: ' + err.message, 'error');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle Test Connection
|
||||
// ── Testar conexão ──────────────────────────────────────────────
|
||||
btnTest.addEventListener('click', async () => {
|
||||
const url = serverUrlInput.value.trim();
|
||||
if (!url) {
|
||||
showToast('Informe a URL do servidor.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
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 na conexão: ' + result.error, 'error');
|
||||
}
|
||||
if (result.success) showToast('Conexão estabelecida com sucesso!', 'success');
|
||||
else showToast('Falha: ' + result.error, 'error');
|
||||
} catch (err) {
|
||||
showToast('Erro: ' + err.message, 'error');
|
||||
} finally {
|
||||
@@ -216,70 +224,54 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
}
|
||||
});
|
||||
|
||||
// Toggle Admin Password Visibility
|
||||
const toggleAdminPassword = document.getElementById('toggle-admin-password');
|
||||
if (toggleAdminPassword) {
|
||||
toggleAdminPassword.addEventListener('click', () => {
|
||||
const type = adminPasswordInput.getAttribute('type') === 'password' ? 'text' : 'password';
|
||||
adminPasswordInput.setAttribute('type', type);
|
||||
toggleAdminPassword.textContent = type === 'password' ? '👁️' : '🙈';
|
||||
// ── 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;
|
||||
}
|
||||
|
||||
// Handle Minimize / Close Window
|
||||
btnCancel.addEventListener('click', () => {
|
||||
window.api.closeWindow();
|
||||
});
|
||||
|
||||
btnLoginCancel.addEventListener('click', () => {
|
||||
window.api.closeWindow();
|
||||
});
|
||||
|
||||
// Terminal Logs
|
||||
const terminalLogs = document.getElementById('terminal-logs');
|
||||
const btnCopyLogs = document.getElementById('btn-copy-logs');
|
||||
|
||||
if (window.api.onLog) {
|
||||
if (window.api?.onLog) {
|
||||
window.api.onLog((log) => {
|
||||
if (terminalLogs) {
|
||||
// remove the waiting message if present
|
||||
const waitingMsg = terminalLogs.querySelector('.log-line');
|
||||
if (waitingMsg && waitingMsg.textContent.includes('Aguardando logs')) {
|
||||
terminalLogs.innerHTML = '';
|
||||
}
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.className = 'log-line';
|
||||
div.textContent = log;
|
||||
terminalLogs.appendChild(div);
|
||||
|
||||
// Auto scroll to bottom
|
||||
terminalLogs.scrollTop = terminalLogs.scrollHeight;
|
||||
|
||||
// Keep max 100 logs
|
||||
while (terminalLogs.children.length > 100) {
|
||||
terminalLogs.removeChild(terminalLogs.firstChild);
|
||||
}
|
||||
}
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
if (btnCopyLogs && terminalLogs) {
|
||||
btnCopyLogs.addEventListener('click', () => {
|
||||
const logsText = Array.from(terminalLogs.children)
|
||||
.map(el => el.textContent)
|
||||
.join('\n');
|
||||
|
||||
navigator.clipboard.writeText(logsText).then(() => {
|
||||
const icon = document.getElementById('copy-icon');
|
||||
const originalText = btnCopyLogs.innerHTML;
|
||||
btnCopyLogs.innerHTML = '✅ Copiado';
|
||||
setTimeout(() => {
|
||||
btnCopyLogs.innerHTML = originalText;
|
||||
}, 2000);
|
||||
}).catch(err => {
|
||||
showToast('Erro ao copiar logs', 'error');
|
||||
});
|
||||
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);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user