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

@ -22,51 +22,122 @@ def validate_app_data(name, server_id, existing_app_id=None):
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 validate_port_data(port_number, protocol, description=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
Args:
port_number: The port number to check
protocol: The protocol (TCP, UDP, etc.)
server_id: The server ID
exclude_app_id: Optional app ID to exclude from the check (for edit operations)
Returns:
Tuple (bool, app_name): Is port in use and by which app
"""
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
Returns tuple (valid, clean_port_number, error_message)
Returns tuple (valid, clean_port, error_message)
"""
# Clean and validate port number
# Existing validation
if not port_number:
return False, None, "Port number is required"
try:
port = int(port_number)
if port < 1 or port > 65535:
return False, None, "Port must be between 1 and 65535"
except (ValueError, TypeError):
return False, None, "Invalid port number"
clean_port = int(port_number)
except ValueError:
return False, None, "Port number must be a valid integer"
# Validate protocol
valid_protocols = ["TCP", "UDP", "SCTP", "OTHER"]
if protocol not in valid_protocols:
return False, None, f"Protocol must be one of: {', '.join(valid_protocols)}"
if clean_port < 1 or clean_port > 65535:
return False, None, "Port number must be between 1 and 65535"
return True, port, None
# Add server-side check for conflicts if server_id is provided
if server_id:
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):
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
# 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
port_number, protocol, description, server_id, app_id
)
if not valid:
continue # Skip invalid ports
return False, error
# Check if port already exists
# Check if port already exists for this app
existing_port = Port.query.filter_by(
app_id=app_id, port_number=clean_port
app_id=app_id, port_number=clean_port, protocol=protocol
).first()
if existing_port:
# Update existing port
existing_port.protocol = protocol
existing_port.description = description
else:
# Create new port
@ -84,46 +155,49 @@ def process_app_ports(app_id, port_data):
return False, str(e)
def save_app(name, server_id, documentation, port_data=None, existing_app_id=None):
def save_app(name, server_id, documentation, port_data, app_id=None, url=None):
"""
Save or update an application and its ports
Returns tuple (success, app_object_or_none, error_message)
Save or update an application
Returns (success, app, error_message)
"""
try:
# Validate input data
valid, error = validate_app_data(name, server_id, existing_app_id)
# Validate application data
valid, error = validate_app_data(name, server_id, app_id)
if not valid:
return False, None, error
# Create or update app
if existing_app_id:
if app_id:
# Update existing app
app = App.query.get(existing_app_id)
app = App.query.get(app_id)
if not app:
return False, None, f"Application with ID {existing_app_id} not found"
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)
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
# Flush to get the app ID if new
db.session.flush()
# Remove all existing ports if updating
if app_id:
Port.query.filter_by(app_id=app_id).delete()
# Process ports if provided
if port_data:
success, error = process_app_ports(app.id, port_data)
if not success:
db.session.rollback()
return False, None, f"Error processing ports: {error}"
# 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
# Commit all changes
db.session.commit()
return True, app, None
except Exception as e:
db.session.rollback()
return False, None, str(e)