Files
rx.scoreodonto.com/dental-server/public/clients.html
T
VPS 4 Builder 9e8d920b75
continuous-integration/webhook Deploy concluido (rx.scoreodonto.com)
feat: premium layout unification under RF Dental and sidebar/toolbar reorganization
2026-05-26 20:26:57 +02:00

473 lines
17 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>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="stylesheet" href="/style.css">
<style>
.clients-container {
max-width: 1200px;
margin: 0 auto;
padding: 40px 20px;
}
.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;
}
.clients-header h1 {
color: var(--dark-color);
margin-bottom: 4px;
font-size: 1.8rem;
font-weight: 700;
}
.header-actions {
display: flex;
gap: 12px;
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 24px;
margin-bottom: 32px;
}
.stat-card {
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: var(--text-secondary);
font-size: 0.95rem;
font-weight: 600;
margin-bottom: 8px;
}
.stat-card .value {
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-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: 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:hover {
border-color: var(--primary-light);
background: var(--bg-surface);
box-shadow: var(--shadow-sm);
}
.client-info {
flex: 1;
}
.client-name {
font-weight: 700;
font-size: 1.1rem;
color: var(--dark-color);
margin-bottom: 6px;
}
.client-details {
font-size: 0.9rem;
color: var(--text-secondary);
line-height: 1.5;
}
.client-status {
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: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
}
.status-identified {
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
color: white;
}
.status-unknown {
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
color: white;
}
.status-testing {
background: #e2e8f0;
color: var(--text-primary);
}
.timestamp {
font-size: 0.85rem;
color: var(--text-muted);
margin-top: 20px;
text-align: right;
font-weight: 500;
}
.test-result {
margin-top: 12px;
padding: 10px 16px;
border-radius: var(--radius-sm);
font-size: 0.85rem;
font-weight: 600;
}
.test-result.success {
background: rgba(16, 185, 129, 0.1);
color: var(--success-color);
border: 1px solid rgba(16, 185, 129, 0.2);
}
.test-result.error {
background: rgba(239, 68, 68, 0.1);
color: var(--danger-color);
border: 1px solid rgba(239, 68, 68, 0.2);
}
</style>
<script>
// Redirecionar se não estiver autenticado
if (!localStorage.getItem('auth_token')) {
window.location.href = '/login';
}
</script>
</head>
<body class="app-container">
<!-- Sidebar -->
<aside class="sidebar">
<div class="sidebar-header">
<div class="logo-icon">
<i class="fa-solid fa-tooth"></i>
</div>
<h2>RF Dental</h2>
</div>
<nav class="sidebar-nav">
<a href="/" class="nav-item">
<i class="fa-solid fa-chart-line"></i> Dashboard
</a>
<a href="/clients" class="nav-item active">
<i class="fa-solid fa-network-wired"></i> Conexões
</a>
<a href="/admin-clinics" class="nav-item">
<i class="fa-solid fa-hospital"></i> Clínicas e Acessos
</a>
</nav>
<div class="sidebar-footer">
<button id="logoutBtn" class="logout-btn" onclick="logout(); return false;">
<i class="fa-solid fa-arrow-right-from-bracket"></i> Sair
</button>
</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-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>
</div>
<div class="stats" id="stats">
<!-- Stats serão carregados aqui -->
</div>
<div class="clients-list-card">
<div id="clientsList">
<div class="empty-state">
<div class="spinner" style="margin: 0 auto 16px auto;"></div>
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() {
loadClients();
}
function logout() {
localStorage.removeItem('auth_token');
window.location.replace('/login');
}
// Inicializar Socket.IO e carregar clientes
initSocket();
loadClients();
// Atualizar a cada 3 segundos
setInterval(loadClients, 3000);
</script>
</main>
</body>
</html>