now the ports cannot be assigned twice, also simmilar application names work
This commit is contained in:
parent
e5300424ef
commit
e9d1f985ae
7 changed files with 57 additions and 106 deletions
|
@ -18,7 +18,7 @@ from flask_wtf import CSRFProtect
|
||||||
import markdown
|
import markdown
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from flask import flash
|
from flask import flash
|
||||||
from app.utils.app_utils import is_port_in_use
|
from app.utils.app_utils import is_port_in_use, validate_port_data
|
||||||
|
|
||||||
bp = Blueprint("api", __name__, url_prefix="/api")
|
bp = Blueprint("api", __name__, url_prefix="/api")
|
||||||
csrf = CSRFProtect()
|
csrf = CSRFProtect()
|
||||||
|
@ -287,89 +287,40 @@ def add_app_port(app_id):
|
||||||
"""Add a port to an application"""
|
"""Add a port to an application"""
|
||||||
app = App.query.get_or_404(app_id)
|
app = App.query.get_or_404(app_id)
|
||||||
|
|
||||||
# Check if request is AJAX (XMLHttpRequest)
|
# Get port details from the form
|
||||||
is_ajax = request.headers.get('X-Requested-With') == 'XMLHttpRequest' or request.accept_mimetypes.best == 'application/json'
|
port_number = request.form.get("port_number")
|
||||||
|
protocol = request.form.get("protocol", "TCP")
|
||||||
|
description = request.form.get("description", "")
|
||||||
|
|
||||||
|
# Validate the port
|
||||||
|
valid, clean_port, error = validate_port_data ( # validate_port_data(
|
||||||
|
port_number,
|
||||||
|
protocol,
|
||||||
|
description,
|
||||||
|
app.server_id,
|
||||||
|
app_id
|
||||||
|
)
|
||||||
|
|
||||||
|
if not valid:
|
||||||
|
flash(error, "danger")
|
||||||
|
return redirect(url_for("dashboard.app_view", app_id=app_id))
|
||||||
|
|
||||||
|
# Create the new port
|
||||||
try:
|
try:
|
||||||
port_number = request.form.get("port_number")
|
|
||||||
protocol = request.form.get("protocol", "TCP")
|
|
||||||
description = request.form.get("description", "")
|
|
||||||
|
|
||||||
# Validate port data with server-side conflict check
|
|
||||||
valid, clean_port, error = validate_port_data(
|
|
||||||
port_number, protocol, description, app.server_id, app_id
|
|
||||||
)
|
|
||||||
|
|
||||||
if not valid:
|
|
||||||
flash(error, "danger")
|
|
||||||
|
|
||||||
# If port is in use by another app, provide a direct link
|
|
||||||
if "already in use by application" in error:
|
|
||||||
app_name = error.split("'")[1] # Extract app name from error message
|
|
||||||
conflict_app = App.query.filter_by(name=app_name, server_id=app.server_id).first()
|
|
||||||
if conflict_app:
|
|
||||||
edit_url = url_for('dashboard.app_edit', app_id=conflict_app.id)
|
|
||||||
edit_link = f'<a href="{edit_url}">Edit {app_name}</a>'
|
|
||||||
flash(f"Would you like to edit the conflicting application? {edit_link}", "info")
|
|
||||||
|
|
||||||
return (
|
|
||||||
redirect(url_for("dashboard.app_view", app_id=app_id))
|
|
||||||
if not is_ajax
|
|
||||||
else jsonify({"success": False, "error": error})
|
|
||||||
), 400
|
|
||||||
|
|
||||||
# Check if port already exists for this app
|
|
||||||
existing_port = Port.query.filter_by(
|
|
||||||
app_id=app_id, port_number=clean_port, protocol=protocol
|
|
||||||
).first()
|
|
||||||
|
|
||||||
if existing_port:
|
|
||||||
error_msg = f"Port {clean_port}/{protocol} already exists for this application"
|
|
||||||
flash(error_msg, "warning")
|
|
||||||
return (
|
|
||||||
redirect(url_for("dashboard.app_view", app_id=app_id))
|
|
||||||
if not is_ajax
|
|
||||||
else jsonify({"success": False, "error": error_msg})
|
|
||||||
), 400
|
|
||||||
|
|
||||||
# Create new port
|
|
||||||
new_port = Port(
|
new_port = Port(
|
||||||
app_id=app_id,
|
app_id=app_id,
|
||||||
port_number=clean_port,
|
port_number=clean_port,
|
||||||
protocol=protocol,
|
protocol=protocol,
|
||||||
description=description,
|
description=description
|
||||||
)
|
)
|
||||||
db.session.add(new_port)
|
db.session.add(new_port)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
flash(f"Port {clean_port}/{protocol} added successfully", "success")
|
||||||
success_msg = f"Port {clean_port}/{protocol} added successfully"
|
|
||||||
flash(success_msg, "success")
|
|
||||||
|
|
||||||
# If it's a regular form submission (not AJAX), redirect
|
|
||||||
if not is_ajax and request.content_type != 'application/json':
|
|
||||||
return redirect(url_for("dashboard.app_view", app_id=app_id))
|
|
||||||
|
|
||||||
# Otherwise return JSON response
|
|
||||||
return jsonify({
|
|
||||||
"success": True,
|
|
||||||
"message": success_msg,
|
|
||||||
"port": {
|
|
||||||
"id": new_port.id,
|
|
||||||
"port_number": new_port.port_number,
|
|
||||||
"protocol": new_port.protocol,
|
|
||||||
"description": new_port.description
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
error_msg = f"Error adding port: {str(e)}"
|
flash(f"Error adding port: {str(e)}", "danger")
|
||||||
flash(error_msg, "danger")
|
|
||||||
return (
|
return redirect(url_for("dashboard.app_view", app_id=app_id))
|
||||||
redirect(url_for("dashboard.app_view", app_id=app_id))
|
|
||||||
if not is_ajax
|
|
||||||
else jsonify({"success": False, "error": error_msg})
|
|
||||||
), 500
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/app/<int:app_id>/ports", methods=["GET"])
|
@bp.route("/app/<int:app_id>/ports", methods=["GET"])
|
||||||
|
|
|
@ -297,13 +297,13 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(`/api/servers/${serverId}/suggest_port`);
|
const response = await fetch(`/api/server/${serverId}/free-port`);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.port) {
|
if (data.success && data.port) {
|
||||||
addPortRow(data.port);
|
addPortRow(data.port);
|
||||||
} else {
|
} else {
|
||||||
showNotification('No available ports found', 'warning');
|
showNotification(data.error || 'No available ports found', 'warning');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error generating random port:', error);
|
console.error('Error generating random port:', error);
|
||||||
|
|
|
@ -325,7 +325,7 @@
|
||||||
<!-- Bootstrap JS -->
|
<!-- Bootstrap JS -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<!-- HTMX for dynamic content -->
|
<!-- HTMX for dynamic content -->
|
||||||
<script src="https://unpkg.com/htmx.org@1.9.2"></script>
|
<!-- <script src="https://unpkg.com/htmx.org@1.9.2"></script> -->
|
||||||
<!-- Custom JS -->
|
<!-- Custom JS -->
|
||||||
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
|
||||||
<script>
|
<script>
|
||||||
|
@ -618,4 +618,4 @@
|
||||||
{% block scripts %}{% endblock %}
|
{% block scripts %}{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -38,15 +38,6 @@ def validate_app_data(name, server_id, existing_app_id=None):
|
||||||
def is_port_in_use(port_number, protocol, server_id, exclude_app_id=None):
|
def is_port_in_use(port_number, protocol, server_id, exclude_app_id=None):
|
||||||
"""
|
"""
|
||||||
Check if a port+protocol combination is already in use by any application on the server
|
Check if a port+protocol combination is already in use by any application on the server
|
||||||
|
|
||||||
Args:
|
|
||||||
port_number: The port number to check
|
|
||||||
protocol: The protocol (TCP, UDP, etc.)
|
|
||||||
server_id: The server ID
|
|
||||||
exclude_app_id: Optional app ID to exclude from the check (for edit operations)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Tuple (bool, app_name): Is port in use and by which app
|
|
||||||
"""
|
"""
|
||||||
from sqlalchemy import and_
|
from sqlalchemy import and_
|
||||||
|
|
||||||
|
@ -57,9 +48,9 @@ def is_port_in_use(port_number, protocol, server_id, exclude_app_id=None):
|
||||||
App.server_id == server_id
|
App.server_id == server_id
|
||||||
)
|
)
|
||||||
|
|
||||||
# Exclude the current app if editing
|
# # Exclude the current app if editing
|
||||||
if exclude_app_id:
|
# if exclude_app_id:
|
||||||
query = query.filter(App.id != exclude_app_id)
|
# query = query.filter(App.id != exclude_app_id)
|
||||||
|
|
||||||
result = query.first()
|
result = query.first()
|
||||||
|
|
||||||
|
@ -70,10 +61,19 @@ def is_port_in_use(port_number, protocol, server_id, exclude_app_id=None):
|
||||||
|
|
||||||
def validate_port_data(port_number, protocol, description, server_id=None, app_id=None):
|
def validate_port_data(port_number, protocol, description, server_id=None, app_id=None):
|
||||||
"""
|
"""
|
||||||
Validate port data
|
Validate port data for an application
|
||||||
Returns tuple (valid, clean_port, error_message)
|
|
||||||
|
Args:
|
||||||
|
port_number: The port number to validate
|
||||||
|
protocol: The protocol (TCP/UDP)
|
||||||
|
description: Port description
|
||||||
|
server_id: ID of the server
|
||||||
|
app_id: ID of the application (for excluding the current app when checking conflicts)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (valid, clean_port, error_message)
|
||||||
"""
|
"""
|
||||||
# Existing validation
|
# Check if port number is provided
|
||||||
if not port_number:
|
if not port_number:
|
||||||
return False, None, "Port number is required"
|
return False, None, "Port number is required"
|
||||||
|
|
||||||
|
@ -85,11 +85,10 @@ def validate_port_data(port_number, protocol, description, server_id=None, app_i
|
||||||
if clean_port < 1 or clean_port > 65535:
|
if clean_port < 1 or clean_port > 65535:
|
||||||
return False, None, "Port number must be between 1 and 65535"
|
return False, None, "Port number must be between 1 and 65535"
|
||||||
|
|
||||||
# Add server-side check for conflicts if server_id is provided
|
# Always check for port conflicts
|
||||||
if server_id:
|
in_use, app_name = is_port_in_use(clean_port, protocol, server_id, app_id)
|
||||||
in_use, app_name = is_port_in_use(clean_port, protocol, server_id, app_id)
|
if in_use:
|
||||||
if in_use:
|
return False, clean_port, f"Port {clean_port}/{protocol} is already in use by application '{app_name}'"
|
||||||
return False, clean_port, f"Port {clean_port}/{protocol} is already in use by application '{app_name}'"
|
|
||||||
|
|
||||||
return True, clean_port, None
|
return True, clean_port, None
|
||||||
|
|
||||||
|
|
|
@ -7,10 +7,10 @@ services:
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
# image: homedocs:latest
|
# image: homedocs:latest
|
||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- "5001:8000"
|
||||||
volumes:
|
volumes:
|
||||||
- ./instance:/app/instance # Persist SQLite database
|
- ./instance:/app/instance # Persist SQLite database
|
||||||
environment:
|
# environment:
|
||||||
- FLASK_ENV=${FLASK_ENV:-production}
|
# - FLASK_ENV=${FLASK_ENV:-dev}
|
||||||
- SECRET_KEY=${SECRET_KEY:-} # Will be generated if empty
|
# - SECRET_KEY=99c8b81f5ed79702c5c2bf1f60a7cdbe4ee58d168cf36b13343c3529ff215637 # Will be generated if empty
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
|
@ -17,7 +17,7 @@ class Config:
|
||||||
'X-Content-Type-Options': 'nosniff',
|
'X-Content-Type-Options': 'nosniff',
|
||||||
'X-Frame-Options': 'SAMEORIGIN',
|
'X-Frame-Options': 'SAMEORIGIN',
|
||||||
'X-XSS-Protection': '1; mode=block',
|
'X-XSS-Protection': '1; mode=block',
|
||||||
'Content-Security-Policy': "default-src 'self'; script-src 'self' https://cdn.jsdelivr.net https://unpkg.com https://cdnjs.cloudflare.com 'unsafe-inline'; style-src 'self' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://fonts.googleapis.com 'unsafe-inline'; font-src 'self' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://fonts.gstatic.com data:; img-src 'self' data:;"
|
# 'Content-Security-Policy': "default-src 'self'; script-src 'self' https://cdn.jsdelivr.net https://unpkg.com https://cdnjs.cloudflare.com 'unsafe-inline'; style-src 'self' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://fonts.googleapis.com 'unsafe-inline'; font-src 'self' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://fonts.gstatic.com data:; img-src 'self' data:;"
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -67,4 +67,4 @@ config = {
|
||||||
'production': ProductionConfig,
|
'production': ProductionConfig,
|
||||||
|
|
||||||
'default': DevelopmentConfig
|
'default': DevelopmentConfig
|
||||||
}
|
}
|
||||||
|
|
5
run.py
5
run.py
|
@ -188,8 +188,9 @@ if __name__ == "__main__":
|
||||||
print("Database not found, initializing...")
|
print("Database not found, initializing...")
|
||||||
try:
|
try:
|
||||||
init_db()
|
init_db()
|
||||||
create_admin_user()
|
|
||||||
# Uncomment to add sample data
|
# Uncomment to add sample data
|
||||||
|
# create_admin_user()
|
||||||
# seed_data()
|
# seed_data()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error initializing database: {e}")
|
print(f"Error initializing database: {e}")
|
||||||
|
@ -197,7 +198,7 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
# Run the application
|
# Run the application
|
||||||
try:
|
try:
|
||||||
app.run(debug=True, port=5000)
|
app.run(debug=True, port=5001)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error starting Flask app: {e}")
|
print(f"Error starting Flask app: {e}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue