This commit is contained in:
pika 2025-03-31 13:47:34 +02:00
parent 5a309a0f6d
commit 1ab129b798
5 changed files with 499 additions and 107 deletions

View file

@ -4,7 +4,7 @@ import markdown
from app.core.models import Server, App, Subnet, Port
from app.core.extensions import db, limiter
from datetime import datetime
from app.utils.app_utils import validate_app_data
from app.utils.app_utils import validate_app_data, is_port_in_use
bp = Blueprint("dashboard", __name__, url_prefix="/dashboard")
@ -223,61 +223,54 @@ def server_delete(server_id):
def app_new(server_id=None):
"""Create a new application"""
servers = Server.query.all()
if request.method == "POST":
# Handle form submission
name = request.form.get("name")
name = request.form.get("name", "").strip()
server_id = request.form.get("server_id")
documentation = request.form.get("documentation", "")
url = request.form.get("url", "") # Get the URL
if not name or not server_id:
flash("Name and server are required", "danger")
return render_template(
"dashboard/app_form.html",
title="New Application",
edit_mode=False,
servers=servers,
selected_server_id=server_id,
)
# Create the app with the URL
app = App(name=name, server_id=server_id, documentation=documentation, url=url)
try:
db.session.add(app)
db.session.commit()
# Process port numbers if any
port_numbers = request.form.getlist("port_numbers[]")
protocols = request.form.getlist("protocols[]")
descriptions = request.form.getlist("port_descriptions[]")
for i, port_number in enumerate(port_numbers):
if port_number and port_number.isdigit():
port = Port(
app_id=app.id,
port_number=int(port_number),
protocol=protocols[i] if i < len(protocols) else "TCP",
description=descriptions[i] if i < len(descriptions) else "",
)
db.session.add(port)
db.session.commit()
url = request.form.get("url", "")
# Process port data from form
port_data = []
port_numbers = request.form.getlist("port_numbers[]")
protocols = request.form.getlist("protocols[]")
descriptions = request.form.getlist("port_descriptions[]")
for i in range(len(port_numbers)):
if port_numbers[i] and port_numbers[i].strip():
protocol = protocols[i] if i < len(protocols) else "TCP"
description = descriptions[i] if i < len(descriptions) else ""
port_data.append((port_numbers[i], protocol, description))
# Server-side validation
from app.utils.app_utils import save_app
success, app, error = save_app(name, server_id, documentation, port_data, url=url)
if success:
flash(f"Application '{name}' created successfully", "success")
return redirect(url_for("dashboard.app_view", app_id=app.id))
except Exception as e:
db.session.rollback()
flash(f"Error creating application: {str(e)}", "danger")
# GET request - render the form
else:
flash(error, "danger")
# Check if it's a port conflict and provide direct link
if "already in use by application" in error:
try:
app_name = error.split("'")[1] # Extract app name from error
conflict_app = App.query.filter_by(name=app_name, server_id=server_id).first()
if conflict_app:
edit_url = url_for('dashboard.app_edit', app_id=conflict_app.id)
flash(f'<a href="{edit_url}" class="alert-link">Edit conflicting application</a>', "info")
except:
pass
# GET request or validation failed - render the form
return render_template(
"dashboard/app_form.html",
title="New Application",
edit_mode=False,
servers=servers,
selected_server_id=server_id, # This will pre-select the server
selected_server_id=server_id,
)
@ -324,6 +317,44 @@ def app_edit(app_id):
description = descriptions[i] if i < len(descriptions) else ""
port_data.append((port_numbers[i], protocol, description))
# Check for port conflicts proactively
conflicts = []
for i, (port_number, protocol, _) in enumerate(port_data):
try:
clean_port = int(port_number)
in_use, conflicting_app_name = is_port_in_use(
clean_port, protocol, server_id, exclude_app_id=app_id
)
if in_use:
conflicts.append((clean_port, protocol, conflicting_app_name))
except (ValueError, TypeError):
continue
if conflicts:
# Find the IDs of conflicting apps for linking
conflict_msgs = []
for port, protocol, conflict_app_name in conflicts:
conflict_app = App.query.filter_by(name=conflict_app_name, server_id=server_id).first()
if conflict_app:
edit_url = url_for('dashboard.app_edit', app_id=conflict_app.id)
conflict_msgs.append(
f'Port {port}/{protocol} is in use by <a href="{edit_url}">{conflict_app_name}</a>'
)
else:
conflict_msgs.append(f'Port {port}/{protocol} is in use by {conflict_app_name}')
for msg in conflict_msgs:
flash(msg, "danger")
return render_template(
"dashboard/app_form.html",
title=f"Edit {app.name}",
edit_mode=True,
servers=servers,
app=app
)
# Replace local validation with shared function
valid, error = validate_app_data(name, server_id, existing_app_id=app_id)
@ -338,7 +369,7 @@ def app_edit(app_id):
from app.utils.app_utils import save_app
success, updated_app, error = save_app(
name, server_id, documentation, port_data, app_id
name, server_id, documentation, port_data, app_id, url
)
if success:
@ -346,17 +377,28 @@ def app_edit(app_id):
return redirect(url_for("dashboard.app_view", app_id=app_id))
else:
flash(error, "danger")
# Extract app name from error and provide link if it's a conflict
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=server_id).first()
if conflict_app:
edit_url = url_for('dashboard.app_edit', app_id=conflict_app.id)
flash(
f'Would you like to edit the conflicting application? '
f'<a href="{edit_url}">Edit {app_name}</a>',
"info"
)
else:
flash(error, "danger")
# For GET requests or failed POSTs
# GET request - display the form
return render_template(
"dashboard/app_form.html",
title=f"Edit Application: {app.name}",
title=f"Edit {app.name}",
edit_mode=True,
app=app,
dashboard_link=url_for("dashboard.dashboard_home"),
servers=servers,
app=app
)