<!DOCTYPE html> <!-- # _ # | | # _ __ ___ ___ _ __ ___ ___ _ _| | _ _ ___ # | '_ ` _ \ / _ \| '_ \ / _ \ / __| | | | || | | / __| # | | | | | | (_) | | | | (_) | (__| |_| | || |_| \__ \ # |_| |_| |_|\___/|_| |_|\___/ \___|\__,_|_(_)__,_|___/ # # (c) Andrea Santaniello 31/10/24 - FRN Weblist --> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>FRN Servers Client Status</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script> <style> body { background-color: #121212; color: #e0e0e0; background-image: url('https://archive.monocul.us/monoculus_sketch_2024_min.png'); background-position: bottom right; background-repeat: no-repeat; } .container-fluid { margin-top: 20px; } .form-control { background-color: #333; color: #e0e0e0; border: 1px solid #444; } .form-control::placeholder { color: #bbb; } .card { background-color: #1e1e1e; border: 1px solid #444; } .card-header { background-color: #333; color: #e0e0e0; } .table { color: #e0e0e0; } .table thead { background-color: #333; } .status-icon { font-size: 1.5rem; } .available { color: #4caf50; } .unavailable { color: #f44336; } .modal-body { font-size: 1.1rem; background-color: #1e1e1e; color: #e0e0e0; } .modal-header { background-color: #333; color: #e0e0e0; } .modal-footer { background-color: #222; } .modal-title { font-weight: bold; } .favorite { color: yellow; } </style> </head> <body> <div class="container-fluid"> <h2 class="text-center mt-4">FRN Servers Client Status</h2> <div class="form-group"> <input type="text" id="searchInput" class="form-control" placeholder="Search clients..."> </div> <div id="servers-container" class="client-table"></div> </div> <div class="modal fade" id="clientModal" tabindex="-1" role="dialog" aria-labelledby="clientModalLabel" aria-hidden="true"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="clientModalLabel">Client Details</h5> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> <p><strong>Status:</strong> <span id="modalStatus"></span></p> <p><strong>Muted:</strong> <span id="modalMuted"></span></p> <p><strong>Country:</strong> <span id="modalCountry"></span></p> <p><strong>City:</strong> <span id="modalCity"></span></p> <p><strong>Band/Channel:</strong> <span id="modalBandChannel"></span></p> <p><strong>Client Type:</strong> <span id="modalClientType"></span></p> <p><strong>Callsign:</strong> <span id="modalCallsign"></span></p> <p><strong>Description:</strong> <span id="modalDescription"></span></p> <p><strong>ID:</strong> <pre id="modalId" style="color: #ffffff;"></pre></p> </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> </div> </div> </div> </div> <script> function updateClientStatus() { $.get("http://localhost:8181/clients", function (data) { localStorage.setItem('serversData', JSON.stringify(data.servers)); renderServers(data.servers); }); } function renderServers(servers) { let favoriteServers = JSON.parse(localStorage.getItem('favoriteServers')) || []; servers.sort((a, b) => { const aFav = favoriteServers.includes(a.server_name) ? 1 : 0; const bFav = favoriteServers.includes(b.server_name) ? 1 : 0; return bFav - aFav; }); let serversHtml = ""; servers.forEach(server => { const isFavorite = favoriteServers.includes(server.server_name); serversHtml += `<div class="card mb-4 server-card"> <div class="card-header"> <h5> <i class="fa-solid fa-star star-icon ${isFavorite ? 'favorite' : ''}" data-server-name="${server.server_name}"></i> ${server.server_name} </h5> </div> <div class="card-body"> <div class="table-responsive"><table class="table table-bordered w-100"> <thead class="thead-dark"> <tr> <th>Status</th> <th>Muted</th> <th>Country</th> <th>City</th> <th>Band/Channel</th> <th>Client Type</th> <th>Callsign</th> <th>Description</th> </tr> </thead> <tbody>`; server.clients.forEach(client => { let clientIcon = ""; if (client.client_type === "Gateway") { clientIcon = `<i class="fa-solid fa-tower-broadcast"></i>`; } else if (client.client_type === "Crosslink") { clientIcon = `<i class="fa-solid fa-arrows-alt"></i>`; } else if (client.client_type === "PC Only") { clientIcon = `<i class="fa fa-desktop"></i>`; } serversHtml += `<tr class="client-row" data-client='${JSON.stringify(client)}'> <td class="text-center"> <i class="fas fa-circle status-icon ${client.status === 'Available' ? 'available' : 'unavailable'}"></i> </td> <td>${client.muted}</td> <td>${client.country}</td> <td>${client.city}</td> <td>${client.band_channel}</td> <td>${clientIcon} ${client.client_type}</td> <td>${client.callsign}</td> <td>${client.description}</td> </tr>`; }); serversHtml += `</tbody> </table></div> </div> </div>`; }); $('#servers-container').html(serversHtml); } $(document).ready(function () { updateClientStatus(); setInterval(updateClientStatus, 5000); // Update every 5 seconds $(document).on('click', '.client-row', function () { const client = $(this).data('client'); $('#modalStatus').text(client.status); $('#modalMuted').text(client.muted); $('#modalCountry').text(client.country); $('#modalCity').text(client.city); $('#modalBandChannel').text(client.band_channel); $('#modalClientType').text(client.client_type); $('#modalCallsign').text(client.callsign); $('#modalDescription').text(client.description); $('#modalId').text(client.id); $('#clientModal').modal('show'); }); $('#searchInput').on('keyup', function () { const value = $(this).val().toLowerCase(); localStorage.setItem('searchValue', value); filterServers(value); }); function filterServers(value) { $('.server-card').each(function () { let serverVisible = false; $(this).find('.client-row').each(function () { const clientVisible = $(this).text().toLowerCase().indexOf(value) > -1; $(this).toggle(clientVisible); if (clientVisible) { serverVisible = true; } }); $(this).toggle(serverVisible); }); } $(document).on('click', '.star-icon', function () { const serverName = $(this).data('server-name'); let favoriteServers = JSON.parse(localStorage.getItem('favoriteServers')) || []; if (favoriteServers.includes(serverName)) { favoriteServers = favoriteServers.filter(name => name !== serverName); $(this).removeClass('favorite'); } else { favoriteServers.push(serverName); $(this).addClass('favorite'); } localStorage.setItem('favoriteServers', JSON.stringify(favoriteServers)); renderServers(JSON.parse(localStorage.getItem('serversData')) || []); const searchValue = localStorage.getItem('searchValue') || ""; filterServers(searchValue); }); const savedSearchValue = localStorage.getItem('searchValue') || ""; $('#searchInput').val(savedSearchValue); filterServers(savedSearchValue); }); </script> </body> </html>