181 lines
No EOL
5.2 KiB
JavaScript
181 lines
No EOL
5.2 KiB
JavaScript
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 = '<span class="ti ti-check"></span>';
|
|
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 = '<div class="alert alert-danger">Failed to load port data</div>';
|
|
});
|
|
}
|
|
|
|
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 += `<div
|
|
class="port-range ${cssClass}"
|
|
style="flex-grow: ${width};"
|
|
title="${range.start}-${range.end}: ${range.percentageUsed.toFixed(1)}% used"
|
|
></div>`;
|
|
});
|
|
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
function renderUsedPorts(usedPorts, container) {
|
|
if (usedPorts.length === 0) {
|
|
container.innerHTML = '<div class="text-muted">No ports in use</div>';
|
|
return;
|
|
}
|
|
|
|
let html = '';
|
|
usedPorts.sort((a, b) => a - b).forEach(port => {
|
|
html += `
|
|
<div class="used-port-tag">
|
|
${port}
|
|
<span class="ti ti-copy copy-port" data-port="${port}" title="Copy port"></span>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
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}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
`;
|
|
|
|
notificationArea.appendChild(notification);
|
|
|
|
// Auto dismiss after 5 seconds
|
|
setTimeout(() => {
|
|
notification.classList.remove('show');
|
|
setTimeout(() => {
|
|
notificationArea.removeChild(notification);
|
|
}, 300);
|
|
}, 5000);
|
|
}
|