From 7b6837cf96dcd0cfb0f41e2c22bd38d9e9164c90 Mon Sep 17 00:00:00 2001 From: pika Date: Sun, 30 Mar 2025 22:31:10 +0200 Subject: [PATCH] wip --- app/routes/__pycache__/api.cpython-313.pyc | Bin 13023 -> 14614 bytes app/routes/api.py | 39 ++++- app/static/js/ports.js | 181 +++++++++++++++++++++ app/templates/dashboard/app_view.html | 6 +- app/templates/dashboard/server_view.html | 128 ++++++++------- app/templates/layout.html | 79 ++++++++- instance/app.db | Bin 36864 -> 36864 bytes 7 files changed, 370 insertions(+), 63 deletions(-) create mode 100644 app/static/js/ports.js diff --git a/app/routes/__pycache__/api.cpython-313.pyc b/app/routes/__pycache__/api.cpython-313.pyc index 98194e4443c4b3ca1e8ffd1e1c1dd70b08aa6f22..7412ec5eb9a442047b227c006073fbc224e70ab4 100644 GIT binary patch delta 1290 zcmZ`%UrZE77@wWpy90LbF9)n3Fd!(q$FXOG(i3w98?0WVF-%Hik#(LyPgn^v$-Bl1bMQL_ws%b?n+w3#_sj>pmy3GL<#%~<1HxxRxgKpy5s$sUMY zu-|&l)tYz+SEI|kg*9YBbk>K^%n3UX3eR1{=K~0dh#*ri`4Rtip&>LR=(r2_Y?W?< zL;};~Cy~Nuo4+tJBsD!vW-ViaI8Bhr8A?*5+}koo&q65$I}81W#TB#?7AiC7WFR6m zhG>+9hX7u=&Dk5*4N3*rD6Vp*#!d$rBpI+m7JDIKgA#~d1*VM5*w!y`I-x(p=Oi?4 z-LUhm4Rf1r>n=X*-ax$MDI(tGc~^3w&~eKRmpt>=40H?Gmj6>{k{U*|fi<;H zB9`h%l(3&2?zMlGCAv?eJNU(g0kS>6ru3bTyoNbQqdpK_0qgU*5wnU%9CI;0jvyErc<^*WWp5=(XPjmV` z+?DQc2Ix3~&?OL`8Qu}@Ot$O7%x<1VQnym+>D2XUgQQK8`N|+PWlkC7(^xDPjRR~a z--ZfvO65nav&T8~2X@Z_ga3GE$2RmdoFDX#mwAUhXN-wN9|C2V8HWY)XtM(O!i%H! zU#)&-qKNj3x~#6LiM*Ql?NClle2JXeQTgV%mnhsy@atHaDe*C0o3d~j*0 z`(<6ncVa=TeIa)ISzTXfXfDV{z*UER;Ht2{gm|xi-GyXj(}$$$LbY574jeFD_+6<6 zg+{ot?YuRo>jmYg-J-pBGqcl&c3rHo>Jfy{J+ewq60!SuyofzqQ!#|F@U6Tc=c<&I z!Ij>5Imh)CIR82pInM@/ports', methods=['GET']) +@login_required +def get_server_ports(server_id): + """Get all used ports for a server""" + server = Server.query.get_or_404(server_id) + + # Get all ports associated with this server + ports = Port.query.filter_by(server_id=server_id).all() + used_ports = [port.number for port in ports] + + return jsonify({ + 'server_id': server_id, + 'used_ports': used_ports + }) + +@bp.route('/server//free-port', methods=['GET']) +@login_required +def get_free_port(server_id): + """Find a free port for a server""" + server = Server.query.get_or_404(server_id) + + # Get all ports associated with this server + used_ports = [port.number for port in Port.query.filter_by(server_id=server_id).all()] + + # Find the first free port (starting from 8000) + for port_number in range(8000, 9000): + if port_number not in used_ports: + return jsonify({ + 'success': True, + 'port': port_number + }) + + return jsonify({ + 'success': False, + 'error': 'No free ports available in the range 8000-9000' + }) \ No newline at end of file diff --git a/app/static/js/ports.js b/app/static/js/ports.js new file mode 100644 index 0000000..7d1a885 --- /dev/null +++ b/app/static/js/ports.js @@ -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 = ''; + 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); +} \ No newline at end of file diff --git a/app/templates/dashboard/app_view.html b/app/templates/dashboard/app_view.html index ed7b910..8363d72 100644 --- a/app/templates/dashboard/app_view.html +++ b/app/templates/dashboard/app_view.html @@ -120,7 +120,7 @@
{% if app.documentation %} - {{ app.documentation|markdown|safe }} + {{ app.documentation|markdown }} {% else %}
@@ -147,7 +147,7 @@