Files
rx.scoreodonto.com/dental-server/public/clients.html
T
2026-05-24 04:57:33 +02:00

485 lines
16 KiB
HTML

<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Clientes Conectados - Dental Server</title>
<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 {
max-width: 1200px;
margin: 0 auto;
}
.header {
background: white;
padding: 20px;
border-radius: 10px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
color: #333;
margin-bottom: 0;
}
.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;
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
.stat-card {
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.stat-card .label {
color: #666;
font-size: 14px;
margin-bottom: 5px;
}
.stat-card .value {
font-size: 32px;
font-weight: bold;
color: #667eea;
}
.clients-list {
background: white;
border-radius: 10px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.client-item {
padding: 15px;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
}
.client-item:last-child {
border-bottom: none;
}
.client-info {
flex: 1;
}
.client-name {
font-weight: 600;
color: #333;
margin-bottom: 5px;
}
.client-details {
font-size: 12px;
color: #666;
line-height: 1.6;
}
.client-status {
padding: 5px 15px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
margin-left: 10px;
}
.status-connected {
background: #d4edda;
color: #155724;
}
.status-identified {
background: #cce5ff;
color: #004085;
}
.status-unknown {
background: #fff3cd;
color: #856404;
}
.status-testing {
background: #e2e3e5;
color: #383d41;
}
.empty-state {
text-align: center;
padding: 40px;
color: #666;
}
.timestamp {
font-size: 12px;
color: #999;
margin-top: 10px;
}
.test-result {
margin-top: 10px;
padding: 10px;
border-radius: 5px;
font-size: 12px;
}
.test-result.success {
background: #d4edda;
color: #155724;
}
.test-result.error {
background: #f8d7da;
color: #721c24;
}
.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
if (!localStorage.getItem('auth_token')) {
window.location.href = '/login';
}
</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>
</div>
<div class="header-actions">
<a href="/" class="btn btn-link">← 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>
</div>
<div class="stats" id="stats">
<!-- Stats serão carregados aqui -->
</div>
<div class="clients-list">
<div id="clientsList">
<div class="empty-state">Carregando...</div>
</div>
<div class="timestamp" id="timestamp"></div>
</div>
</div>
<script src="/socket.io/socket.io.js"></script>
<script>
let socket = null;
let testInProgress = false;
let testResults = {};
// Conectar ao Socket.IO
function initSocket() {
const token = localStorage.getItem('auth_token');
if (!token) {
window.location.href = '/login';
return;
}
socket = io({ auth: { token } });
socket.on('connect', () => {
console.log('✅ Conectado ao servidor');
});
socket.on('disconnect', () => {
console.log('❌ Desconectado do servidor');
});
// Listener para atualização de lista de clientes
socket.on('clients-list-updated', (clients) => {
console.log('📋 Lista de clientes atualizada:', clients);
loadClients();
});
// Listener para resposta de teste de conexão
socket.on('test-connection-ack', (data) => {
console.log('✅ Teste de conexão respondido:', data);
if (data.testId) {
testResults[data.socketId] = {
success: true,
latency: data.latency || 'N/A',
timestamp: data.timestamp
};
updateTestResults();
}
});
}
async function loadClients() {
try {
const response = await fetch('/api/socket/status');
const data = await response.json();
// Atualizar stats
document.getElementById('stats').innerHTML = `
<div class="stat-card">
<div class="label">Total de Conexões</div>
<div class="value">${data.totalConnections}</div>
</div>
<div class="stat-card">
<div class="label">Clientes Identificados</div>
<div class="value">${data.identifiedClients}</div>
</div>
<div class="stat-card">
<div class="label">Não Identificados</div>
<div class="value">${data.totalConnections - data.identifiedClients}</div>
</div>
`;
// Atualizar lista
if (data.clients.length === 0) {
document.getElementById('clientsList').innerHTML = `
<div class="empty-state">
<h2>📭 Nenhum cliente conectado</h2>
<p>Aguardando conexões...</p>
</div>
`;
} else {
let html = '';
// Mostrar identificados primeiro
data.identified.forEach(client => {
const testResult = testResults[client.socketId];
html += `
<div class="client-item">
<div class="client-info">
<div class="client-name">${client.name || 'Sem nome'}</div>
<div class="client-details">
Tipo: ${client.type} |
Sistema: ${client.system || 'N/A'} |
Versão: ${client.version || 'N/A'} |
ID: ${client.socketId}
</div>
<div class="client-details" style="margin-top: 5px;">
Conectado em: ${new Date(client.connectedAt).toLocaleString('pt-BR')}
</div>
${testResult ? `
<div class="test-result ${testResult.success ? 'success' : 'error'}">
${testResult.success ?
`✅ Teste OK - Latência: ${testResult.latency}ms` :
`${testResult.error || 'Falha no teste'}`
}
</div>
` : ''}
</div>
<div class="client-status ${testInProgress ? 'status-testing' : 'status-identified'}">
${testInProgress ? '🔄 Testando...' : '✅ Identificado'}
</div>
</div>
`;
});
// Mostrar não identificados (como abas do navegador visualizando o painel)
data.clients.filter(c => !c.isIdentified).forEach(client => {
const isLocal = client.remoteAddress === '::1' || client.remoteAddress === '127.0.0.1' || client.remoteAddress === '::ffff:127.0.0.1';
const displayName = isLocal ? 'Painel Web (Navegador Local)' : 'Conexão Web (Painel de Monitoramento)';
const displayStatus = '🌐 Apenas Visualização';
html += `
<div class="client-item">
<div class="client-info">
<div class="client-name">${displayName}</div>
<div class="client-details">
Transport: ${client.transport} |
ID: ${client.id}
</div>
<div class="client-details" style="margin-top: 5px;">
IP: ${client.remoteAddress || 'N/A'}
</div>
</div>
<div class="client-status" style="background: #e2e8f0; color: #4a5568;">
${displayStatus}
</div>
</div>
`;
});
document.getElementById('clientsList').innerHTML = html;
}
// Atualizar timestamp
document.getElementById('timestamp').textContent =
`Última atualização: ${new Date(data.timestamp).toLocaleString('pt-BR')}`;
} catch (error) {
console.error('Erro ao carregar clientes:', error);
document.getElementById('clientsList').innerHTML = `
<div class="empty-state">
<h2>❌ Erro ao carregar</h2>
<p>${error.message}</p>
</div>
`;
}
}
async function testConnection() {
if (testInProgress) {
return;
}
testInProgress = true;
testResults = {};
const testBtn = document.getElementById('testBtn');
testBtn.disabled = true;
testBtn.innerHTML = '<span class="loading"></span> Testando...';
try {
// Limpar resultados anteriores
testResults = {};
loadClients(); // Atualizar UI para mostrar estado de teste
// Chamar endpoint de teste
const response = await fetch('/api/socket/test-connection', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
const result = await response.json();
// Processar resultados
if (result.testResults && result.testResults.length > 0) {
result.testResults.forEach(test => {
testResults[test.socketId] = test;
});
}
// Atualizar interface
updateTestResults();
loadClients();
// Mostrar resumo
const successCount = result.testResults ? result.testResults.filter(r => r.success).length : 0;
const totalCount = result.testResults ? result.testResults.length : 0;
if (totalCount > 0) {
alert(`✅ Teste concluído!\n${successCount} de ${totalCount} clientes responderam com sucesso.`);
} else {
alert('⚠️ Nenhum cliente identificado para testar!');
}
} catch (error) {
console.error('Erro no teste de conexão:', error);
alert('❌ Erro ao executar teste: ' + error.message);
} finally {
testInProgress = false;
testBtn.disabled = false;
testBtn.innerHTML = '🧪 Testar Conexão';
}
}
function updateTestResults() {
// Atualizar interface com resultados
loadClients();
}
// Inicializar Socket.IO e carregar clientes
initSocket();
loadClients();
// Atualizar a cada 3 segundos
setInterval(loadClients, 3000);
</script>
</body>
</html>