This commit is contained in:
pika 2025-03-31 00:35:42 +02:00
parent 30e9c9328e
commit 254593d260
20 changed files with 156 additions and 65 deletions

Binary file not shown.

View file

@ -1,4 +1,13 @@
from flask import Blueprint, jsonify, request, abort, current_app, render_template, redirect, url_for
from flask import (
Blueprint,
jsonify,
request,
abort,
current_app,
render_template,
redirect,
url_for,
)
from flask_login import login_required
from app.core.models import Subnet, Server, App, Port
from app.core.extensions import db
@ -277,59 +286,77 @@ def suggest_port(server_id):
def add_app_port(app_id):
"""Add a port to an application"""
app = App.query.get_or_404(app_id)
try:
port_number = request.form.get("port_number")
protocol = request.form.get("protocol", "TCP")
description = request.form.get("description", "")
# Validate port data
valid, clean_port, error = validate_port_data(port_number, protocol, description)
valid, clean_port, error = validate_port_data(
port_number, protocol, description
)
if not valid:
flash(error, "danger")
return redirect(url_for("dashboard.app_view", app_id=app_id)) if not request.is_xhr else jsonify({"success": False, "error": error}), 400
return (
redirect(url_for("dashboard.app_view", app_id=app_id))
if not request.is_xhr
else jsonify({"success": False, "error": error})
), 400
# Check if port already exists
existing_port = Port.query.filter_by(app_id=app_id, port_number=clean_port).first()
existing_port = Port.query.filter_by(
app_id=app_id, port_number=clean_port
).first()
if existing_port:
error_msg = f"Port {clean_port} already exists for this application"
flash(error_msg, "warning")
return redirect(url_for("dashboard.app_view", app_id=app_id)) if not request.is_xhr else jsonify({"success": False, "error": error_msg}), 400
return (
redirect(url_for("dashboard.app_view", app_id=app_id))
if not request.is_xhr
else jsonify({"success": False, "error": error_msg})
), 400
# Create new port
new_port = Port(
app_id=app_id,
port_number=clean_port,
protocol=protocol,
description=description
description=description,
)
db.session.add(new_port)
db.session.commit()
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 request.is_xhr and not request.is_json:
return redirect(url_for("dashboard.app_view", app_id=app_id))
# Otherwise return JSON for API/AJAX calls
return jsonify({
"success": True,
"message": success_msg,
"port": {
"id": new_port.id,
"number": new_port.port_number,
"protocol": new_port.protocol,
"description": new_port.description
return jsonify(
{
"success": True,
"message": success_msg,
"port": {
"id": new_port.id,
"number": new_port.port_number,
"protocol": new_port.protocol,
"description": new_port.description,
},
}
})
)
except Exception as e:
db.session.rollback()
flash(f"Error: {str(e)}", "danger")
return redirect(url_for("dashboard.app_view", app_id=app_id)) if not request.is_xhr else jsonify({"success": False, "error": str(e)}), 500
return (
redirect(url_for("dashboard.app_view", app_id=app_id))
if not request.is_xhr
else jsonify({"success": False, "error": str(e)})
), 500
@bp.route("/app/<int:app_id>/ports", methods=["GET"])
@ -362,19 +389,21 @@ def delete_app_port(app_id, port_id):
"""Delete a port from an application"""
app = App.query.get_or_404(app_id)
port = Port.query.get_or_404(port_id)
if port.app_id != app.id:
flash("Port does not belong to this application", "danger")
return redirect(url_for("dashboard.app_view", app_id=app_id))
try:
db.session.delete(port)
db.session.commit()
flash(f"Port {port.port_number}/{port.protocol} deleted successfully", "success")
flash(
f"Port {port.port_number}/{port.protocol} deleted successfully", "success"
)
except Exception as e:
db.session.rollback()
flash(f"Error deleting port: {str(e)}", "danger")
return redirect(url_for("dashboard.app_view", app_id=app_id))

View file

@ -218,52 +218,65 @@ def server_delete(server_id):
@bp.route("/app/new", methods=["GET", "POST"])
@bp.route("/app/new/<int:server_id>", methods=["GET", "POST"])
@login_required
def app_new():
"""Create a new application with comprehensive error handling"""
# Get all servers for dropdown
def app_new(server_id=None):
"""Create a new application"""
servers = Server.query.all()
if not servers:
flash("You need to create a server before adding applications", "warning")
return redirect(url_for("dashboard.server_new"))
if request.method == "POST":
# Get form data
name = request.form.get("name", "").strip()
# Handle form submission
name = request.form.get("name")
server_id = request.form.get("server_id")
documentation = request.form.get("documentation", "")
# 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[]")
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,
)
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))
# Create the app
app = App(name=name, server_id=server_id, documentation=documentation)
# Save application
from app.utils.app_utils import save_app
try:
db.session.add(app)
db.session.commit()
success, app, error = save_app(name, server_id, documentation, port_data)
# Process port numbers if any
port_numbers = request.form.getlist("port_numbers[]")
protocols = request.form.getlist("protocols[]")
descriptions = request.form.getlist("port_descriptions[]")
if success:
flash("Application created successfully", "success")
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()
flash(f"Application '{name}' created successfully", "success")
return redirect(url_for("dashboard.app_view", app_id=app.id))
else:
flash(error, "danger")
# For GET requests or failed POSTs
except Exception as e:
db.session.rollback()
flash(f"Error creating application: {str(e)}", "danger")
# GET request - render the form
return render_template(
"dashboard/app_form.html",
title="Create New Application",
title="New Application",
edit_mode=False,
dashboard_link=url_for("dashboard.dashboard_home"),
servers=servers,
selected_server_id=server_id, # This will pre-select the server
)

View file

@ -58,7 +58,8 @@
<select class="form-select" name="server_id" required>
<option value="">Select a server</option>
{% for server in servers %}
<option value="{{ server.id }}" {% if app and server.id==app.server_id %}selected{% endif %}>
<option value="{{ server.id }}" {% if app and server.id==app.server_id %}selected {% elif selected_server_id
and server.id|string==selected_server_id|string %}selected {% endif %}>
{{ server.hostname }} ({{ server.ip_address }})
</option>
{% endfor %}

View file

@ -1,5 +1,7 @@
{% extends "layout.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="container-xl">
<div class="page-header d-print-none">
@ -12,13 +14,16 @@
{{ app.name }}
</h2>
</div>
<div class="col-auto ms-auto">
<div class="col-auto ms-auto d-print-none">
<div class="btn-list">
<a href="{{ url_for('dashboard.app_edit', app_id=app.id) }}" class="btn btn-primary">
<span class="ti ti-edit me-2"></span>Edit Application
<a href="{{ url_for('dashboard.app_edit', app_id=app.id) }}" class="btn btn-primary d-none d-sm-inline-block">
<span class="ti ti-edit"></span>
Edit Application
</a>
<button class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#deleteAppModal">
<span class="ti ti-trash me-2"></span>Delete
<button class="btn btn-danger d-none d-sm-inline-block" data-bs-toggle="modal"
data-bs-target="#deleteAppModal">
<span class="ti ti-trash"></span>
Delete
</button>
</div>
</div>
@ -180,7 +185,7 @@
</div>
</div>
<!-- Confirmation Modal for Port Deletion -->
<!-- Delete Port Modal -->
<div class="modal modal-blur fade" id="deletePortModal" tabindex="-1">
<div class="modal-dialog modal-sm modal-dialog-centered">
<div class="modal-content">
@ -342,7 +347,9 @@
border-left-color: #d73a49;
}
</style>
{% endblock %}
{% block extra_js %}
<script>
// Store app ID for JavaScript use
const appId = {{ app.id }};
@ -365,6 +372,9 @@
.then(response => response.json())
.then(data => {
document.querySelector('#addPortForm input[name="port_number"]').value = data.port;
})
.catch(error => {
console.error('Error fetching random port:', error);
});
});
}

38
app/utils/validators.py Normal file
View file

@ -0,0 +1,38 @@
def validate_app_data(name, server_id, existing_app_id=None):
"""
Validate application data, checking for required fields and uniqueness
Args:
name: The application name
server_id: The server ID
existing_app_id: The ID of the application being edited (if any)
Returns:
tuple: (valid, error_message)
"""
# Check for required fields
if not name or not name.strip():
return False, "Application name is required"
if not server_id:
return False, "Server is required"
# Check name length
if len(name) < 2:
return False, "Application name must be at least 2 characters"
# Check for uniqueness of name on the server
from app.models import App
query = App.query.filter(App.name == name, App.server_id == server_id)
# If editing an existing app, exclude it from the uniqueness check
if existing_app_id:
query = query.filter(App.id != existing_app_id)
existing_app = query.first()
if existing_app:
return False, f"Application '{name}' already exists on this server"
return True, None

Binary file not shown.