From 1774fe598fd1325cb67a583a05626a5dca54f99c Mon Sep 17 00:00:00 2001 From: infosave2007 Date: Fri, 30 Jan 2026 21:23:57 +0300 Subject: [PATCH] feat: Add real-time online client status updates for servers --- public/index.php | 34 +++++++++++++++++++++++++++++++ templates/servers/view.twig | 40 +++++++++++++++++++++++++++++++++---- 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/public/index.php b/public/index.php index d8d3ac0..cc438a5 100644 --- a/public/index.php +++ b/public/index.php @@ -2401,6 +2401,40 @@ Router::get('/api/servers/{id}/clients', function ($params) { } }); +// API: Get online clients for a server (real-time) +Router::get('/api/servers/{id}/online', function ($params) { + header('Content-Type: application/json'); + + $user = authenticateRequest(); + if (!$user) { + http_response_code(401); + echo json_encode(['error' => 'Unauthorized']); + return; + } + + $serverId = (int) $params['id']; + + try { + $server = new VpnServer($serverId); + $serverData = $server->getData(); + + // Check ownership + if ($serverData['user_id'] != $user['id'] && $user['role'] !== 'admin') { + http_response_code(403); + echo json_encode(['error' => 'Forbidden']); + return; + } + + require_once __DIR__ . '/../inc/ServerMonitoring.php'; + $onlineLogins = ServerMonitoring::getOnlineClientsForServer($serverData); + + echo json_encode(['success' => true, 'online' => $onlineLogins]); + } catch (Exception $e) { + http_response_code(500); + echo json_encode(['error' => $e->getMessage()]); + } +}); + // API: List server protocols Router::get('/api/servers/{id}/protocols', function ($params) { header('Content-Type: application/json'); diff --git a/templates/servers/view.twig b/templates/servers/view.twig index 6592140..52739ef 100644 --- a/templates/servers/view.twig +++ b/templates/servers/view.twig @@ -276,13 +276,13 @@ {% endif %} {{ client.client_ip }} - + {% if client.name in online_logins %} - Online + Online {% elseif client.status == 'active' %} - {{ t('status.active') }} + {{ t('status.active') }} {% else %} - {{ t('status.disabled') }} + {{ t('status.disabled') }} {% endif %} @@ -977,6 +977,38 @@ if (document.querySelector('[id^="client-speed-"]')) { updateClientSpeeds(); setInterval(updateClientSpeeds, 30000); } + +// Real-time online status updates +async function updateOnlineStatus() { + const serverId = {{ server.id }}; + try { + const response = await fetch(`/api/servers/${serverId}/online`, { + credentials: 'same-origin' + }); + if (!response.ok) return; + const data = await response.json(); + if (!data.success) return; + + const onlineSet = new Set(data.online); + document.querySelectorAll('td[data-client-name]').forEach(cell => { + const clientName = cell.dataset.clientName; + const clientStatus = cell.dataset.clientStatus; + + if (onlineSet.has(clientName)) { + cell.innerHTML = 'Online'; + } else if (clientStatus === 'active') { + cell.innerHTML = '{{ t("status.active") }}'; + } else { + cell.innerHTML = '{{ t("status.disabled") }}'; + } + }); + } catch (e) { + console.error('Failed to update online status:', e); + } +} + +// Poll every 5 seconds +setInterval(updateOnlineStatus, 5000); {% endif %} {% endblock %}