wip
This commit is contained in:
parent
30e9c9328e
commit
254593d260
20 changed files with 156 additions and 65 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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 flask_login import login_required
|
||||||
from app.core.models import Subnet, Server, App, Port
|
from app.core.models import Subnet, Server, App, Port
|
||||||
from app.core.extensions import db
|
from app.core.extensions import db
|
||||||
|
@ -284,25 +293,37 @@ def add_app_port(app_id):
|
||||||
description = request.form.get("description", "")
|
description = request.form.get("description", "")
|
||||||
|
|
||||||
# Validate port data
|
# 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:
|
if not valid:
|
||||||
flash(error, "danger")
|
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
|
# 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:
|
if existing_port:
|
||||||
error_msg = f"Port {clean_port} already exists for this application"
|
error_msg = f"Port {clean_port} already exists for this application"
|
||||||
flash(error_msg, "warning")
|
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
|
# 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()
|
||||||
|
@ -315,21 +336,27 @@ def add_app_port(app_id):
|
||||||
return redirect(url_for("dashboard.app_view", app_id=app_id))
|
return redirect(url_for("dashboard.app_view", app_id=app_id))
|
||||||
|
|
||||||
# Otherwise return JSON for API/AJAX calls
|
# Otherwise return JSON for API/AJAX calls
|
||||||
return jsonify({
|
return jsonify(
|
||||||
|
{
|
||||||
"success": True,
|
"success": True,
|
||||||
"message": success_msg,
|
"message": success_msg,
|
||||||
"port": {
|
"port": {
|
||||||
"id": new_port.id,
|
"id": new_port.id,
|
||||||
"number": new_port.port_number,
|
"number": new_port.port_number,
|
||||||
"protocol": new_port.protocol,
|
"protocol": new_port.protocol,
|
||||||
"description": new_port.description
|
"description": new_port.description,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
flash(f"Error: {str(e)}", "danger")
|
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"])
|
@bp.route("/app/<int:app_id>/ports", methods=["GET"])
|
||||||
|
@ -370,7 +397,9 @@ def delete_app_port(app_id, port_id):
|
||||||
try:
|
try:
|
||||||
db.session.delete(port)
|
db.session.delete(port)
|
||||||
db.session.commit()
|
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:
|
except Exception as e:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
flash(f"Error deleting port: {str(e)}", "danger")
|
flash(f"Error deleting port: {str(e)}", "danger")
|
||||||
|
|
|
@ -218,52 +218,65 @@ def server_delete(server_id):
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/app/new", methods=["GET", "POST"])
|
@bp.route("/app/new", methods=["GET", "POST"])
|
||||||
|
@bp.route("/app/new/<int:server_id>", methods=["GET", "POST"])
|
||||||
@login_required
|
@login_required
|
||||||
def app_new():
|
def app_new(server_id=None):
|
||||||
"""Create a new application with comprehensive error handling"""
|
"""Create a new application"""
|
||||||
# Get all servers for dropdown
|
|
||||||
servers = Server.query.all()
|
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":
|
if request.method == "POST":
|
||||||
# Get form data
|
# Handle form submission
|
||||||
name = request.form.get("name", "").strip()
|
name = request.form.get("name")
|
||||||
server_id = request.form.get("server_id")
|
server_id = request.form.get("server_id")
|
||||||
documentation = request.form.get("documentation", "")
|
documentation = request.form.get("documentation", "")
|
||||||
|
|
||||||
# Process port data from form
|
if not name or not server_id:
|
||||||
port_data = []
|
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
|
||||||
|
app = App(name=name, server_id=server_id, documentation=documentation)
|
||||||
|
|
||||||
|
try:
|
||||||
|
db.session.add(app)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# Process port numbers if any
|
||||||
port_numbers = request.form.getlist("port_numbers[]")
|
port_numbers = request.form.getlist("port_numbers[]")
|
||||||
protocols = request.form.getlist("protocols[]")
|
protocols = request.form.getlist("protocols[]")
|
||||||
descriptions = request.form.getlist("port_descriptions[]")
|
descriptions = request.form.getlist("port_descriptions[]")
|
||||||
|
|
||||||
for i in range(len(port_numbers)):
|
for i, port_number in enumerate(port_numbers):
|
||||||
if port_numbers[i] and port_numbers[i].strip():
|
if port_number and port_number.isdigit():
|
||||||
protocol = protocols[i] if i < len(protocols) else "TCP"
|
port = Port(
|
||||||
description = descriptions[i] if i < len(descriptions) else ""
|
app_id=app.id,
|
||||||
port_data.append((port_numbers[i], protocol, description))
|
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)
|
||||||
|
|
||||||
# Save application
|
db.session.commit()
|
||||||
from app.utils.app_utils import save_app
|
flash(f"Application '{name}' created successfully", "success")
|
||||||
|
|
||||||
success, app, error = save_app(name, server_id, documentation, port_data)
|
|
||||||
|
|
||||||
if success:
|
|
||||||
flash("Application created successfully", "success")
|
|
||||||
return redirect(url_for("dashboard.app_view", app_id=app.id))
|
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(
|
return render_template(
|
||||||
"dashboard/app_form.html",
|
"dashboard/app_form.html",
|
||||||
title="Create New Application",
|
title="New Application",
|
||||||
edit_mode=False,
|
edit_mode=False,
|
||||||
dashboard_link=url_for("dashboard.dashboard_home"),
|
|
||||||
servers=servers,
|
servers=servers,
|
||||||
|
selected_server_id=server_id, # This will pre-select the server
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
Binary file not shown.
|
@ -58,7 +58,8 @@
|
||||||
<select class="form-select" name="server_id" required>
|
<select class="form-select" name="server_id" required>
|
||||||
<option value="">Select a server</option>
|
<option value="">Select a server</option>
|
||||||
{% for server in servers %}
|
{% 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 }})
|
{{ server.hostname }} ({{ server.ip_address }})
|
||||||
</option>
|
</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
|
|
||||||
|
{% block title %}{{ title }}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-xl">
|
<div class="container-xl">
|
||||||
<div class="page-header d-print-none">
|
<div class="page-header d-print-none">
|
||||||
|
@ -12,13 +14,16 @@
|
||||||
{{ app.name }}
|
{{ app.name }}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto ms-auto">
|
<div class="col-auto ms-auto d-print-none">
|
||||||
<div class="btn-list">
|
<div class="btn-list">
|
||||||
<a href="{{ url_for('dashboard.app_edit', app_id=app.id) }}" class="btn btn-primary">
|
<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 me-2"></span>Edit Application
|
<span class="ti ti-edit"></span>
|
||||||
|
Edit Application
|
||||||
</a>
|
</a>
|
||||||
<button class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#deleteAppModal">
|
<button class="btn btn-danger d-none d-sm-inline-block" data-bs-toggle="modal"
|
||||||
<span class="ti ti-trash me-2"></span>Delete
|
data-bs-target="#deleteAppModal">
|
||||||
|
<span class="ti ti-trash"></span>
|
||||||
|
Delete
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -180,7 +185,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Confirmation Modal for Port Deletion -->
|
<!-- Delete Port Modal -->
|
||||||
<div class="modal modal-blur fade" id="deletePortModal" tabindex="-1">
|
<div class="modal modal-blur fade" id="deletePortModal" tabindex="-1">
|
||||||
<div class="modal-dialog modal-sm modal-dialog-centered">
|
<div class="modal-dialog modal-sm modal-dialog-centered">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
|
@ -342,7 +347,9 @@
|
||||||
border-left-color: #d73a49;
|
border-left-color: #d73a49;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_js %}
|
||||||
<script>
|
<script>
|
||||||
// Store app ID for JavaScript use
|
// Store app ID for JavaScript use
|
||||||
const appId = {{ app.id }};
|
const appId = {{ app.id }};
|
||||||
|
@ -365,6 +372,9 @@
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
document.querySelector('#addPortForm input[name="port_number"]').value = data.port;
|
document.querySelector('#addPortForm input[name="port_number"]').value = data.port;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching random port:', error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
38
app/utils/validators.py
Normal file
38
app/utils/validators.py
Normal 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
|
BIN
instance/app.db
BIN
instance/app.db
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue