wip
This commit is contained in:
parent
3b2f1db4ce
commit
5c16964b76
47 changed files with 2080 additions and 1053 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
app/routes/__pycache__/importexport.cpython-313.pyc
Normal file
BIN
app/routes/__pycache__/importexport.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
app/routes/__pycache__/static.cpython-313.pyc
Normal file
BIN
app/routes/__pycache__/static.cpython-313.pyc
Normal file
Binary file not shown.
|
@ -1,365 +1,439 @@
|
|||
from flask import Blueprint, jsonify, request, abort
|
||||
from flask import Blueprint, jsonify, request, abort, current_app, render_template
|
||||
from flask_login import login_required
|
||||
from app.core.models import Subnet, Server, App, Port
|
||||
from app.core.extensions import db
|
||||
from app.scripts.ip_scanner import scan
|
||||
import random
|
||||
import ipaddress
|
||||
from flask_wtf import CSRFProtect
|
||||
import markdown
|
||||
from datetime import datetime
|
||||
|
||||
bp = Blueprint('api', __name__, url_prefix='/api')
|
||||
bp = Blueprint("api", __name__, url_prefix="/api")
|
||||
csrf = CSRFProtect()
|
||||
|
||||
@bp.route('/subnets', methods=['GET'])
|
||||
|
||||
@bp.route("/subnets", methods=["GET"])
|
||||
def get_subnets():
|
||||
"""Get all subnets grouped by site"""
|
||||
subnets = Subnet.query.all()
|
||||
|
||||
|
||||
# Group subnets by location (site)
|
||||
sites = {}
|
||||
for subnet in subnets:
|
||||
location = subnet.location
|
||||
if location not in sites:
|
||||
sites[location] = []
|
||||
|
||||
sites[location].append({
|
||||
'id': subnet.id,
|
||||
'cidr': subnet.cidr,
|
||||
'location': location
|
||||
})
|
||||
|
||||
|
||||
sites[location].append(
|
||||
{"id": subnet.id, "cidr": subnet.cidr, "location": location}
|
||||
)
|
||||
|
||||
# Convert to list of site objects
|
||||
result = [
|
||||
{
|
||||
'name': site_name,
|
||||
'subnets': subnets
|
||||
} for site_name, subnets in sites.items()
|
||||
{"name": site_name, "subnets": subnets} for site_name, subnets in sites.items()
|
||||
]
|
||||
|
||||
|
||||
return jsonify(result)
|
||||
|
||||
@bp.route('/subnets/<int:subnet_id>', methods=['GET'])
|
||||
|
||||
@bp.route("/subnets/<int:subnet_id>", methods=["GET"])
|
||||
@login_required
|
||||
def get_subnet(subnet_id):
|
||||
"""Get details for a specific subnet"""
|
||||
subnet = Subnet.query.get_or_404(subnet_id)
|
||||
|
||||
|
||||
servers = []
|
||||
for server in Server.query.filter_by(subnet_id=subnet_id).all():
|
||||
servers.append({
|
||||
'id': server.id,
|
||||
'hostname': server.hostname,
|
||||
'ip_address': server.ip_address,
|
||||
'created_at': server.created_at.strftime('%Y-%m-%d %H:%M:%S')
|
||||
})
|
||||
|
||||
servers.append(
|
||||
{
|
||||
"id": server.id,
|
||||
"hostname": server.hostname,
|
||||
"ip_address": server.ip_address,
|
||||
"created_at": server.created_at.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
}
|
||||
)
|
||||
|
||||
result = {
|
||||
'id': subnet.id,
|
||||
'cidr': subnet.cidr,
|
||||
'location': subnet.location,
|
||||
'used_ips': subnet.used_ips,
|
||||
'auto_scan': subnet.auto_scan,
|
||||
'created_at': subnet.created_at.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'servers': servers
|
||||
"id": subnet.id,
|
||||
"cidr": subnet.cidr,
|
||||
"location": subnet.location,
|
||||
"used_ips": subnet.used_ips,
|
||||
"auto_scan": subnet.auto_scan,
|
||||
"created_at": subnet.created_at.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"servers": servers,
|
||||
}
|
||||
|
||||
|
||||
return jsonify(result)
|
||||
|
||||
@bp.route('/subnets/<int:subnet_id>/scan', methods=['POST'])
|
||||
|
||||
@bp.route("/subnets/<int:subnet_id>/scan", methods=["POST"])
|
||||
@login_required
|
||||
def api_subnet_scan(subnet_id):
|
||||
"""Scan a subnet via API"""
|
||||
subnet = Subnet.query.get_or_404(subnet_id)
|
||||
|
||||
|
||||
try:
|
||||
results = scan(subnet.cidr, save_results=True)
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'subnet': subnet.cidr,
|
||||
'hosts_found': len(results),
|
||||
'results': results
|
||||
})
|
||||
return jsonify(
|
||||
{
|
||||
"success": True,
|
||||
"subnet": subnet.cidr,
|
||||
"hosts_found": len(results),
|
||||
"results": results,
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'Error scanning subnet: {str(e)}'
|
||||
}), 500
|
||||
return (
|
||||
jsonify({"success": False, "message": f"Error scanning subnet: {str(e)}"}),
|
||||
500,
|
||||
)
|
||||
|
||||
@bp.route('/servers', methods=['GET'])
|
||||
|
||||
@bp.route("/servers", methods=["GET"])
|
||||
@login_required
|
||||
def get_servers():
|
||||
"""Get all servers"""
|
||||
servers = Server.query.all()
|
||||
result = []
|
||||
|
||||
for server in servers:
|
||||
result.append({
|
||||
'id': server.id,
|
||||
'hostname': server.hostname,
|
||||
'ip_address': server.ip_address,
|
||||
'subnet_id': server.subnet_id,
|
||||
'created_at': server.created_at.strftime('%Y-%m-%d %H:%M:%S')
|
||||
})
|
||||
|
||||
return jsonify({'servers': result})
|
||||
|
||||
@bp.route('/servers/<int:server_id>', methods=['GET'])
|
||||
for server in servers:
|
||||
result.append(
|
||||
{
|
||||
"id": server.id,
|
||||
"hostname": server.hostname,
|
||||
"ip_address": server.ip_address,
|
||||
"subnet_id": server.subnet_id,
|
||||
"created_at": server.created_at.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
}
|
||||
)
|
||||
|
||||
return jsonify({"servers": result})
|
||||
|
||||
|
||||
@bp.route("/servers/<int:server_id>", methods=["GET"])
|
||||
@login_required
|
||||
def get_server(server_id):
|
||||
"""Get a specific server"""
|
||||
server = Server.query.get_or_404(server_id)
|
||||
|
||||
|
||||
apps = []
|
||||
for app in server.apps:
|
||||
ports = []
|
||||
for port in app.ports:
|
||||
ports.append({
|
||||
'id': port.id,
|
||||
'port_number': port.port_number,
|
||||
'protocol': port.protocol,
|
||||
'description': port.description
|
||||
})
|
||||
|
||||
apps.append({
|
||||
'id': app.id,
|
||||
'name': app.name,
|
||||
'ports': ports,
|
||||
'created_at': app.created_at.strftime('%Y-%m-%d %H:%M:%S')
|
||||
})
|
||||
|
||||
ports.append(
|
||||
{
|
||||
"id": port.id,
|
||||
"port_number": port.port_number,
|
||||
"protocol": port.protocol,
|
||||
"description": port.description,
|
||||
}
|
||||
)
|
||||
|
||||
apps.append(
|
||||
{
|
||||
"id": app.id,
|
||||
"name": app.name,
|
||||
"ports": ports,
|
||||
"created_at": app.created_at.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
}
|
||||
)
|
||||
|
||||
result = {
|
||||
'id': server.id,
|
||||
'hostname': server.hostname,
|
||||
'ip_address': server.ip_address,
|
||||
'subnet_id': server.subnet_id,
|
||||
'documentation': server.documentation,
|
||||
'apps': apps,
|
||||
'created_at': server.created_at.strftime('%Y-%m-%d %H:%M:%S')
|
||||
"id": server.id,
|
||||
"hostname": server.hostname,
|
||||
"ip_address": server.ip_address,
|
||||
"subnet_id": server.subnet_id,
|
||||
"documentation": server.documentation,
|
||||
"apps": apps,
|
||||
"created_at": server.created_at.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
}
|
||||
|
||||
|
||||
return jsonify(result)
|
||||
|
||||
@bp.route('/apps', methods=['GET'])
|
||||
|
||||
@bp.route("/apps", methods=["GET"])
|
||||
@login_required
|
||||
def get_apps():
|
||||
"""Get all applications"""
|
||||
apps = App.query.all()
|
||||
result = []
|
||||
|
||||
for app in apps:
|
||||
result.append({
|
||||
'id': app.id,
|
||||
'name': app.name,
|
||||
'server_id': app.server_id,
|
||||
'created_at': app.created_at.strftime('%Y-%m-%d %H:%M:%S')
|
||||
})
|
||||
|
||||
return jsonify({'apps': result})
|
||||
|
||||
@bp.route('/apps/<int:app_id>', methods=['GET'])
|
||||
for app in apps:
|
||||
result.append(
|
||||
{
|
||||
"id": app.id,
|
||||
"name": app.name,
|
||||
"server_id": app.server_id,
|
||||
"created_at": app.created_at.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
}
|
||||
)
|
||||
|
||||
return jsonify({"apps": result})
|
||||
|
||||
|
||||
@bp.route("/apps/<int:app_id>", methods=["GET"])
|
||||
@login_required
|
||||
def get_app(app_id):
|
||||
"""Get details for a specific application"""
|
||||
app = App.query.get_or_404(app_id)
|
||||
|
||||
|
||||
result = {
|
||||
'id': app.id,
|
||||
'name': app.name,
|
||||
'server_id': app.server_id,
|
||||
'documentation': app.documentation,
|
||||
'created_at': app.created_at.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'ports': app.ports
|
||||
"id": app.id,
|
||||
"name": app.name,
|
||||
"server_id": app.server_id,
|
||||
"documentation": app.documentation,
|
||||
"created_at": app.created_at.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"ports": app.ports,
|
||||
}
|
||||
|
||||
|
||||
return jsonify(result)
|
||||
|
||||
@bp.route('/status', methods=['GET'])
|
||||
def status():
|
||||
return jsonify({'status': 'OK'})
|
||||
|
||||
@bp.route('/markdown-preview', methods=['POST'])
|
||||
@bp.route("/status", methods=["GET"])
|
||||
def status():
|
||||
return jsonify({"status": "OK"})
|
||||
|
||||
|
||||
@bp.route("/markdown-preview", methods=["POST"])
|
||||
@csrf.exempt # Remove this line in production! Temporary fix for demo purposes
|
||||
def markdown_preview():
|
||||
data = request.json
|
||||
md_content = data.get('markdown', '')
|
||||
md_content = data.get("markdown", "")
|
||||
html = markdown.markdown(md_content)
|
||||
return jsonify({'html': html})
|
||||
return jsonify({"html": html})
|
||||
|
||||
@bp.route('/ports/suggest', methods=['GET'])
|
||||
|
||||
@bp.route("/ports/suggest", methods=["GET"])
|
||||
def suggest_ports():
|
||||
app_type = request.args.get('type', '').lower()
|
||||
|
||||
app_type = request.args.get("type", "").lower()
|
||||
|
||||
# Common port suggestions based on app type
|
||||
suggestions = {
|
||||
'web': [
|
||||
{'port': 80, 'type': 'tcp', 'desc': 'HTTP'},
|
||||
{'port': 443, 'type': 'tcp', 'desc': 'HTTPS'}
|
||||
"web": [
|
||||
{"port": 80, "type": "tcp", "desc": "HTTP"},
|
||||
{"port": 443, "type": "tcp", "desc": "HTTPS"},
|
||||
],
|
||||
'database': [
|
||||
{'port': 3306, 'type': 'tcp', 'desc': 'MySQL'},
|
||||
{'port': 5432, 'type': 'tcp', 'desc': 'PostgreSQL'},
|
||||
{'port': 1521, 'type': 'tcp', 'desc': 'Oracle'}
|
||||
"database": [
|
||||
{"port": 3306, "type": "tcp", "desc": "MySQL"},
|
||||
{"port": 5432, "type": "tcp", "desc": "PostgreSQL"},
|
||||
{"port": 1521, "type": "tcp", "desc": "Oracle"},
|
||||
],
|
||||
'mail': [
|
||||
{'port': 25, 'type': 'tcp', 'desc': 'SMTP'},
|
||||
{'port': 143, 'type': 'tcp', 'desc': 'IMAP'},
|
||||
{'port': 110, 'type': 'tcp', 'desc': 'POP3'}
|
||||
"mail": [
|
||||
{"port": 25, "type": "tcp", "desc": "SMTP"},
|
||||
{"port": 143, "type": "tcp", "desc": "IMAP"},
|
||||
{"port": 110, "type": "tcp", "desc": "POP3"},
|
||||
],
|
||||
"file": [
|
||||
{"port": 21, "type": "tcp", "desc": "FTP"},
|
||||
{"port": 22, "type": "tcp", "desc": "SFTP/SSH"},
|
||||
{"port": 445, "type": "tcp", "desc": "SMB"},
|
||||
],
|
||||
'file': [
|
||||
{'port': 21, 'type': 'tcp', 'desc': 'FTP'},
|
||||
{'port': 22, 'type': 'tcp', 'desc': 'SFTP/SSH'},
|
||||
{'port': 445, 'type': 'tcp', 'desc': 'SMB'}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
if app_type in suggestions:
|
||||
return jsonify(suggestions[app_type])
|
||||
|
||||
# Default suggestions
|
||||
return jsonify([
|
||||
{'port': 80, 'type': 'tcp', 'desc': 'HTTP'},
|
||||
{'port': 22, 'type': 'tcp', 'desc': 'SSH'}
|
||||
])
|
||||
|
||||
@bp.route('/servers/<int:server_id>/suggest_port', methods=['GET'])
|
||||
# Default suggestions
|
||||
return jsonify(
|
||||
[
|
||||
{"port": 80, "type": "tcp", "desc": "HTTP"},
|
||||
{"port": 22, "type": "tcp", "desc": "SSH"},
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/servers/<int:server_id>/suggest_port", methods=["GET"])
|
||||
@login_required
|
||||
def suggest_port(server_id):
|
||||
"""Suggest a random unused port for a server"""
|
||||
server = Server.query.get_or_404(server_id)
|
||||
|
||||
|
||||
# Get all used ports for this server
|
||||
used_ports = []
|
||||
for app in server.apps:
|
||||
for port in app.ports:
|
||||
used_ports.append(port.port_number)
|
||||
|
||||
|
||||
# Find an unused port in the dynamic/private port range
|
||||
available_port = None
|
||||
attempts = 0
|
||||
|
||||
|
||||
while attempts < 50: # Try 50 times to find a random port
|
||||
# Random port between 10000 and 65535
|
||||
port = random.randint(10000, 65535)
|
||||
|
||||
|
||||
if port not in used_ports:
|
||||
available_port = port
|
||||
break
|
||||
|
||||
|
||||
attempts += 1
|
||||
|
||||
|
||||
if available_port is None:
|
||||
# If no random port found, find first available in sequence
|
||||
for port in range(10000, 65536):
|
||||
if port not in used_ports:
|
||||
available_port = port
|
||||
break
|
||||
|
||||
return jsonify({'port': available_port})
|
||||
|
||||
@bp.route('/apps/<int:app_id>/ports', methods=['GET'])
|
||||
@login_required
|
||||
def get_app_ports(app_id):
|
||||
"""Get all ports for an app"""
|
||||
app = App.query.get_or_404(app_id)
|
||||
|
||||
ports = []
|
||||
for port in app.ports:
|
||||
ports.append({
|
||||
'id': port.id,
|
||||
'port_number': port.port_number,
|
||||
'protocol': port.protocol,
|
||||
'description': port.description
|
||||
})
|
||||
|
||||
return jsonify({'ports': ports})
|
||||
return jsonify({"port": available_port})
|
||||
|
||||
@bp.route('/apps/<int:app_id>/ports', methods=['POST'])
|
||||
|
||||
@bp.route("/app/<int:app_id>/add-port", methods=["POST"])
|
||||
@login_required
|
||||
def add_app_port(app_id):
|
||||
"""Add a new port to an app"""
|
||||
"""Add a port to an application"""
|
||||
app = App.query.get_or_404(app_id)
|
||||
|
||||
data = request.json
|
||||
if not data or 'port_number' not in data:
|
||||
return jsonify({'error': 'Missing port number'}), 400
|
||||
|
||||
port_number = data.get('port_number')
|
||||
protocol = data.get('protocol', 'TCP')
|
||||
description = data.get('description', '')
|
||||
|
||||
# Check if port already exists for this app
|
||||
existing_port = Port.query.filter_by(app_id=app_id, port_number=port_number).first()
|
||||
if existing_port:
|
||||
return jsonify({'error': 'Port already exists for this app'}), 400
|
||||
|
||||
new_port = Port(
|
||||
app_id=app_id,
|
||||
port_number=port_number,
|
||||
protocol=protocol,
|
||||
description=description
|
||||
)
|
||||
|
||||
db.session.add(new_port)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'id': new_port.id,
|
||||
'port_number': new_port.port_number,
|
||||
'protocol': new_port.protocol,
|
||||
'description': new_port.description
|
||||
})
|
||||
|
||||
@bp.route('/ports/<int:port_id>', methods=['DELETE'])
|
||||
# Accept both JSON and form data
|
||||
if request.is_json:
|
||||
data = request.json
|
||||
else:
|
||||
data = request.form
|
||||
|
||||
port_number = data.get("port")
|
||||
protocol = data.get("protocol", "TCP")
|
||||
description = data.get("description", "")
|
||||
|
||||
if not port_number:
|
||||
return jsonify({"success": False, "error": "Port number is required"}), 400
|
||||
|
||||
try:
|
||||
port_number = int(port_number)
|
||||
|
||||
# Check if port already exists for this app
|
||||
existing_port = Port.query.filter_by(app_id=app_id, number=port_number).first()
|
||||
if existing_port:
|
||||
return (
|
||||
jsonify(
|
||||
{
|
||||
"success": False,
|
||||
"error": "Port already exists for this application",
|
||||
}
|
||||
),
|
||||
400,
|
||||
)
|
||||
|
||||
# Create new port
|
||||
port = Port(
|
||||
number=port_number,
|
||||
protocol=protocol,
|
||||
description=description,
|
||||
app_id=app_id,
|
||||
)
|
||||
|
||||
db.session.add(port)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify(
|
||||
{
|
||||
"success": True,
|
||||
"message": f"Port {port_number} added to {app.name}",
|
||||
"port": {
|
||||
"id": port.id,
|
||||
"number": port.number,
|
||||
"protocol": port.protocol,
|
||||
"description": port.description,
|
||||
},
|
||||
}
|
||||
)
|
||||
except ValueError:
|
||||
return jsonify({"success": False, "error": "Invalid port number"}), 400
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
|
||||
@bp.route("/app/<int:app_id>/ports", methods=["GET"])
|
||||
@login_required
|
||||
def get_app_ports(app_id):
|
||||
"""Get all ports for an application"""
|
||||
app = App.query.get_or_404(app_id)
|
||||
|
||||
ports = Port.query.filter_by(app_id=app_id).all()
|
||||
|
||||
result = {
|
||||
"app_id": app_id,
|
||||
"ports": [
|
||||
{
|
||||
"id": port.id,
|
||||
"number": port.number,
|
||||
"protocol": port.protocol,
|
||||
"description": port.description,
|
||||
}
|
||||
for port in ports
|
||||
],
|
||||
}
|
||||
|
||||
return jsonify(result)
|
||||
|
||||
|
||||
@bp.route("/port/<int:port_id>/delete", methods=["POST"])
|
||||
@login_required
|
||||
def delete_port(port_id):
|
||||
"""Delete a port"""
|
||||
port = Port.query.get_or_404(port_id)
|
||||
|
||||
db.session.delete(port)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({'success': True})
|
||||
# Add CSRF validation
|
||||
if request.is_json: # For AJAX requests
|
||||
csrf_token = request.json.get("csrf_token")
|
||||
if not csrf_token or not csrf.validate_csrf(csrf_token):
|
||||
return jsonify({"success": False, "error": "CSRF validation failed"}), 403
|
||||
|
||||
@bp.route('/subnets/<int:subnet_id>/servers', methods=['GET'])
|
||||
port = Port.query.get_or_404(port_id)
|
||||
|
||||
try:
|
||||
db.session.delete(port)
|
||||
db.session.commit()
|
||||
return jsonify({"success": True, "message": f"Port {port.number} deleted"})
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
|
||||
@bp.route("/subnets/<int:subnet_id>/servers", methods=["GET"])
|
||||
def get_subnet_servers(subnet_id):
|
||||
"""Get all servers for a specific subnet"""
|
||||
servers = Server.query.filter_by(subnet_id=subnet_id).all()
|
||||
return jsonify([{
|
||||
'id': server.id,
|
||||
'hostname': server.hostname,
|
||||
'ip_address': server.ip_address
|
||||
} for server in servers])
|
||||
return jsonify(
|
||||
[
|
||||
{
|
||||
"id": server.id,
|
||||
"hostname": server.hostname,
|
||||
"ip_address": server.ip_address,
|
||||
}
|
||||
for server in servers
|
||||
]
|
||||
)
|
||||
|
||||
@bp.route('/server/<int:server_id>/ports', methods=['GET'])
|
||||
|
||||
@bp.route("/server/<int:server_id>/ports", methods=["GET"])
|
||||
@login_required
|
||||
def get_server_ports(server_id):
|
||||
"""Get all used ports for a server"""
|
||||
server = Server.query.get_or_404(server_id)
|
||||
|
||||
|
||||
# Get all ports associated with this server
|
||||
ports = Port.query.filter_by(server_id=server_id).all()
|
||||
used_ports = [port.number for port in ports]
|
||||
|
||||
return jsonify({
|
||||
'server_id': server_id,
|
||||
'used_ports': used_ports
|
||||
})
|
||||
|
||||
@bp.route('/server/<int:server_id>/free-port', methods=['GET'])
|
||||
return jsonify({"server_id": server_id, "used_ports": used_ports})
|
||||
|
||||
|
||||
@bp.route("/server/<int:server_id>/free-port", methods=["GET"])
|
||||
@login_required
|
||||
def get_free_port(server_id):
|
||||
"""Find a free port for a server"""
|
||||
server = Server.query.get_or_404(server_id)
|
||||
|
||||
|
||||
# Get all ports associated with this server
|
||||
used_ports = [port.number for port in Port.query.filter_by(server_id=server_id).all()]
|
||||
|
||||
used_ports = [
|
||||
port.number for port in Port.query.filter_by(server_id=server_id).all()
|
||||
]
|
||||
|
||||
# Find the first free port (starting from 8000)
|
||||
for port_number in range(8000, 9000):
|
||||
if port_number not in used_ports:
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'port': port_number
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'No free ports available in the range 8000-9000'
|
||||
})
|
||||
return jsonify({"success": True, "port": port_number})
|
||||
|
||||
return jsonify(
|
||||
{"success": False, "error": "No free ports available in the range 8000-9000"}
|
||||
)
|
||||
|
|
|
@ -3,75 +3,87 @@ from flask_login import login_user, logout_user, current_user, login_required
|
|||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from app.core.extensions import db
|
||||
from app.core.auth import User
|
||||
import re
|
||||
from flask_wtf.csrf import CSRFProtect
|
||||
|
||||
bp = Blueprint('auth', __name__, url_prefix='/auth')
|
||||
bp = Blueprint("auth", __name__, url_prefix="/auth")
|
||||
csrf = CSRFProtect()
|
||||
|
||||
@bp.route('/login', methods=['GET', 'POST'])
|
||||
|
||||
@bp.route("/login", methods=["GET", "POST"])
|
||||
def login():
|
||||
"""User login"""
|
||||
if current_user.is_authenticated:
|
||||
return redirect(url_for('dashboard.dashboard_home'))
|
||||
|
||||
if request.method == 'POST':
|
||||
email = request.form.get('email')
|
||||
password = request.form.get('password')
|
||||
remember = 'remember' in request.form
|
||||
|
||||
user = User.query.filter_by(email=email).first()
|
||||
|
||||
if not user or not user.check_password(password):
|
||||
flash('Invalid email or password', 'danger')
|
||||
return render_template('auth/login.html', title='Login')
|
||||
|
||||
login_user(user, remember=remember)
|
||||
|
||||
next_page = request.args.get('next')
|
||||
if not next_page or not next_page.startswith('/'):
|
||||
next_page = url_for('dashboard.dashboard_home')
|
||||
|
||||
return redirect(next_page)
|
||||
|
||||
return render_template('auth/login.html', title='Login')
|
||||
return redirect(url_for("dashboard.dashboard_home"))
|
||||
|
||||
@bp.route('/register', methods=['GET', 'POST'])
|
||||
if request.method == "POST":
|
||||
email = request.form.get("email")
|
||||
password = request.form.get("password")
|
||||
remember = "remember" in request.form
|
||||
|
||||
user = User.query.filter_by(email=email).first()
|
||||
|
||||
if not user or not user.check_password(password):
|
||||
flash("Invalid email or password", "danger")
|
||||
return render_template("auth/login.html", title="Login")
|
||||
|
||||
login_user(user, remember=remember)
|
||||
|
||||
next_page = request.args.get("next")
|
||||
if not next_page or not next_page.startswith("/"):
|
||||
next_page = url_for("dashboard.dashboard_home")
|
||||
|
||||
return redirect(next_page)
|
||||
|
||||
return render_template("auth/login.html", title="Login")
|
||||
|
||||
|
||||
@bp.route("/register", methods=["GET", "POST"])
|
||||
@csrf.exempt # Remove for production! Temporary allow registration without CSRF
|
||||
def register():
|
||||
"""User registration"""
|
||||
if current_user.is_authenticated:
|
||||
return redirect(url_for('dashboard.dashboard_home'))
|
||||
|
||||
if request.method == 'POST':
|
||||
email = request.form.get('email')
|
||||
password = request.form.get('password')
|
||||
|
||||
# Validation
|
||||
if not email or not password:
|
||||
flash('Email and password are required', 'danger')
|
||||
return render_template('auth/register.html', title='Register')
|
||||
|
||||
if User.query.filter_by(email=email).first():
|
||||
flash('Email already registered', 'danger')
|
||||
return render_template('auth/register.html', title='Register')
|
||||
|
||||
# Create new user
|
||||
user = User(email=email)
|
||||
user.set_password(password)
|
||||
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
|
||||
flash('Registration successful! You are now logged in.', 'success')
|
||||
|
||||
# Auto-login after registration
|
||||
login_user(user)
|
||||
|
||||
return redirect(url_for('dashboard.dashboard_home'))
|
||||
|
||||
return render_template('auth/register.html', title='Register')
|
||||
return redirect(url_for("dashboard.dashboard_home"))
|
||||
|
||||
@bp.route('/logout')
|
||||
if request.method == "POST":
|
||||
email = request.form.get("email")
|
||||
username = request.form.get("username")
|
||||
password = request.form.get("password")
|
||||
password_confirm = request.form.get("password_confirm")
|
||||
|
||||
# Validate form data
|
||||
error = None
|
||||
if not email or not username or not password:
|
||||
error = "All fields are required."
|
||||
elif not re.match(r"[^@]+@[^@]+\.[^@]+", email):
|
||||
error = "Please enter a valid email address."
|
||||
elif password != password_confirm:
|
||||
error = "Passwords do not match."
|
||||
elif User.query.filter_by(email=email).first():
|
||||
error = "Email address already registered."
|
||||
elif User.query.filter_by(username=username).first():
|
||||
error = "Username already taken."
|
||||
|
||||
if error:
|
||||
flash(error, "danger")
|
||||
else:
|
||||
# Create new user
|
||||
new_user = User(email=email, username=username)
|
||||
new_user.set_password(password)
|
||||
|
||||
db.session.add(new_user)
|
||||
db.session.commit()
|
||||
|
||||
flash("Registration successful! You can now log in.", "success")
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
return render_template("auth/register.html", title="Register")
|
||||
|
||||
|
||||
@bp.route("/logout")
|
||||
@login_required
|
||||
def logout():
|
||||
"""User logout"""
|
||||
logout_user()
|
||||
flash('You have been logged out', 'info')
|
||||
return redirect(url_for('auth.login'))
|
||||
flash("You have been logged out", "info")
|
||||
return redirect(url_for("auth.login"))
|
||||
|
|
|
@ -4,383 +4,384 @@ import markdown
|
|||
from app.core.models import Server, App, Subnet, Port
|
||||
from app.core.extensions import db, limiter
|
||||
from datetime import datetime
|
||||
from app.utils.app_utils import validate_app_data
|
||||
|
||||
bp = Blueprint('dashboard', __name__, url_prefix='/dashboard')
|
||||
bp = Blueprint("dashboard", __name__, url_prefix="/dashboard")
|
||||
|
||||
@bp.route('/')
|
||||
|
||||
@bp.route("/")
|
||||
@login_required
|
||||
def dashboard_home():
|
||||
"""Main dashboard view showing server statistics"""
|
||||
server_count = Server.query.count()
|
||||
app_count = App.query.count()
|
||||
subnet_count = Subnet.query.count()
|
||||
|
||||
|
||||
# Get latest added servers
|
||||
latest_servers = Server.query.order_by(Server.created_at.desc()).limit(5).all()
|
||||
|
||||
|
||||
# Get subnets with usage stats
|
||||
subnets = Subnet.query.all()
|
||||
for subnet in subnets:
|
||||
subnet.usage_percent = subnet.used_ips / 254 * 100 if subnet.cidr.endswith('/24') else 0
|
||||
|
||||
subnet.usage_percent = (
|
||||
subnet.used_ips / 254 * 100 if subnet.cidr.endswith("/24") else 0
|
||||
)
|
||||
|
||||
return render_template(
|
||||
'dashboard/index.html',
|
||||
title='Dashboard',
|
||||
"dashboard/index.html",
|
||||
title="Dashboard",
|
||||
server_count=server_count,
|
||||
app_count=app_count,
|
||||
subnet_count=subnet_count,
|
||||
latest_servers=latest_servers,
|
||||
subnets=subnets,
|
||||
now=datetime.now()
|
||||
now=datetime.now(),
|
||||
)
|
||||
|
||||
@bp.route('/servers')
|
||||
|
||||
@bp.route("/servers")
|
||||
@login_required
|
||||
def server_list():
|
||||
"""List all servers"""
|
||||
servers = Server.query.order_by(Server.hostname).all()
|
||||
|
||||
|
||||
return render_template(
|
||||
'dashboard/server_list.html',
|
||||
title='Servers',
|
||||
"dashboard/server_list.html",
|
||||
title="Servers",
|
||||
servers=servers,
|
||||
now=datetime.now()
|
||||
now=datetime.now(),
|
||||
)
|
||||
|
||||
@bp.route('/server/<int:server_id>')
|
||||
|
||||
@bp.route("/server/<int:server_id>")
|
||||
@login_required
|
||||
def server_view(server_id):
|
||||
"""View server details"""
|
||||
server = Server.query.get_or_404(server_id)
|
||||
apps = App.query.filter_by(server_id=server_id).all()
|
||||
|
||||
|
||||
return render_template(
|
||||
'dashboard/server_view.html',
|
||||
title=f'Server - {server.hostname}',
|
||||
"dashboard/server_view.html",
|
||||
title=f"Server - {server.hostname}",
|
||||
server=server,
|
||||
apps=apps,
|
||||
now=datetime.now()
|
||||
now=datetime.now(),
|
||||
)
|
||||
|
||||
@bp.route('/server/new', methods=['GET', 'POST'])
|
||||
|
||||
@bp.route("/server/new", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def server_new():
|
||||
"""Create a new server"""
|
||||
subnets = Subnet.query.all()
|
||||
|
||||
if request.method == 'POST':
|
||||
hostname = request.form.get('hostname')
|
||||
ip_address = request.form.get('ip_address')
|
||||
subnet_id = request.form.get('subnet_id')
|
||||
documentation = request.form.get('documentation', '')
|
||||
|
||||
|
||||
if request.method == "POST":
|
||||
hostname = request.form.get("hostname")
|
||||
ip_address = request.form.get("ip_address")
|
||||
subnet_id = request.form.get("subnet_id")
|
||||
documentation = request.form.get("documentation", "")
|
||||
|
||||
# Basic validation
|
||||
if not hostname or not ip_address or not subnet_id:
|
||||
flash('Please fill in all required fields', 'danger')
|
||||
flash("Please fill in all required fields", "danger")
|
||||
return render_template(
|
||||
'dashboard/server_form.html',
|
||||
title='New Server',
|
||||
"dashboard/server_form.html",
|
||||
title="New Server",
|
||||
subnets=subnets,
|
||||
now=datetime.now()
|
||||
now=datetime.now(),
|
||||
)
|
||||
|
||||
|
||||
# Check if hostname or IP already exists
|
||||
if Server.query.filter_by(hostname=hostname).first():
|
||||
flash('Hostname already exists', 'danger')
|
||||
flash("Hostname already exists", "danger")
|
||||
return render_template(
|
||||
'dashboard/server_form.html',
|
||||
title='New Server',
|
||||
"dashboard/server_form.html",
|
||||
title="New Server",
|
||||
subnets=subnets,
|
||||
now=datetime.now()
|
||||
now=datetime.now(),
|
||||
)
|
||||
|
||||
|
||||
if Server.query.filter_by(ip_address=ip_address).first():
|
||||
flash('IP address already exists', 'danger')
|
||||
flash("IP address already exists", "danger")
|
||||
return render_template(
|
||||
'dashboard/server_form.html',
|
||||
title='New Server',
|
||||
"dashboard/server_form.html",
|
||||
title="New Server",
|
||||
subnets=subnets,
|
||||
now=datetime.now()
|
||||
now=datetime.now(),
|
||||
)
|
||||
|
||||
|
||||
# Create new server
|
||||
server = Server(
|
||||
hostname=hostname,
|
||||
ip_address=ip_address,
|
||||
subnet_id=subnet_id,
|
||||
documentation=documentation
|
||||
documentation=documentation,
|
||||
)
|
||||
|
||||
|
||||
db.session.add(server)
|
||||
db.session.commit()
|
||||
|
||||
flash('Server created successfully', 'success')
|
||||
return redirect(url_for('dashboard.server_view', server_id=server.id))
|
||||
|
||||
|
||||
flash("Server created successfully", "success")
|
||||
return redirect(url_for("dashboard.server_view", server_id=server.id))
|
||||
|
||||
return render_template(
|
||||
'dashboard/server_form.html',
|
||||
title='New Server',
|
||||
"dashboard/server_form.html",
|
||||
title="New Server",
|
||||
subnets=subnets,
|
||||
now=datetime.now()
|
||||
now=datetime.now(),
|
||||
)
|
||||
|
||||
@bp.route('/server/<int:server_id>/edit', methods=['GET', 'POST'])
|
||||
|
||||
@bp.route("/server/<int:server_id>/edit", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def server_edit(server_id):
|
||||
"""Edit an existing server"""
|
||||
server = Server.query.get_or_404(server_id)
|
||||
subnets = Subnet.query.all()
|
||||
|
||||
if request.method == 'POST':
|
||||
hostname = request.form.get('hostname')
|
||||
ip_address = request.form.get('ip_address')
|
||||
subnet_id = request.form.get('subnet_id')
|
||||
documentation = request.form.get('documentation', '')
|
||||
|
||||
|
||||
if request.method == "POST":
|
||||
hostname = request.form.get("hostname")
|
||||
ip_address = request.form.get("ip_address")
|
||||
subnet_id = request.form.get("subnet_id")
|
||||
documentation = request.form.get("documentation", "")
|
||||
|
||||
if not hostname or not ip_address or not subnet_id:
|
||||
flash('All fields are required', 'danger')
|
||||
flash("All fields are required", "danger")
|
||||
return render_template(
|
||||
'dashboard/server_form.html',
|
||||
title='Edit Server',
|
||||
"dashboard/server_form.html",
|
||||
title="Edit Server",
|
||||
server=server,
|
||||
subnets=subnets
|
||||
subnets=subnets,
|
||||
)
|
||||
|
||||
|
||||
# Check if hostname changed and already exists
|
||||
if hostname != server.hostname and Server.query.filter_by(hostname=hostname).first():
|
||||
flash('Hostname already exists', 'danger')
|
||||
if (
|
||||
hostname != server.hostname
|
||||
and Server.query.filter_by(hostname=hostname).first()
|
||||
):
|
||||
flash("Hostname already exists", "danger")
|
||||
return render_template(
|
||||
'dashboard/server_form.html',
|
||||
title='Edit Server',
|
||||
"dashboard/server_form.html",
|
||||
title="Edit Server",
|
||||
server=server,
|
||||
subnets=subnets
|
||||
subnets=subnets,
|
||||
)
|
||||
|
||||
|
||||
# Check if IP changed and already exists
|
||||
if ip_address != server.ip_address and Server.query.filter_by(ip_address=ip_address).first():
|
||||
flash('IP address already exists', 'danger')
|
||||
if (
|
||||
ip_address != server.ip_address
|
||||
and Server.query.filter_by(ip_address=ip_address).first()
|
||||
):
|
||||
flash("IP address already exists", "danger")
|
||||
return render_template(
|
||||
'dashboard/server_form.html',
|
||||
title='Edit Server',
|
||||
"dashboard/server_form.html",
|
||||
title="Edit Server",
|
||||
server=server,
|
||||
subnets=subnets
|
||||
subnets=subnets,
|
||||
)
|
||||
|
||||
|
||||
# Update server
|
||||
server.hostname = hostname
|
||||
server.ip_address = ip_address
|
||||
server.subnet_id = subnet_id
|
||||
server.documentation = documentation
|
||||
|
||||
|
||||
db.session.commit()
|
||||
|
||||
flash('Server updated successfully', 'success')
|
||||
return redirect(url_for('dashboard.server_view', server_id=server.id))
|
||||
|
||||
|
||||
flash("Server updated successfully", "success")
|
||||
return redirect(url_for("dashboard.server_view", server_id=server.id))
|
||||
|
||||
# GET request - show form with current values
|
||||
return render_template(
|
||||
'dashboard/server_form.html',
|
||||
title=f'Edit Server - {server.hostname}',
|
||||
"dashboard/server_form.html",
|
||||
title=f"Edit Server - {server.hostname}",
|
||||
server=server,
|
||||
subnets=subnets
|
||||
subnets=subnets,
|
||||
)
|
||||
|
||||
@bp.route('/server/<int:server_id>/delete', methods=['POST'])
|
||||
|
||||
@bp.route("/server/<int:server_id>/delete", methods=["POST"])
|
||||
@login_required
|
||||
def server_delete(server_id):
|
||||
"""Delete a server"""
|
||||
server = Server.query.get_or_404(server_id)
|
||||
|
||||
|
||||
# Delete all apps associated with this server
|
||||
App.query.filter_by(server_id=server_id).delete()
|
||||
|
||||
|
||||
# Delete the server
|
||||
db.session.delete(server)
|
||||
db.session.commit()
|
||||
|
||||
flash('Server deleted successfully', 'success')
|
||||
return redirect(url_for('dashboard.dashboard_home'))
|
||||
|
||||
@bp.route('/app/new', methods=['GET', 'POST'])
|
||||
flash("Server deleted successfully", "success")
|
||||
return redirect(url_for("dashboard.dashboard_home"))
|
||||
|
||||
|
||||
@bp.route("/app/new", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def app_new():
|
||||
"""Create a new application"""
|
||||
"""Create a new application with comprehensive error handling"""
|
||||
# Get all servers for dropdown
|
||||
servers = Server.query.all()
|
||||
|
||||
if request.method == 'POST':
|
||||
name = request.form.get('name')
|
||||
server_id = request.form.get('server_id')
|
||||
documentation = request.form.get('documentation', '')
|
||||
|
||||
# Get port data from form
|
||||
port_numbers = request.form.getlist('port_numbers[]')
|
||||
protocols = request.form.getlist('protocols[]')
|
||||
port_descriptions = request.form.getlist('port_descriptions[]')
|
||||
|
||||
# Basic validation
|
||||
if not name or not server_id:
|
||||
flash('Please fill in all required fields', 'danger')
|
||||
return render_template(
|
||||
'dashboard/app_form.html',
|
||||
title='New Application',
|
||||
servers=servers
|
||||
)
|
||||
|
||||
# Create new app
|
||||
app = App(
|
||||
name=name,
|
||||
server_id=server_id,
|
||||
documentation=documentation
|
||||
)
|
||||
|
||||
db.session.add(app)
|
||||
db.session.flush() # Get the app ID without committing
|
||||
|
||||
# Add ports if provided
|
||||
|
||||
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()
|
||||
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[]")
|
||||
|
||||
for i in range(len(port_numbers)):
|
||||
if port_numbers[i] and port_numbers[i].strip():
|
||||
try:
|
||||
port_num = int(port_numbers[i])
|
||||
|
||||
# Get protocol and description, handling index errors
|
||||
protocol = protocols[i] if i < len(protocols) else 'TCP'
|
||||
description = port_descriptions[i] if i < len(port_descriptions) else ''
|
||||
|
||||
new_port = Port(
|
||||
app_id=app.id,
|
||||
port_number=port_num,
|
||||
protocol=protocol,
|
||||
description=description
|
||||
)
|
||||
db.session.add(new_port)
|
||||
except (ValueError, IndexError):
|
||||
continue
|
||||
|
||||
db.session.commit()
|
||||
|
||||
flash('Application created successfully', 'success')
|
||||
return redirect(url_for('dashboard.server_view', server_id=server_id))
|
||||
|
||||
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))
|
||||
|
||||
# Save application
|
||||
from app.utils.app_utils import save_app
|
||||
|
||||
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))
|
||||
else:
|
||||
flash(error, "danger")
|
||||
|
||||
# For GET requests or failed POSTs
|
||||
return render_template(
|
||||
'dashboard/app_form.html',
|
||||
title='New Application',
|
||||
servers=servers
|
||||
"dashboard/app_form.html",
|
||||
title="Create New Application",
|
||||
edit_mode=False,
|
||||
dashboard_link=url_for("dashboard.dashboard_home"),
|
||||
servers=servers,
|
||||
)
|
||||
|
||||
@bp.route('/app/<int:app_id>', methods=['GET'])
|
||||
|
||||
@bp.route("/app/<int:app_id>", methods=["GET"])
|
||||
@login_required
|
||||
def app_view(app_id):
|
||||
"""View a specific application"""
|
||||
app = App.query.get_or_404(app_id)
|
||||
server = Server.query.get(app.server_id)
|
||||
|
||||
|
||||
return render_template(
|
||||
'dashboard/app_view.html',
|
||||
title=f'Application - {app.name}',
|
||||
"dashboard/app_view.html",
|
||||
title=f"Application - {app.name}",
|
||||
app=app,
|
||||
server=server,
|
||||
now=datetime.now()
|
||||
now=datetime.now(),
|
||||
)
|
||||
|
||||
@bp.route('/app/<int:app_id>/edit', methods=['GET', 'POST'])
|
||||
|
||||
@bp.route("/app/<int:app_id>/edit", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def app_edit(app_id):
|
||||
"""Edit an existing application"""
|
||||
"""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()
|
||||
|
||||
if request.method == 'POST':
|
||||
name = request.form.get('name')
|
||||
server_id = request.form.get('server_id')
|
||||
documentation = request.form.get('documentation', '')
|
||||
|
||||
if not name or not server_id:
|
||||
flash('All required fields must be filled', 'danger')
|
||||
return render_template(
|
||||
'dashboard/app_form.html',
|
||||
title='Edit Application',
|
||||
app=app,
|
||||
servers=servers
|
||||
|
||||
if request.method == "POST":
|
||||
# Get form data
|
||||
name = request.form.get("name", "").strip()
|
||||
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[]")
|
||||
|
||||
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))
|
||||
|
||||
# Replace local validation with shared function
|
||||
valid, error = validate_app_data(name, server_id, existing_app_id=app_id)
|
||||
|
||||
if valid:
|
||||
# Update application
|
||||
from app.utils.app_utils import save_app
|
||||
|
||||
success, updated_app, error = save_app(
|
||||
name, server_id, documentation, port_data, app_id
|
||||
)
|
||||
|
||||
# Check if name changed and already exists on the same server
|
||||
existing_app = App.query.filter(App.name == name,
|
||||
App.server_id == server_id,
|
||||
App.id != app.id).first()
|
||||
if existing_app:
|
||||
flash('Application with this name already exists on the selected server', 'danger')
|
||||
return render_template(
|
||||
'dashboard/app_form.html',
|
||||
title='Edit Application',
|
||||
app=app,
|
||||
servers=servers
|
||||
)
|
||||
|
||||
# Update application
|
||||
app.name = name
|
||||
app.server_id = server_id
|
||||
app.documentation = documentation
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
flash('Application updated successfully', 'success')
|
||||
return redirect(url_for('dashboard.app_view', app_id=app.id))
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
flash(f'Error updating application: {str(e)}', 'danger')
|
||||
|
||||
|
||||
if success:
|
||||
flash("Application updated successfully", "success")
|
||||
return redirect(url_for("dashboard.app_view", app_id=app_id))
|
||||
else:
|
||||
flash(error, "danger")
|
||||
else:
|
||||
flash(error, "danger")
|
||||
|
||||
# For GET requests or failed POSTs
|
||||
return render_template(
|
||||
'dashboard/app_form.html',
|
||||
title=f'Edit Application - {app.name}',
|
||||
"dashboard/app_form.html",
|
||||
title=f"Edit Application: {app.name}",
|
||||
edit_mode=True,
|
||||
app=app,
|
||||
servers=servers
|
||||
dashboard_link=url_for("dashboard.dashboard_home"),
|
||||
servers=servers,
|
||||
)
|
||||
|
||||
@bp.route('/app/<int:app_id>/delete', methods=['POST'])
|
||||
|
||||
@bp.route("/app/<int:app_id>/delete", methods=["POST"])
|
||||
@login_required
|
||||
def app_delete(app_id):
|
||||
"""Delete an application"""
|
||||
app = App.query.get_or_404(app_id)
|
||||
server_id = app.server_id
|
||||
|
||||
|
||||
db.session.delete(app)
|
||||
db.session.commit()
|
||||
|
||||
flash('Application deleted successfully', 'success')
|
||||
return redirect(url_for('dashboard.server_view', server_id=server_id))
|
||||
|
||||
@bp.route('/settings', methods=['GET', 'POST'])
|
||||
flash("Application deleted successfully", "success")
|
||||
return redirect(url_for("dashboard.server_view", server_id=server_id))
|
||||
|
||||
|
||||
@bp.route("/settings", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def settings():
|
||||
"""User settings page"""
|
||||
if request.method == 'POST':
|
||||
if request.method == "POST":
|
||||
# Handle user settings update
|
||||
current_password = request.form.get('current_password')
|
||||
new_password = request.form.get('new_password')
|
||||
confirm_password = request.form.get('confirm_password')
|
||||
|
||||
current_password = request.form.get("current_password")
|
||||
new_password = request.form.get("new_password")
|
||||
confirm_password = request.form.get("confirm_password")
|
||||
|
||||
# Validate inputs
|
||||
if not current_password:
|
||||
flash('Current password is required', 'danger')
|
||||
return redirect(url_for('dashboard.settings'))
|
||||
|
||||
flash("Current password is required", "danger")
|
||||
return redirect(url_for("dashboard.settings"))
|
||||
|
||||
if new_password != confirm_password:
|
||||
flash('New passwords do not match', 'danger')
|
||||
return redirect(url_for('dashboard.settings'))
|
||||
|
||||
flash("New passwords do not match", "danger")
|
||||
return redirect(url_for("dashboard.settings"))
|
||||
|
||||
# Verify current password
|
||||
if not current_user.check_password(current_password):
|
||||
flash('Current password is incorrect', 'danger')
|
||||
return redirect(url_for('dashboard.settings'))
|
||||
|
||||
flash("Current password is incorrect", "danger")
|
||||
return redirect(url_for("dashboard.settings"))
|
||||
|
||||
# Update password
|
||||
current_user.set_password(new_password)
|
||||
db.session.commit()
|
||||
|
||||
flash('Password updated successfully', 'success')
|
||||
return redirect(url_for('dashboard.settings'))
|
||||
|
||||
return render_template(
|
||||
'dashboard/settings.html',
|
||||
title='User Settings'
|
||||
)
|
||||
|
||||
flash("Password updated successfully", "success")
|
||||
return redirect(url_for("dashboard.settings"))
|
||||
|
||||
return render_template("dashboard/settings.html", title="User Settings")
|
||||
|
|
|
@ -5,100 +5,101 @@ import csv
|
|||
import io
|
||||
import datetime
|
||||
|
||||
bp = Blueprint('importexport', __name__, url_prefix='/import-export')
|
||||
bp = Blueprint("importexport", __name__, url_prefix="/import-export")
|
||||
|
||||
MODEL_MAP = {
|
||||
'subnet': Subnet,
|
||||
'server': Server,
|
||||
'app': App
|
||||
}
|
||||
MODEL_MAP = {"subnet": Subnet, "server": Server, "app": App}
|
||||
|
||||
@bp.route('/export/<model_name>', methods=['GET'])
|
||||
|
||||
@bp.route("/export/<model_name>", methods=["GET"])
|
||||
def export_model(model_name):
|
||||
if model_name not in MODEL_MAP:
|
||||
return jsonify({'error': 'Invalid model name'}), 400
|
||||
|
||||
return jsonify({"error": "Invalid model name"}), 400
|
||||
|
||||
model = MODEL_MAP[model_name]
|
||||
instances = model.query.all()
|
||||
|
||||
|
||||
# Create a CSV file in memory
|
||||
output = io.StringIO()
|
||||
writer = csv.writer(output)
|
||||
|
||||
|
||||
# Get column names from model
|
||||
columns = [column.name for column in model.__table__.columns]
|
||||
writer.writerow(columns)
|
||||
|
||||
|
||||
# Write data
|
||||
for instance in instances:
|
||||
row = [getattr(instance, column) for column in columns]
|
||||
writer.writerow(row)
|
||||
|
||||
|
||||
# Create response
|
||||
timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
filename = f"{model_name}_{timestamp}.csv"
|
||||
|
||||
|
||||
response = make_response(output.getvalue())
|
||||
response.headers['Content-Disposition'] = f'attachment; filename={filename}'
|
||||
response.headers['Content-type'] = 'text/csv'
|
||||
|
||||
response.headers["Content-Disposition"] = f"attachment; filename={filename}"
|
||||
response.headers["Content-type"] = "text/csv"
|
||||
|
||||
return response
|
||||
|
||||
@bp.route('/import/<model_name>', methods=['GET', 'POST'])
|
||||
|
||||
@bp.route("/import/<model_name>", methods=["GET", "POST"])
|
||||
def import_model(model_name):
|
||||
if model_name not in MODEL_MAP:
|
||||
return jsonify({'error': 'Invalid model name'}), 400
|
||||
|
||||
return jsonify({"error": "Invalid model name"}), 400
|
||||
|
||||
model = MODEL_MAP[model_name]
|
||||
|
||||
if request.method == 'GET':
|
||||
|
||||
if request.method == "GET":
|
||||
# Show import form
|
||||
return render_template('import_form.html', model_name=model_name)
|
||||
|
||||
return render_template("import_form.html", model_name=model_name)
|
||||
|
||||
# Process CSV upload
|
||||
if 'file' not in request.files:
|
||||
return jsonify({'error': 'No file part'}), 400
|
||||
|
||||
file = request.files['file']
|
||||
if file.filename == '':
|
||||
return jsonify({'error': 'No selected file'}), 400
|
||||
|
||||
if not file.filename.endswith('.csv'):
|
||||
return jsonify({'error': 'File must be CSV format'}), 400
|
||||
|
||||
if "file" not in request.files:
|
||||
return jsonify({"error": "No file part"}), 400
|
||||
|
||||
file = request.files["file"]
|
||||
if file.filename == "":
|
||||
return jsonify({"error": "No selected file"}), 400
|
||||
|
||||
if not file.filename.endswith(".csv"):
|
||||
return jsonify({"error": "File must be CSV format"}), 400
|
||||
|
||||
try:
|
||||
# Read CSV
|
||||
stream = io.StringIO(file.stream.read().decode("UTF8"), newline=None)
|
||||
csv_reader = csv.reader(stream)
|
||||
|
||||
|
||||
# Get headers
|
||||
headers = next(csv_reader)
|
||||
|
||||
|
||||
# Validate required columns
|
||||
required_columns = [col.name for col in model.__table__.columns
|
||||
if not col.nullable and col.name != 'id']
|
||||
|
||||
required_columns = [
|
||||
col.name
|
||||
for col in model.__table__.columns
|
||||
if not col.nullable and col.name != "id"
|
||||
]
|
||||
|
||||
for col in required_columns:
|
||||
if col not in headers:
|
||||
return jsonify({'error': f'Required column {col} missing'}), 400
|
||||
|
||||
return jsonify({"error": f"Required column {col} missing"}), 400
|
||||
|
||||
# Process rows
|
||||
imported = 0
|
||||
for row in csv_reader:
|
||||
data = dict(zip(headers, row))
|
||||
|
||||
|
||||
# Remove id to create new record
|
||||
if 'id' in data:
|
||||
del data['id']
|
||||
|
||||
if "id" in data:
|
||||
del data["id"]
|
||||
|
||||
# Create new instance
|
||||
instance = model(**data)
|
||||
db.session.add(instance)
|
||||
imported += 1
|
||||
|
||||
|
||||
db.session.commit()
|
||||
return jsonify({'success': f'Imported {imported} records successfully'})
|
||||
|
||||
return jsonify({"success": f"Imported {imported} records successfully"})
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({'error': str(e)}), 500
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
|
|
@ -1,260 +1,267 @@
|
|||
from flask import Blueprint, render_template, redirect, url_for, request, flash, jsonify
|
||||
from flask_login import login_required
|
||||
from app.core.models import Subnet, Server
|
||||
from app.core.models import Subnet, Server, App
|
||||
from app.core.extensions import db
|
||||
from app.scripts.ip_scanner import scan
|
||||
import ipaddress
|
||||
from datetime import datetime
|
||||
import json
|
||||
|
||||
bp = Blueprint('ipam', __name__, url_prefix='/ipam')
|
||||
bp = Blueprint("ipam", __name__, url_prefix="/ipam")
|
||||
|
||||
@bp.route('/')
|
||||
|
||||
@bp.route("/")
|
||||
@login_required
|
||||
def ipam_home():
|
||||
"""Main IPAM dashboard"""
|
||||
subnets = Subnet.query.all()
|
||||
|
||||
|
||||
# Calculate usage for each subnet
|
||||
for subnet in subnets:
|
||||
network = ipaddress.ip_network(subnet.cidr, strict=False)
|
||||
max_hosts = network.num_addresses - 2 if network.prefixlen < 31 else network.num_addresses
|
||||
max_hosts = (
|
||||
network.num_addresses - 2
|
||||
if network.prefixlen < 31
|
||||
else network.num_addresses
|
||||
)
|
||||
used_count = Server.query.filter_by(subnet_id=subnet.id).count()
|
||||
subnet.usage_percent = (used_count / max_hosts) * 100 if max_hosts > 0 else 0
|
||||
|
||||
|
||||
return render_template(
|
||||
'ipam/index.html',
|
||||
title='IPAM Dashboard',
|
||||
subnets=subnets,
|
||||
now=datetime.now()
|
||||
"ipam/index.html", title="IPAM Dashboard", subnets=subnets, now=datetime.now()
|
||||
)
|
||||
|
||||
@bp.route('/subnet/new', methods=['GET', 'POST'])
|
||||
|
||||
@bp.route("/subnet/new", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def subnet_new():
|
||||
"""Create a new subnet"""
|
||||
if request.method == 'POST':
|
||||
cidr = request.form.get('cidr')
|
||||
location = request.form.get('location')
|
||||
auto_scan = request.form.get('auto_scan') == 'on'
|
||||
|
||||
if request.method == "POST":
|
||||
cidr = request.form.get("cidr")
|
||||
location = request.form.get("location")
|
||||
auto_scan = request.form.get("auto_scan") == "on"
|
||||
|
||||
# Basic validation
|
||||
if not cidr or not location:
|
||||
flash('Please fill in all required fields', 'danger')
|
||||
return render_template(
|
||||
'ipam/subnet_form.html',
|
||||
title='New Subnet'
|
||||
)
|
||||
|
||||
flash("Please fill in all required fields", "danger")
|
||||
return render_template("ipam/subnet_form.html", title="New Subnet")
|
||||
|
||||
# Validate CIDR format
|
||||
try:
|
||||
ipaddress.ip_network(cidr, strict=False)
|
||||
except ValueError:
|
||||
flash('Invalid CIDR format', 'danger')
|
||||
return render_template(
|
||||
'ipam/subnet_form.html',
|
||||
title='New Subnet'
|
||||
)
|
||||
|
||||
flash("Invalid CIDR format", "danger")
|
||||
return render_template("ipam/subnet_form.html", title="New Subnet")
|
||||
|
||||
# Check if CIDR already exists
|
||||
if Subnet.query.filter_by(cidr=cidr).first():
|
||||
flash('Subnet already exists', 'danger')
|
||||
return render_template(
|
||||
'ipam/subnet_form.html',
|
||||
title='New Subnet'
|
||||
)
|
||||
|
||||
flash("Subnet already exists", "danger")
|
||||
return render_template("ipam/subnet_form.html", title="New Subnet")
|
||||
|
||||
# Create new subnet with JSON string for active_hosts, not a Python list
|
||||
subnet = Subnet(
|
||||
cidr=cidr,
|
||||
location=location,
|
||||
active_hosts=json.dumps([]), # Convert empty list to JSON string
|
||||
last_scanned=None,
|
||||
auto_scan=auto_scan
|
||||
auto_scan=auto_scan,
|
||||
)
|
||||
|
||||
|
||||
db.session.add(subnet)
|
||||
db.session.commit()
|
||||
|
||||
flash('Subnet created successfully', 'success')
|
||||
return redirect(url_for('ipam.subnet_view', subnet_id=subnet.id))
|
||||
|
||||
return render_template(
|
||||
'ipam/subnet_form.html',
|
||||
title='New Subnet'
|
||||
)
|
||||
|
||||
@bp.route('/subnet/<int:subnet_id>')
|
||||
flash("Subnet created successfully", "success")
|
||||
return redirect(url_for("ipam.subnet_view", subnet_id=subnet.id))
|
||||
|
||||
return render_template("ipam/subnet_form.html", title="New Subnet")
|
||||
|
||||
|
||||
@bp.route("/subnet/<int:subnet_id>")
|
||||
@login_required
|
||||
def subnet_view(subnet_id):
|
||||
"""View a specific subnet"""
|
||||
"""View a subnet and all its hosts"""
|
||||
subnet = Subnet.query.get_or_404(subnet_id)
|
||||
|
||||
# Get all servers in this subnet
|
||||
|
||||
# Get servers in this subnet
|
||||
servers = Server.query.filter_by(subnet_id=subnet_id).all()
|
||||
|
||||
# Parse CIDR for display
|
||||
|
||||
# Get applications in this subnet
|
||||
subnet_apps = []
|
||||
for server in servers:
|
||||
apps = App.query.filter_by(server_id=server.id).all()
|
||||
subnet_apps.extend(apps)
|
||||
|
||||
# Calculate usage statistics
|
||||
network = ipaddress.ip_network(subnet.cidr, strict=False)
|
||||
subnet_info = {
|
||||
'network_address': str(network.network_address),
|
||||
'broadcast_address': str(network.broadcast_address),
|
||||
'netmask': str(network.netmask),
|
||||
'num_addresses': network.num_addresses,
|
||||
'host_range': f"{str(network.network_address + 1)} - {str(network.broadcast_address - 1)}" if network.prefixlen < 31 else subnet.cidr
|
||||
}
|
||||
|
||||
total_ips = network.num_addresses - 2 # Subtract network and broadcast addresses
|
||||
used_ips = Server.query.filter_by(subnet_id=subnet_id).count()
|
||||
|
||||
return render_template(
|
||||
'ipam/subnet_view.html',
|
||||
title=subnet.cidr,
|
||||
"ipam/subnet_view.html",
|
||||
title=f"Subnet {subnet.cidr}",
|
||||
subnet=subnet,
|
||||
subnet_info=subnet_info,
|
||||
servers=servers,
|
||||
now=datetime.now()
|
||||
subnet_apps=subnet_apps,
|
||||
total_ips=total_ips,
|
||||
used_ips=used_ips,
|
||||
)
|
||||
|
||||
@bp.route('/subnet/<int:subnet_id>/edit', methods=['GET', 'POST'])
|
||||
|
||||
@bp.route("/subnet/<int:subnet_id>/edit", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def subnet_edit(subnet_id):
|
||||
"""Edit a subnet"""
|
||||
subnet = Subnet.query.get_or_404(subnet_id)
|
||||
|
||||
if request.method == 'POST':
|
||||
cidr = request.form.get('cidr')
|
||||
location = request.form.get('location')
|
||||
auto_scan = request.form.get('auto_scan') == 'on'
|
||||
|
||||
|
||||
if request.method == "POST":
|
||||
cidr = request.form.get("cidr")
|
||||
location = request.form.get("location")
|
||||
auto_scan = request.form.get("auto_scan") == "on"
|
||||
|
||||
# Validate inputs
|
||||
if not all([cidr, location]):
|
||||
flash('All fields are required', 'danger')
|
||||
return render_template('ipam/subnet_form.html',
|
||||
title='Edit Subnet',
|
||||
subnet=subnet,
|
||||
edit_mode=True)
|
||||
|
||||
flash("All fields are required", "danger")
|
||||
return render_template(
|
||||
"ipam/subnet_form.html",
|
||||
title="Edit Subnet",
|
||||
subnet=subnet,
|
||||
edit_mode=True,
|
||||
)
|
||||
|
||||
# Validate CIDR format
|
||||
try:
|
||||
ipaddress.ip_network(cidr, strict=False)
|
||||
except ValueError:
|
||||
flash('Invalid CIDR format', 'danger')
|
||||
return render_template('ipam/subnet_form.html',
|
||||
title='Edit Subnet',
|
||||
subnet=subnet,
|
||||
edit_mode=True)
|
||||
|
||||
flash("Invalid CIDR format", "danger")
|
||||
return render_template(
|
||||
"ipam/subnet_form.html",
|
||||
title="Edit Subnet",
|
||||
subnet=subnet,
|
||||
edit_mode=True,
|
||||
)
|
||||
|
||||
# Update subnet
|
||||
subnet.cidr = cidr
|
||||
subnet.location = location
|
||||
subnet.auto_scan = auto_scan
|
||||
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
flash(f'Subnet {cidr} has been updated', 'success')
|
||||
return redirect(url_for('ipam.subnet_view', subnet_id=subnet.id))
|
||||
flash(f"Subnet {cidr} has been updated", "success")
|
||||
return redirect(url_for("ipam.subnet_view", subnet_id=subnet.id))
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
flash(f'Error updating subnet: {str(e)}', 'danger')
|
||||
|
||||
return render_template('ipam/subnet_form.html',
|
||||
title='Edit Subnet',
|
||||
subnet=subnet,
|
||||
edit_mode=True)
|
||||
flash(f"Error updating subnet: {str(e)}", "danger")
|
||||
|
||||
@bp.route('/subnet/<int:subnet_id>/delete', methods=['POST'])
|
||||
return render_template(
|
||||
"ipam/subnet_form.html", title="Edit Subnet", subnet=subnet, edit_mode=True
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/subnet/<int:subnet_id>/delete", methods=["POST"])
|
||||
@login_required
|
||||
def subnet_delete(subnet_id):
|
||||
"""Delete a subnet"""
|
||||
subnet = Subnet.query.get_or_404(subnet_id)
|
||||
|
||||
|
||||
# Check if subnet has servers
|
||||
servers_count = Server.query.filter_by(subnet_id=subnet_id).count()
|
||||
if servers_count > 0:
|
||||
flash(f'Cannot delete subnet {subnet.cidr}. It has {servers_count} servers assigned.', 'danger')
|
||||
return redirect(url_for('ipam.subnet_view', subnet_id=subnet_id))
|
||||
|
||||
flash(
|
||||
f"Cannot delete subnet {subnet.cidr}. It has {servers_count} servers assigned.",
|
||||
"danger",
|
||||
)
|
||||
return redirect(url_for("ipam.subnet_view", subnet_id=subnet_id))
|
||||
|
||||
db.session.delete(subnet)
|
||||
db.session.commit()
|
||||
|
||||
flash(f'Subnet {subnet.cidr} has been deleted', 'success')
|
||||
return redirect(url_for('ipam.ipam_home'))
|
||||
|
||||
@bp.route('/subnet/<int:subnet_id>/scan', methods=['POST'])
|
||||
flash(f"Subnet {subnet.cidr} has been deleted", "success")
|
||||
return redirect(url_for("ipam.ipam_home"))
|
||||
|
||||
|
||||
@bp.route("/subnet/<int:subnet_id>/scan", methods=["POST"])
|
||||
@login_required
|
||||
def subnet_scan(subnet_id):
|
||||
"""Manually scan a subnet"""
|
||||
subnet = Subnet.query.get_or_404(subnet_id)
|
||||
|
||||
|
||||
try:
|
||||
# Call the scan function with manual_trigger=True
|
||||
scan(subnet, manual_trigger=True)
|
||||
db.session.commit()
|
||||
flash(f'Scan completed for subnet {subnet.cidr}', 'success')
|
||||
flash(f"Scan completed for subnet {subnet.cidr}", "success")
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
flash(f'Error scanning subnet: {str(e)}', 'danger')
|
||||
|
||||
return redirect(url_for('ipam.subnet_view', subnet_id=subnet_id))
|
||||
flash(f"Error scanning subnet: {str(e)}", "danger")
|
||||
|
||||
@bp.route('/subnet/<int:subnet_id>/force-delete', methods=['POST'])
|
||||
return redirect(url_for("ipam.subnet_view", subnet_id=subnet_id))
|
||||
|
||||
|
||||
@bp.route("/subnet/<int:subnet_id>/force-delete", methods=["POST"])
|
||||
@login_required
|
||||
def subnet_force_delete(subnet_id):
|
||||
"""Force delete a subnet and all its related servers and applications"""
|
||||
subnet = Subnet.query.get_or_404(subnet_id)
|
||||
|
||||
|
||||
try:
|
||||
# Get all servers to be deleted for reporting
|
||||
servers = Server.query.filter_by(subnet_id=subnet_id).all()
|
||||
server_count = len(servers)
|
||||
|
||||
|
||||
# This will cascade delete all related servers and their applications
|
||||
db.session.delete(subnet)
|
||||
db.session.commit()
|
||||
|
||||
flash(f'Subnet {subnet.cidr} and {server_count} related servers were deleted successfully', 'success')
|
||||
return redirect(url_for('dashboard.ipam_home'))
|
||||
|
||||
flash(
|
||||
f"Subnet {subnet.cidr} and {server_count} related servers were deleted successfully",
|
||||
"success",
|
||||
)
|
||||
return redirect(url_for("dashboard.ipam_home"))
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
flash(f'Error deleting subnet: {str(e)}', 'danger')
|
||||
return redirect(url_for('dashboard.subnet_view', subnet_id=subnet_id))
|
||||
flash(f"Error deleting subnet: {str(e)}", "danger")
|
||||
return redirect(url_for("dashboard.subnet_view", subnet_id=subnet_id))
|
||||
|
||||
@bp.route('/subnet/create-ajax', methods=['POST'])
|
||||
|
||||
@bp.route("/subnet/create-ajax", methods=["POST"])
|
||||
@login_required
|
||||
def subnet_create_ajax():
|
||||
"""Create a subnet via AJAX"""
|
||||
data = request.json
|
||||
if not data:
|
||||
return jsonify({'success': False, 'error': 'No data provided'})
|
||||
|
||||
cidr = data.get('cidr')
|
||||
location = data.get('location')
|
||||
auto_scan = data.get('auto_scan', False)
|
||||
|
||||
return jsonify({"success": False, "error": "No data provided"})
|
||||
|
||||
cidr = data.get("cidr")
|
||||
location = data.get("location")
|
||||
auto_scan = data.get("auto_scan", False)
|
||||
|
||||
if not cidr or not location:
|
||||
return jsonify({'success': False, 'error': 'CIDR and location are required'})
|
||||
|
||||
return jsonify({"success": False, "error": "CIDR and location are required"})
|
||||
|
||||
# Validate CIDR
|
||||
try:
|
||||
network = ipaddress.ip_network(cidr, strict=False)
|
||||
except ValueError as e:
|
||||
return jsonify({'success': False, 'error': f'Invalid CIDR: {str(e)}'})
|
||||
|
||||
return jsonify({"success": False, "error": f"Invalid CIDR: {str(e)}"})
|
||||
|
||||
# Create subnet
|
||||
subnet = Subnet(
|
||||
cidr=cidr,
|
||||
location=location,
|
||||
auto_scan=auto_scan,
|
||||
active_hosts=json.dumps([])
|
||||
cidr=cidr, location=location, auto_scan=auto_scan, active_hosts=json.dumps([])
|
||||
)
|
||||
|
||||
|
||||
try:
|
||||
db.session.add(subnet)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'subnet_id': subnet.id,
|
||||
'cidr': subnet.cidr,
|
||||
'location': subnet.location
|
||||
})
|
||||
|
||||
return jsonify(
|
||||
{
|
||||
"success": True,
|
||||
"subnet_id": subnet.id,
|
||||
"cidr": subnet.cidr,
|
||||
"location": subnet.location,
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({'success': False, 'error': str(e)})
|
||||
return jsonify({"success": False, "error": str(e)})
|
||||
|
|
96
app/routes/static.py
Normal file
96
app/routes/static.py
Normal file
|
@ -0,0 +1,96 @@
|
|||
from flask import Blueprint, send_from_directory, current_app
|
||||
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/css/tabler.min.css")
|
||||
def tabler_css():
|
||||
"""Serve tabler CSS from static folder or download if missing"""
|
||||
css_path = os.path.join(current_app.static_folder, "css")
|
||||
|
||||
# Create directory if it doesn't exist
|
||||
if not os.path.exists(css_path):
|
||||
os.makedirs(css_path)
|
||||
|
||||
css_file = os.path.join(css_path, "tabler.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@latest/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.min.css from CDN")
|
||||
else:
|
||||
print(f"Failed to download tabler CSS: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f"Error downloading tabler CSS: {e}")
|
||||
|
||||
return send_from_directory(css_path, "tabler.min.css")
|
||||
|
||||
|
||||
@bp.route("/static/img/favicon.png")
|
||||
def favicon():
|
||||
"""Serve favicon from static folder or create a default one if missing"""
|
||||
img_path = os.path.join(current_app.static_folder, "img")
|
||||
|
||||
# Create directory if it doesn't exist
|
||||
if not os.path.exists(img_path):
|
||||
os.makedirs(img_path)
|
||||
|
||||
favicon_file = os.path.join(img_path, "favicon.png")
|
||||
|
||||
# If file doesn't exist, create a simple one
|
||||
if not os.path.exists(favicon_file):
|
||||
# Try to download a default favicon
|
||||
import requests
|
||||
|
||||
try:
|
||||
# Using a simple placeholder favicon
|
||||
cdn_url = "https://www.google.com/favicon.ico"
|
||||
response = requests.get(cdn_url)
|
||||
if response.status_code == 200:
|
||||
with open(favicon_file, "wb") as f:
|
||||
f.write(response.content)
|
||||
print(f"Created default favicon.png")
|
||||
else:
|
||||
print(f"Failed to download favicon: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f"Error creating favicon: {e}")
|
||||
|
||||
return send_from_directory(img_path, "favicon.png")
|
Loading…
Add table
Add a link
Reference in a new issue