From 0a31714a93a2b17f4b3b562a02655be3116cefc4 Mon Sep 17 00:00:00 2001 From: pika Date: Thu, 3 Apr 2025 13:51:52 +0200 Subject: [PATCH] wip --- Dockerfile | 13 ++-- app/routes/dashboard.py | 103 +++++++++----------------- app/routes/static.py | 60 +++++++-------- app/templates/dashboard/app_form.html | 49 +++++++++--- app/templates/dashboard/app_view.html | 68 +++++++++-------- app/templates/layout.html | 4 +- app/utils/app_utils.py | 4 + compose.yml | 2 +- run.py | 4 + wsgi.py | 1 + 10 files changed, 159 insertions(+), 149 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2365998..646bad4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,20 +20,19 @@ RUN pip install --upgrade pip && \ COPY . . # Create the instance directory for SQLite -RUN mkdir -p instance && \ - chmod 777 instance +# RUN mkdir -p instance && \ +# chmod 777 instance # Create a non-root user to run the app -RUN useradd -m appuser && \ - chown -R appuser:appuser /app +# RUN useradd -m appuser && \ +# chown -R appuser:appuser /app -USER appuser +# USER appuser # Set environment variables ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ - SECRET_KEY="" \ FLASK_APP=wsgi.py # Run gunicorn -CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--timeout", "120", "--workers", "4", "wsgi:app"] +CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--timeout", "120", "--workers", "4", "run:app"] diff --git a/app/routes/dashboard.py b/app/routes/dashboard.py index ea3e7c4..0318fcb 100644 --- a/app/routes/dashboard.py +++ b/app/routes/dashboard.py @@ -298,7 +298,6 @@ def app_view(app_id): @login_required def app_edit(app_id): """Edit an existing application with comprehensive error handling""" - # Get the application and all servers app = App.query.get_or_404(app_id) servers = Server.query.all() @@ -323,87 +322,57 @@ def app_edit(app_id): # Check for port conflicts proactively conflicts = [] + seen_ports = set() # To track ports already seen in this submission + for i, (port_number, protocol, _) in enumerate(port_data): try: clean_port = int(port_number) + # Check if this port has already been seen in this submission + port_key = f"{clean_port}/{protocol}" + if port_key in seen_ports: + conflicts.append((clean_port, protocol, "Duplicate port in submission")) + continue + seen_ports.add(port_key) + + # Check if the port is in use by another application 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)) + conflicts.append((clean_port, protocol, f"Port {clean_port}/{protocol} is already in use by application '{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 {conflict_app_name}' - ) - 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 - ) + for conflict in conflicts: + flash(f"Conflict: {conflict[0]}/{conflict[1]} - {conflict[2]}", "danger") + return render_template("dashboard/app_edit.html", app=app, servers=servers) - # Replace local validation with shared function - valid, error = validate_app_data(name, server_id, existing_app_id=app_id) + # Update application details + app.name = name + app.server_id = server_id + app.documentation = documentation + app.url = url - if valid: - # Update application with URL - app.name = name - app.server_id = server_id - app.documentation = documentation - app.url = url + # Only delete existing ports if new port data is provided + if port_data: + # Remove existing ports and add new ones + Port.query.filter_by(app_id=app_id).delete() + for port_number, protocol, description in port_data: + new_port = Port( + app_id=app_id, + port_number=int(port_number), + protocol=protocol, + description=description + ) + db.session.add(new_port) - # Update application - from app.utils.app_utils import save_app + db.session.commit() + flash("Application updated successfully", "success") + return redirect(url_for("dashboard.app_view", app_id=app_id)) - success, updated_app, error = save_app( - name, server_id, documentation, port_data, app_id, url - ) - - if success: - flash("Application updated successfully", "success") - 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'Edit {app_name}', - "info" - ) - else: - flash(error, "danger") - - # GET request - display the form - return render_template( - "dashboard/app_form.html", - title=f"Edit {app.name}", - edit_mode=True, - servers=servers, - app=app - ) + return render_template("dashboard/app_edit.html", app=app, servers=servers) @bp.route("/app//delete", methods=["POST"]) diff --git a/app/routes/static.py b/app/routes/static.py index 02a2a14..21777f7 100644 --- a/app/routes/static.py +++ b/app/routes/static.py @@ -4,34 +4,34 @@ import os bp = Blueprint("static_assets", __name__) -@bp.route("/static/libs/tabler-icons/tabler-icons.min.css") -def tabler_icons(): - """Serve tabler-icons CSS from node_modules or download if missing""" - icons_path = os.path.join(current_app.static_folder, "libs", "tabler-icons") - - # Create directory if it doesn't exist - if not os.path.exists(icons_path): - os.makedirs(icons_path) - - css_file = os.path.join(icons_path, "tabler-icons.min.css") - - # If file doesn't exist, download from CDN - if not os.path.exists(css_file): - import requests - - try: - cdn_url = "https://cdn.jsdelivr.net/npm/@tabler/icons@latest/iconfont/tabler-icons.min.css" - response = requests.get(cdn_url) - if response.status_code == 200: - with open(css_file, "wb") as f: - f.write(response.content) - print(f"Downloaded tabler-icons.min.css from CDN") - else: - print(f"Failed to download tabler-icons CSS: {response.status_code}") - except Exception as e: - print(f"Error downloading tabler-icons CSS: {e}") - - return send_from_directory(icons_path, "tabler-icons.min.css") +# @bp.route("/static/libs/tabler-icons/tabler-icons.min.css") +# def tabler_icons(): +# """Serve tabler-icons CSS from node_modules or download if missing""" +# icons_path = os.path.join(current_app.static_folder, "libs", "tabler-icons") +# +# # Create directory if it doesn't exist +# if not os.path.exists(icons_path): +# os.makedirs(icons_path) +# +# css_file = os.path.join(icons_path, "tabler-icons.min.css") +# +# # If file doesn't exist, download from CDN +# if not os.path.exists(css_file): +# import requests +# +# try: +# cdn_url = "https://cdn.jsdelivr.net/npm/@tabler/core@1.1.1/dist/css/tabler.min.css" +# response = requests.get(cdn_url) +# if response.status_code == 200: +# with open(css_file, "wb") as f: +# f.write(response.content) +# print(f"Downloaded tabler-icons.min.css from CDN") +# else: +# print(f"Failed to download tabler-icons CSS: {response.status_code}") +# except Exception as e: +# print(f"Error downloading tabler-icons CSS: {e}") +# +# return send_from_directory(icons_path, "tabler-icons.min.css") @bp.route("/static/css/tabler.min.css") @@ -50,7 +50,7 @@ def tabler_css(): import requests try: - cdn_url = "https://cdn.jsdelivr.net/npm/@tabler/core@latest/dist/css/tabler.min.css" + cdn_url = "https://cdn.jsdelivr.net/npm/@tabler/core@1.1.1/dist/css/tabler.min.css" response = requests.get(cdn_url) if response.status_code == 200: with open(css_file, "wb") as f: @@ -82,7 +82,7 @@ def favicon(): try: # Using a simple placeholder favicon - cdn_url = "https://www.google.com/favicon.ico" + cdn_url = "https://www.svgrepo.com/show/529863/server-minimalistic.svg" response = requests.get(cdn_url) if response.status_code == 200: with open(favicon_file, "wb") as f: diff --git a/app/templates/dashboard/app_form.html b/app/templates/dashboard/app_form.html index 343e6d0..d769517 100644 --- a/app/templates/dashboard/app_form.html +++ b/app/templates/dashboard/app_form.html @@ -119,8 +119,6 @@ @@ -144,8 +142,6 @@ @@ -251,9 +247,48 @@ {% endblock %} \ No newline at end of file diff --git a/app/templates/layout.html b/app/templates/layout.html index 195641a..b0cc5cc 100644 --- a/app/templates/layout.html +++ b/app/templates/layout.html @@ -17,8 +17,8 @@ - + + diff --git a/app/utils/app_utils.py b/app/utils/app_utils.py index 940c345..f911ad9 100644 --- a/app/utils/app_utils.py +++ b/app/utils/app_utils.py @@ -105,6 +105,10 @@ def process_app_ports(app_id, port_data, server_id=None): if app: server_id = app.server_id + # If no port data is provided, that's valid (app with no ports) + if not port_data: + return True, None + # Track the port+protocol combinations we've seen to avoid duplicates seen_ports = set() diff --git a/compose.yml b/compose.yml index 9c125bc..867a191 100644 --- a/compose.yml +++ b/compose.yml @@ -7,7 +7,7 @@ services: dockerfile: Dockerfile # image: homedocs:latest ports: - - "5001:8000" + - "5000:8000" volumes: - ./instance:/app/instance # Persist SQLite database # environment: diff --git a/run.py b/run.py index 9f6577d..40081e4 100644 --- a/run.py +++ b/run.py @@ -10,6 +10,7 @@ from datetime import datetime import random import string import json +from flask_wtf.csrf import CSRFProtect # Add the current directory to Python path current_dir = os.path.abspath(os.path.dirname(__file__)) @@ -79,6 +80,9 @@ def register_routes(app): print("Starting Flask app with SQLite database...") app = create_app("development") +# Set up CSRF protection +csrf = CSRFProtect(app) + @app.shell_context_processor def make_shell_context(): diff --git a/wsgi.py b/wsgi.py index 7c48ac6..8dff4aa 100644 --- a/wsgi.py +++ b/wsgi.py @@ -1,6 +1,7 @@ import os import secrets from app import create_app +from flask_wtf.csrf import CSRFProtect # Generate a secret key if not provided if not os.environ.get("SECRET_KEY"):