This commit is contained in:
pika 2025-03-30 22:31:10 +02:00
parent 9433d9d235
commit 7b6837cf96
7 changed files with 370 additions and 63 deletions

181
app/static/js/ports.js Normal file
View file

@ -0,0 +1,181 @@
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);
}