from app.core.models import App, Port, Server from app.core.extensions import db from flask import flash import re def validate_app_data(name, server_id, existing_app_id=None): """ Validate application data Returns tuple (valid, error_message) """ if not name or not server_id: return False, "Please fill in all required fields" # Check if app exists with same name on same server query = App.query.filter(App.name == name, App.server_id == server_id) # If editing, exclude the current app if existing_app_id: query = query.filter(App.id != existing_app_id) if query.first(): return False, f"Application '{name}' already exists on this server" # Find similar app names on this server for suggestion similar_apps = App.query.filter( App.name.ilike(f"%{name}%"), App.server_id == server_id ).limit(3).all() if similar_apps and (not existing_app_id or not any(app.id == existing_app_id for app in similar_apps)): similar_names = ", ".join([f"'{app.name}'" for app in similar_apps]) return False, f"Similar application names found on this server: {similar_names}" return True, 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 """ from sqlalchemy import and_ # Join App and Port models to find ports used by apps on this server query = db.session.query(Port, App).join(App).filter( Port.port_number == port_number, Port.protocol == protocol, App.server_id == server_id ) # # Exclude the current app if editing # if exclude_app_id: # query = query.filter(App.id != exclude_app_id) result = query.first() if result: return True, result.App.name return False, None def validate_port_data(port_number, protocol, description, server_id=None, app_id=None): """ Validate port data for an application 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) """ # Check if port number is provided if not port_number: return False, None, "Port number is required" try: clean_port = int(port_number) except ValueError: return False, None, "Port number must be a valid integer" if clean_port < 1 or clean_port > 65535: return False, None, "Port number must be between 1 and 65535" # Always check for port conflicts in_use, app_name = is_port_in_use(clean_port, protocol, server_id, app_id) if in_use: return False, clean_port, f"Port {clean_port}/{protocol} is already in use by application '{app_name}'" return True, clean_port, None def process_app_ports(app_id, port_data, server_id=None): """ Process port data for an application port_data should be a list of tuples (port_number, protocol, description) Returns (success, error_message) """ # Get the app's server_id if not provided if not server_id and app_id: app = App.query.get(app_id) 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() try: for port_number, protocol, description in port_data: # Skip empty port entries if not port_number or not port_number.strip(): continue port_key = f"{port_number}/{protocol}" # Check for duplicates within this form submission if port_key in seen_ports: return False, f"Duplicate port: {port_key} is specified multiple times" seen_ports.add(port_key) # Validate the port data valid, clean_port, error = validate_port_data( port_number, protocol, description, server_id, app_id ) if not valid: return False, error # 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: # Update existing port existing_port.description = description else: # Create new port new_port = Port( app_id=app_id, port_number=clean_port, protocol=protocol, description=description, ) db.session.add(new_port) return True, None except Exception as e: db.session.rollback() return False, str(e) def save_app(name, server_id, documentation, port_data, app_id=None, url=None): """ Save or update an application Returns (success, app, error_message) """ try: # Validate application data valid, error = validate_app_data(name, server_id, app_id) if not valid: return False, None, error if app_id: # Update existing app app = App.query.get(app_id) if not app: return False, None, "Application not found" app.name = name app.server_id = server_id app.documentation = documentation app.url = url else: # Create new app app = App( name=name, server_id=server_id, documentation=documentation, url=url ) db.session.add(app) db.session.flush() # Get the app ID without committing # Remove all existing ports if updating if app_id: Port.query.filter_by(app_id=app_id).delete() # Process and save ports port_success, port_error = process_app_ports(app.id, port_data, server_id) if not port_success: db.session.rollback() return False, None, port_error db.session.commit() return True, app, None except Exception as e: db.session.rollback() return False, None, str(e)