document.addEventListener('DOMContentLoaded', function () { // Initialize the compact port display initPortDisplay(); // Add event listener for copy buttons document.addEventListener('click', function (e) { if (e.target.classList.contains('copy-port')) { const port = e.target.dataset.port; copyToClipboard(port); // Visual feedback const originalText = e.target.innerHTML; e.target.innerHTML = ''; setTimeout(() => { e.target.innerHTML = originalText; }, 1000); } }); // Get free port button const getFreePortBtn = document.getElementById('get-free-port'); if (getFreePortBtn) { getFreePortBtn.addEventListener('click', function () { fetch(`/api/server/${serverId}/free-port`) .then(response => response.json()) .then(data => { if (data.success) { copyToClipboard(data.port); showNotification('success', `Free port ${data.port} copied to clipboard`); } else { showNotification('error', data.error || 'Error finding free port'); } }) .catch(error => { console.error('Error:', error); showNotification('error', 'Failed to get free port'); }); }); } }); function initPortDisplay() { const portRangesContainer = document.getElementById('port-ranges'); const usedPortsList = document.getElementById('used-ports-list'); if (!portRangesContainer || !usedPortsList) return; // Get port usage data from server fetch(`/api/server/${serverId}/ports`) .then(response => response.json()) .then(data => { // Process the data and organize into ranges const usedPorts = data.used_ports || []; const ranges = generatePortRanges(usedPorts); // Render port ranges visualization renderPortRanges(ranges, portRangesContainer); // Render list of used ports with copy buttons renderUsedPorts(usedPorts, usedPortsList); }) .catch(error => { console.error('Error fetching port data:', error); portRangesContainer.innerHTML = '
Failed to load port data
'; }); } function generatePortRanges(usedPorts) { // Create a simplified representation of port ranges const ranges = []; const portMax = 9100; const segmentSize = 100; for (let i = 0; i < portMax; i += segmentSize) { const start = i; const end = Math.min(i + segmentSize - 1, portMax); // Count used ports in this range const usedCount = usedPorts.filter(port => port >= start && port <= end).length; const percentageUsed = (usedCount / segmentSize) * 100; ranges.push({ start, end, percentageUsed, isEmpty: usedCount === 0, isFull: usedCount === segmentSize }); } return ranges; } function renderPortRanges(ranges, container) { let html = ''; ranges.forEach(range => { const width = (range.end - range.start + 1) / 91; // Calculate relative width const cssClass = range.percentageUsed > 0 ? 'port-range-used' : 'port-range-free'; html += `
`; }); container.innerHTML = html; } function renderUsedPorts(usedPorts, container) { if (usedPorts.length === 0) { container.innerHTML = '
No ports in use
'; return; } let html = ''; usedPorts.sort((a, b) => a - b).forEach(port => { html += `
${port}
`; }); container.innerHTML = html; } function copyToClipboard(text) { // Use modern clipboard API with fallback if (navigator.clipboard) { navigator.clipboard.writeText(text) .catch(err => { console.error('Failed to copy: ', err); fallbackCopyToClipboard(text); }); } else { fallbackCopyToClipboard(text); } } function fallbackCopyToClipboard(text) { const textArea = document.createElement('textarea'); textArea.value = text; textArea.style.position = 'fixed'; textArea.style.left = '-999999px'; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { document.execCommand('copy'); } catch (err) { console.error('Fallback copy failed:', err); } document.body.removeChild(textArea); } function showNotification(type, message) { const notificationArea = document.getElementById('notification-area'); if (!notificationArea) return; const notification = document.createElement('div'); notification.className = `alert alert-${type === 'error' ? 'danger' : type} alert-dismissible fade show notification`; notification.innerHTML = ` ${message} `; notificationArea.appendChild(notification); // Auto dismiss after 5 seconds setTimeout(() => { notification.classList.remove('show'); setTimeout(() => { notificationArea.removeChild(notification); }, 300); }, 5000); }